From 6deb9f966eb9a280cc16428ba9324ffc15ea60a8 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 18 Aug 2016 15:49:51 +0200 Subject: Live timelines using ActionCable --- app/assets/javascripts/api/accounts.coffee | 3 -- app/assets/javascripts/api/accounts/lookup.coffee | 3 -- app/assets/javascripts/api/follows.coffee | 3 -- app/assets/javascripts/api/statuses.coffee | 3 -- app/assets/javascripts/application.js | 1 - app/assets/javascripts/cable.js | 13 +++++++++ app/assets/javascripts/channels/timeline.js | 13 +++++++++ app/assets/javascripts/oauth/applications.coffee | 3 -- app/assets/javascripts/profiler.coffee | 5 ---- app/assets/javascripts/settings.coffee | 3 -- app/assets/javascripts/statuses.coffee | 3 -- app/channels/application_cable/channel.rb | 5 ++++ app/channels/application_cable/connection.rb | 20 +++++++++++++ app/channels/timeline_channel.rb | 10 +++++++ app/controllers/application_controller.rb | 2 +- app/services/fan_out_on_write_service.rb | 35 ++++++++++++++++------- app/services/precompute_feed_service.rb | 2 +- 17 files changed, 88 insertions(+), 39 deletions(-) delete mode 100644 app/assets/javascripts/api/accounts.coffee delete mode 100644 app/assets/javascripts/api/accounts/lookup.coffee delete mode 100644 app/assets/javascripts/api/follows.coffee delete mode 100644 app/assets/javascripts/api/statuses.coffee create mode 100644 app/assets/javascripts/cable.js create mode 100644 app/assets/javascripts/channels/timeline.js delete mode 100644 app/assets/javascripts/oauth/applications.coffee delete mode 100644 app/assets/javascripts/profiler.coffee delete mode 100644 app/assets/javascripts/settings.coffee delete mode 100644 app/assets/javascripts/statuses.coffee create mode 100644 app/channels/application_cable/channel.rb create mode 100644 app/channels/application_cable/connection.rb create mode 100644 app/channels/timeline_channel.rb (limited to 'app') diff --git a/app/assets/javascripts/api/accounts.coffee b/app/assets/javascripts/api/accounts.coffee deleted file mode 100644 index 24f83d18b..000000000 --- a/app/assets/javascripts/api/accounts.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/api/accounts/lookup.coffee b/app/assets/javascripts/api/accounts/lookup.coffee deleted file mode 100644 index 24f83d18b..000000000 --- a/app/assets/javascripts/api/accounts/lookup.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/api/follows.coffee b/app/assets/javascripts/api/follows.coffee deleted file mode 100644 index 24f83d18b..000000000 --- a/app/assets/javascripts/api/follows.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/api/statuses.coffee b/app/assets/javascripts/api/statuses.coffee deleted file mode 100644 index 24f83d18b..000000000 --- a/app/assets/javascripts/api/statuses.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e07c5a830..646c5aba4 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,5 +12,4 @@ // //= require jquery //= require jquery_ujs -//= require turbolinks //= require_tree . diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js new file mode 100644 index 000000000..71ee1e66d --- /dev/null +++ b/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the rails generate channel command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/app/assets/javascripts/channels/timeline.js b/app/assets/javascripts/channels/timeline.js new file mode 100644 index 000000000..ca7c50d12 --- /dev/null +++ b/app/assets/javascripts/channels/timeline.js @@ -0,0 +1,13 @@ +App.timeline = App.cable.subscriptions.create("TimelineChannel", { + connected: function() { + console.log('Connected'); + }, + + disconnected: function() { + console.log('Disconnected'); + }, + + received: function(data) { + console.log(JSON.parse(data.message)); + } +}); diff --git a/app/assets/javascripts/oauth/applications.coffee b/app/assets/javascripts/oauth/applications.coffee deleted file mode 100644 index 24f83d18b..000000000 --- a/app/assets/javascripts/oauth/applications.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/profiler.coffee b/app/assets/javascripts/profiler.coffee deleted file mode 100644 index 40dfd0af6..000000000 --- a/app/assets/javascripts/profiler.coffee +++ /dev/null @@ -1,5 +0,0 @@ -$ -> - $(document).on 'turbolinks:load', -> - unless typeof window.MiniProfiler == 'undefined' - window.MiniProfiler.init() - window.MiniProfiler.pageTransition() diff --git a/app/assets/javascripts/settings.coffee b/app/assets/javascripts/settings.coffee deleted file mode 100644 index 24f83d18b..000000000 --- a/app/assets/javascripts/settings.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/statuses.coffee b/app/assets/javascripts/statuses.coffee deleted file mode 100644 index 24f83d18b..000000000 --- a/app/assets/javascripts/statuses.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 000000000..d56fa30f4 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 000000000..f7cbe5485 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,20 @@ +# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. +module ApplicationCable + class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + self.current_user = find_verified_user + end + + protected + + def find_verified_user + if verified_user = env['warden'].user + verified_user + else + reject_unauthorized_connection + end + end + end +end diff --git a/app/channels/timeline_channel.rb b/app/channels/timeline_channel.rb new file mode 100644 index 000000000..c128fae58 --- /dev/null +++ b/app/channels/timeline_channel.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. +class TimelineChannel < ApplicationCable::Channel + def subscribed + stream_from "timeline:#{current_user.id}" + end + + def unsubscribed + # Any cleanup needed when channel is unsubscribed + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d5eaecdb1..f90628b0d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,7 +5,7 @@ class ApplicationController < ActionController::Base # Profiling before_action do - if current_user && current_user.admin? + if (current_user && current_user.admin?) || Rails.env == 'development' Rack::MiniProfiler.authorize_request end end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index c8c775b93..34684a06f 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -10,13 +10,13 @@ class FanOutOnWriteService < BaseService private def deliver_to_self(status) - push(:home, status.account.id, status) + push(:home, status.account, status) end def deliver_to_followers(status) status.account.followers.each do |follower| next if !follower.local? || FeedManager.filter_status?(status, follower) - push(:home, follower.id, status) + push(:home, follower, status) end end @@ -24,23 +24,38 @@ class FanOutOnWriteService < BaseService status.mentions.each do |mention| mentioned_account = mention.account next unless mentioned_account.local? - push(:mentions, mentioned_account.id, status) + push(:mentions, mentioned_account, status) end end - def push(type, receiver_id, status) - redis.zadd(FeedManager.key(type, receiver_id), status.id, status.id) - trim(type, receiver_id) + def push(type, receiver, status) + redis.zadd(FeedManager.key(type, receiver.id), status.id, status.id) + trim(type, receiver) + ActionCable.server.broadcast("timeline:#{receiver.id}", message: inline_render(receiver, status)) end - def trim(type, receiver_id) - return unless redis.zcard(FeedManager.key(type, receiver_id)) > FeedManager::MAX_ITEMS + def trim(type, receiver) + return unless redis.zcard(FeedManager.key(type, receiver.id)) > FeedManager::MAX_ITEMS - last = redis.zrevrange(FeedManager.key(type, receiver_id), FeedManager::MAX_ITEMS - 1, FeedManager::MAX_ITEMS - 1) - redis.zremrangebyscore(FeedManager.key(type, receiver_id), '-inf', "(#{last.last}") + last = redis.zrevrange(FeedManager.key(type, receiver.id), FeedManager::MAX_ITEMS - 1, FeedManager::MAX_ITEMS - 1) + redis.zremrangebyscore(FeedManager.key(type, receiver.id), '-inf', "(#{last.last}") end def redis $redis end + + def inline_render(receiver, status) + rabl_scope = Class.new(BaseService) do + def initialize(account) + @account = account + end + + def current_user + @account.user + end + end + + Rabl::Renderer.new('api/statuses/show', status, view_path: 'app/views', format: :json, scope: rabl_scope.new(receiver)).render + end end diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb index de4201a8f..c8050bbd0 100644 --- a/app/services/precompute_feed_service.rb +++ b/app/services/precompute_feed_service.rb @@ -8,7 +8,7 @@ class PrecomputeFeedService < BaseService Status.send("as_#{type}_timeline", account).order('created_at desc').limit(FeedManager::MAX_ITEMS).each do |status| next if type == :home && FeedManager.filter_status?(status, account) - redis.zadd(FeedManager.key(type, receiver_id), status.id, status.id) + redis.zadd(FeedManager.key(type, account.id), status.id, status.id) instant_return << status unless instant_return.size > limit end -- cgit