From 5e69e1af4e8f7f22900c0d6f202be1e4e6c62fdf Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 27 Nov 2012 00:22:52 +0100 Subject: [PATCH] Added ajax long polling. --- .../_dashboard/activity_stream.js.coffee | 27 +-- .../app/controllers/_dashboard/rss.js.coffee | 45 +++- .../controllers/_dashboard/ticket.js.coffee | 47 ++-- .../controllers/agent_ticket_view.js.coffee | 36 ++- .../app/lib/app_post/ajax.js.coffee | 2 - .../app/lib/app_post/websocket.js.coffee | 212 +++++++++++++----- .../app/views/dashboard/rss.jst.eco | 3 + app/controllers/activity_controller.rb | 2 +- app/controllers/rss_controller.rb | 1 + .../ticket_overviews_controller.rb | 32 +-- app/models/ticket.rb | 6 +- lib/rss.rb | 1 + lib/session.rb | 18 +- 13 files changed, 314 insertions(+), 118 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee b/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee index 4113b681e..1e91e4379 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee @@ -8,8 +8,6 @@ class App.DashboardActivityStream extends App.Controller super @items = [] - # refresh list ever 140 sec. -# @interval( @fetch, 1400000, 'dashboard_activity_stream' ) @fetch() # bind to rebuild view event @@ -22,17 +20,20 @@ class App.DashboardActivityStream extends App.Controller if cache @load( cache ) -# # get data -# App.Com.ajax( -# id: 'dashoard_activity_stream', -# type: 'GET', -# url: '/api/activity_stream', -# data: { -# limit: @limit, -# } -# processData: true, -# success: @load -# ) + # init fetch via ajax, all other updates on time via websockets + else + App.Com.ajax( + id: 'dashoard_activity_stream' + type: 'GET' + url: '/api/activity_stream' + data: { + limit: 8 + } + processData: true + success: (data) => + App.Store.write( 'activity_stream', data ) + @load(data) + ) load: (data) => items = data.activity_stream diff --git a/app/assets/javascripts/app/controllers/_dashboard/rss.js.coffee b/app/assets/javascripts/app/controllers/_dashboard/rss.js.coffee index d95af74bd..b3130f8dd 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/rss.js.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/rss.js.coffee @@ -11,19 +11,42 @@ class App.DashboardRss extends App.Controller @fetch() fetch: => + + # get data from cache cache = App.Store.get( 'dashboard_rss' ) if cache - @load( cache ) + @render( cache ) - load: (data) => - items = data.items || [] - @head = data.head || '?' - @render(items) + # init fetch via ajax, all other updates on time via websockets + else + App.Com.ajax( + id: 'dashboard_rss' + type: 'GET' + url: '/api/rss_fetch' + data: { + limit: 8 + url: 'http://www.heise.de/newsticker/heise-atom.xml' + } + processData: true + success: (data) => + if data.message + @render( + head: 'Heise ATOM' + message: data.message + ) + else + App.Store.write( 'dashboard_rss', data ) + @render(data) + error: => + @render( + head: 'Heise ATOM' + message: 'Unable to fetch rss!' + ) + ) - render: (items) -> - html = App.view('dashboard/rss')( - head: @head, - items: items + render: (data) -> + @html App.view('dashboard/rss')( + head: data.head, + items: data.item || [] + message: data.message ) - html = $(html) - @html html diff --git a/app/assets/javascripts/app/controllers/_dashboard/ticket.js.coffee b/app/assets/javascripts/app/controllers/_dashboard/ticket.js.coffee index ce25164c2..9bb961166 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/ticket.js.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/ticket.js.coffee @@ -8,7 +8,7 @@ class App.DashboardTicket extends App.Controller constructor: -> super - @start_page = 1 + @start_page = 1 @navupdate '#' # set new key @@ -25,25 +25,37 @@ class App.DashboardTicket extends App.Controller # use cache of first page cache = App.Store.get( @key ) if cache -# @render( cache ) @load( cache ) - # get data -# App.Com.ajax( -# id: 'dashboard_ticket_' + @key, -# type: 'GET', -# url: '/api/ticket_overviews', -# data: { -# view: @view, -# view_mode: 'd', -# start_page: @start_page, -# } -# processData: true, -# success: @load -# ) + # init fetch via ajax, all other updates on time via websockets + else + App.Com.ajax( + id: 'dashboard_ticket_' + @key, + type: 'GET', + url: '/api/ticket_overviews', + data: { + view: @view, + view_mode: 'd', + start_page: @start_page, + } + processData: true, + success: (data) => + data.ajax = true + @load(data) + ) load: (data) => + if data.ajax + data.ajax = false + App.Store.write( @key, data ) + + # load user collection + App.Collection.load( type: 'User', data: data.collections.users ) + + # load ticket collection + App.Collection.load( type: 'Ticket', data: data.collections.tickets ) + # get meta data App.Overview.refresh( data.overview, options: { clear: true } ) @@ -57,11 +69,12 @@ class App.DashboardTicket extends App.Controller @log 'refetch...', record @fetch() -# App.Store.write( @key, data ) - @render( data ) render: (data) -> + return if !data + return if !data.ticket_list + return if !data.overview @overview = data.overview @tickets_count = data.tickets_count diff --git a/app/assets/javascripts/app/controllers/agent_ticket_view.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_view.js.coffee index 3f1ad4bec..cc8f28b63 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_view.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_view.js.coffee @@ -37,12 +37,42 @@ class Index extends App.Controller # use cache of first page cache = App.Store.get( @key ) if cache - @overview = cache.overview - @tickets_count = cache.tickets_count - @ticket_list = cache.ticket_list @load(cache) + # init fetch via ajax, all other updates on time via websockets + else + App.Com.ajax( + id: 'ticket_overview_' + @key, + type: 'GET', + url: '/api/ticket_overviews', + data: { + view: @view, + view_mode: @view_mode, + } + processData: true, + success: (data) => + data.ajax = true + @load(data) + ) + load: (data) => + return if !data + return if !data.ticket_list + return if !data.overview + + @overview = data.overview + @tickets_count = data.tickets_count + @ticket_list = data.ticket_list + + if data.ajax + data.ajax = false + App.Store.write( @key, data ) + + # load user collection + App.Collection.load( type: 'User', data: data.collections.users ) + + # load ticket collection + App.Collection.load( type: 'Ticket', data: data.collections.tickets ) # get meta data @overview = data.overview diff --git a/app/assets/javascripts/app/lib/app_post/ajax.js.coffee b/app/assets/javascripts/app/lib/app_post/ajax.js.coffee index df6efca59..5d33741d8 100644 --- a/app/assets/javascripts/app/lib/app_post/ajax.js.coffee +++ b/app/assets/javascripts/app/lib/app_post/ajax.js.coffee @@ -5,9 +5,7 @@ class App.Com @ajax: (args) -> # Must be a static method if _instance == undefined _instance ?= new _Singleton - _instance.ajax(args) - _instance # The actual Singleton class class _Singleton diff --git a/app/assets/javascripts/app/lib/app_post/websocket.js.coffee b/app/assets/javascripts/app/lib/app_post/websocket.js.coffee index 013210b3f..65ed5428f 100644 --- a/app/assets/javascripts/app/lib/app_post/websocket.js.coffee +++ b/app/assets/javascripts/app/lib/app_post/websocket.js.coffee @@ -9,7 +9,7 @@ class App.WebSocket @close: (args) -> # Must be a static method if _instance isnt undefined - _instance.close() + _instance.close(args) @send: (args) -> # Must be a static method @connect() @@ -20,6 +20,7 @@ class App.WebSocket _instance.auth(args) @_spool: -> + @connect() _instance.spool() # The actual Singleton class @@ -27,9 +28,12 @@ class _Singleton extends App.Controller @include App.Log queue: [] - supported: true - lastSpoolMessage: undefined - connectionEstablished: false + supported: true + lastSpoolMessage: undefined + connectionEstablished: false + connectionWasEstablished: false + backend: 'websocket' + client_id: undefined constructor: (@args) -> @@ -46,19 +50,22 @@ class _Singleton extends App.Controller @connect() send: (data) => - return if !@supported -# console.log 'ws:send trying', data, @ws, @ws.readyState - - # A value of 0 indicates that the connection has not yet been established. - # A value of 1 indicates that the connection is established and communication is possible. - # A value of 2 indicates that the connection is going through the closing handshake. - # A value of 3 indicates that the connection has been closed or could not be opened. - if @ws.readyState is 0 - @queue.push data + if @backend is 'ajax' + @_ajaxSend(data) else -# console.log( 'ws:send', data ) - string = JSON.stringify( data ) - @ws.send(string) + +# console.log 'ws:send trying', data, @ws, @ws.readyState + + # A value of 0 indicates that the connection has not yet been established. + # A value of 1 indicates that the connection is established and communication is possible. + # A value of 2 indicates that the connection is going through the closing handshake. + # A value of 3 indicates that the connection has been closed or could not be opened. + if @ws.readyState is 0 + @queue.push data + else +# console.log( 'ws:send', data ) + string = JSON.stringify( data ) + @ws.send(string) auth: (data) => return if !@supported @@ -77,7 +84,7 @@ class _Singleton extends App.Controller if @lastSpoolMessage data['timestamp'] = @lastSpoolMessage - @log 'Event', 'debug', 'spool', data + @log 'Websocket', 'debug', 'spool', data # ask for spool messages App.Event.trigger( @@ -89,37 +96,38 @@ class _Singleton extends App.Controller @lastSpoolMessage = Math.round( +new Date()/1000 ) close: => - return if !@supported + return if @backend is 'ajax' @ws.close() ping: => - return if !@supported + return if @backend is 'ajax' - @log 'Event', 'debug', 'send websockend ping' + @log 'Websocket', 'debug', 'send websockend ping' @send( { action: 'ping' } ) # check if ping is back within 2 min @clearDelay('websocket-ping-check') check = => - @log 'Event', 'notice', 'no websockend ping response, reconnect...' + @log 'Websocket', 'notice', 'no websockend ping response, reconnect...' @close() @delay check, 120000, 'websocket-ping-check' pong: -> - return if !@supported - @log 'Event', 'debug', 'received websockend ping' + return if @backend is 'ajax' + + @log 'Websocket', 'debug', 'received websockend ping' # test again after 1 min @delay @ping, 60000 connect: => + return if @backend is 'ajax' if !window.WebSocket - @error = new App.ErrorModal( - message: 'Sorry, no websocket support!' - ) - @supported = false + @backend = 'ajax' + @log 'WebSocket', 'notice', 'no support of websocket, use ajax long polling' + @_ajaxInit() return protocol = 'ws://' @@ -130,9 +138,10 @@ class _Singleton extends App.Controller # Set event handlers. @ws.onopen = => - @log 'Event', 'notice', 'new websocked connection open' + @log 'Websocket', 'notice', 'new websocket connection open' @connectionEstablished = true + @connectionWasEstablished = true # close error message show up (because try so connect again) if exists @clearDelay('websocket-no-connection-try-reconnect') @@ -144,7 +153,7 @@ class _Singleton extends App.Controller # empty queue for item in @queue - @log 'Event', 'debug', 'empty ws queue', item + @log 'Websocket', 'debug', 'empty ws queue', item @send(item) @queue = [] @@ -153,41 +162,31 @@ class _Singleton extends App.Controller @ws.onmessage = (e) => pipe = JSON.parse( e.data ) - @log 'Event', 'debug', 'ws:onmessage', pipe - - # go through all blocks - for item in pipe - - # reset reconnect loop - if item['action'] is 'pong' - @pong() - - # fill collection - if item['collection'] - @log 'Event', 'debug', "ws:onmessage collection:" + item['collection'] - App.Store.write( item['collection'], item['data'] ) - - # fire event - if item['event'] - if typeof item['event'] is 'object' - for event in item['event'] - @log 'Event', 'debug', "ws:onmessage event:" + event - App.Event.trigger( event, item['data'] ) - else - @log 'Event', 'debug', "ws:onmessage event:" + item['event'] - App.Event.trigger( item['event'], item['data'] ) + @log 'Websocket', 'debug', 'ws:onmessage', pipe + @_receiveMessage(pipe) @ws.onclose = (e) => - @log 'Event', 'debug', "ws:onclose", e + @log 'Websocket', 'debug', "ws:onclose", e # set timestamp to get spool messages later if @connectionEstablished @lastSpoolMessage = Math.round( +new Date()/1000 ) @connectionEstablished = false + return if @backend is 'ajax' + # show error message, first try to reconnect if !@error message = => + + # use fallback if no connection was possible + if !@connectionWasEstablished + @backend = 'ajax' + @log 'WebSocket', 'notice', 'No connection to websocket, use use ajax long polling as fallback' + @_ajaxInit() + return + + # show reconnect message @error = new App.ErrorModal( message: 'No connection to websocket, trying to reconnect...' ) @@ -197,5 +196,110 @@ class _Singleton extends App.Controller @delay @connect, 4500 @ws.onerror = (e) => - @log 'Event', 'debug', "ws:onerror", e + @log 'Websocket', 'debug', "ws:onerror", e + _receiveMessage: (data = []) => + + # go through all blocks + for item in data + + # reset reconnect loop + if item['action'] is 'pong' + @pong() + + # fill collection + if item['collection'] + @log 'Websocket', 'debug', "onmessage collection:" + item['collection'] + App.Store.write( item['collection'], item['data'] ) + + # fire event + if item['event'] + if typeof item['event'] is 'object' + for event in item['event'] + @log 'Websocket', 'debug', "onmessage event:" + event + App.Event.trigger( event, item['data'] ) + else + @log 'Websocket', 'debug', "onmessage event:" + item['event'] + App.Event.trigger( item['event'], item['data'] ) + + _ajaxInit: (data = {}) => + + # return if init is already done and not forced + return if @_ajaxInitDone && !data.force + + # stop init request if new one is started + if @_ajaxInitWorking + console.log '@_ajaxInitWorking', @_ajaxInitWorking + @_ajaxInitWorking.abort() + + # call init request + @_ajaxInitWorking = App.Com.ajax( + type: 'POST' + url: '/api/message_send' + data: JSON.stringify({ data: { action: 'login' } }) + processData: false + queue: false + success: (data) => + if data.client_id + @log 'Websocket', 'notice', 'ajax:new client_id', data.client_id + @client_id = data.client_id + @_ajaxReceive() + @_ajaxSendQueue() + @_ajaxInitDone = true + @_ajaxInitWorking = false + error: => + @_ajaxInitDone = true + @_ajaxInitWorking = false + ) + + _ajaxSend: (data) => + @log 'Websocket', 'debug', 'ajax:sendmessage', data + if !@client_id || @client_id is undefined || !@_ajaxInitDone + @_ajaxInit() + @queue.push data + else + @queue.push data + @_ajaxSendQueue() + + _ajaxSendQueue: => + while !_.isEmpty(@queue) + data = @queue.shift() + App.Com.ajax( + type: 'POST' + url: '/api/message_send' + data: JSON.stringify({ client_id: @client_id, data: data }) + processData: false + queue: true + success: (data) => + if data && data.error + @client_id = undefined + @_ajaxInit( force: true ) + error: => + @client_id = undefined + @_ajaxInit( force: true ) + ) + + _ajaxReceive: => + return if !@client_id + return if @_ajaxReceiveWorking is true + @_ajaxReceiveWorking = true + App.Com.ajax( + id: 'message_receive', + type: 'POST' + url: '/api/message_receive' + data: JSON.stringify({ client_id: @client_id }) + processData: false + success: (data) => + @log 'Websocket', 'notice', 'ajax:onmessage', data + @_receiveMessage(data) + if data && data.error + @client_id = undefined + @_ajaxInit( force: true ) + @_ajaxReceiveWorking = false + @_ajaxReceive() + error: (data) => + @client_id = undefined + @_ajaxInit( force: true ) + @_ajaxReceiveWorking = false + @delay @_ajaxReceive, 5000 + ) diff --git a/app/assets/javascripts/app/views/dashboard/rss.jst.eco b/app/assets/javascripts/app/views/dashboard/rss.jst.eco index 171869459..5b849e6c2 100644 --- a/app/assets/javascripts/app/views/dashboard/rss.jst.eco +++ b/app/assets/javascripts/app/views/dashboard/rss.jst.eco @@ -1,6 +1,9 @@

<%- @T( @head ) %>

+ <% if @message: %> + <%- @T(@message) %> + <% end %>
    <% for item in @items: %>
  • <%= item.title %>"
  • diff --git a/app/controllers/activity_controller.rb b/app/controllers/activity_controller.rb index a594727c7..1e80f7d6d 100644 --- a/app/controllers/activity_controller.rb +++ b/app/controllers/activity_controller.rb @@ -3,7 +3,7 @@ class ActivityController < ApplicationController # GET /api/activity_stream def activity_stream - activity_stream = History.activity_stream_fulldata(current_user, params[:limit]) + activity_stream = History.activity_stream_fulldata( current_user, params[:limit] ) # return result render :json => activity_stream diff --git a/app/controllers/rss_controller.rb b/app/controllers/rss_controller.rb index 8c51e2f21..e4e8de697 100644 --- a/app/controllers/rss_controller.rb +++ b/app/controllers/rss_controller.rb @@ -20,6 +20,7 @@ curl http://localhost/api/rss_fetch.json -v -u #{login}:#{password} -H "Content- items = RSS.fetch(params[:url], params[:limit]) if items == nil render :json => { :message => "failed to fetch #{ params[:url] }", :status => :unprocessable_entity } + return end render :json => { :items => items } end diff --git a/app/controllers/ticket_overviews_controller.rb b/app/controllers/ticket_overviews_controller.rb index 25d73679e..adf68def5 100644 --- a/app/controllers/ticket_overviews_controller.rb +++ b/app/controllers/ticket_overviews_controller.rb @@ -22,8 +22,8 @@ class TicketOverviewsController < ApplicationController :array => true, ) tickets = [] - overview[:tickets].each {|ticket| - data = { :id => ticket.id } + overview[:tickets].each {|ticket_id| + data = { :id => ticket_id } tickets.push data } @@ -33,21 +33,24 @@ class TicketOverviewsController < ApplicationController :tickets => tickets, :tickets_count => overview[:tickets_count], } - return + return end overview = Ticket.overview( - :view => params[:view], - :view_mode => params[:view_mode], - :current_user_id => current_user.id, - :start_page => params[:start_page], - :array => true, + :view => params[:view], +# :view_mode => params[:view_mode], + :current_user => User.find( current_user.id ), + :array => true, ) - + if !overview + render :json => { :error => "No such view #{ params[:view] }!" }, :status => :unprocessable_entity + return + end + # get related users users = {} tickets = [] - overview[:tickets].each {|ticket| - data = Ticket.full_data(ticket.id) + overview[:ticket_list].each {|ticket_id| + data = Ticket.full_data(ticket_id) tickets.push data if !users[ data['owner_id'] ] users[ data['owner_id'] ] = User.user_data_full( data['owner_id'] ) @@ -84,12 +87,15 @@ class TicketOverviewsController < ApplicationController # return result render :json => { :overview => overview[:overview], - :tickets => tickets, + :ticket_list => overview[:ticket_list], :tickets_count => overview[:tickets_count], - :users => users, :bulk => { :group_id__owner_id => groups_users, }, + :collections => { + :users => users, + :tickets => tickets, + }, } end diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 3c4f34f28..c51c79aa2 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -217,6 +217,10 @@ class Ticket < ApplicationModel } } + if data[:view] && !overview_selected + return + end + # sortby # prio # state @@ -290,7 +294,7 @@ class Ticket < ApplicationModel count() return { - :tickets => ticket_ids, + :ticket_list => ticket_ids, :tickets_count => tickets_count, :overview => overview_selected_raw, } diff --git a/lib/rss.rb b/lib/rss.rb index 411122284..84f5704b1 100644 --- a/lib/rss.rb +++ b/lib/rss.rb @@ -29,6 +29,7 @@ module RSS rescue Exception => e puts "can't fetch #{url}" puts e.inspect + return end return items diff --git a/lib/session.rb b/lib/session.rb index 75e683662..7636a4026 100644 --- a/lib/session.rb +++ b/lib/session.rb @@ -40,6 +40,16 @@ module Session return session_list end + def self.touch( client_id ) + data = self.get(client_id) + path = @path + '/' + client_id.to_s + data[:meta][:last_ping] = Time.new.to_i.to_s + File.open( path + '/session', 'w' ) { |file| + file.puts Marshal.dump(data) + } + return true + end + def self.get( client_id ) session_file = @path + '/' + client_id.to_s + '/session' data = nil @@ -105,6 +115,7 @@ module Session @@user_threads[user.id] = Thread.new { UserState.new(user.id) @@user_threads[user.id] = nil + puts "close user(#{user.id}) thread" # raise "Exception from thread" } end @@ -119,13 +130,14 @@ module Session @@client_threads[client_id] = Thread.new { ClientState.new(client_id) @@client_threads[client_id] = nil + puts "close client(#{client_id}) thread" # raise "Exception from thread" } end } # system settings - sleep 0.4 + sleep 0.5 end end @@ -464,7 +476,7 @@ class ClientState self.log 'notify', "push overview_data #{overview.meta[:url]} for user #{user.id}" users = {} tickets = [] - overview_data[:tickets].each {|ticket_id| + overview_data[:ticket_list].each {|ticket_id| self.ticket( ticket_id, tickets, users ) } @@ -494,7 +506,7 @@ class ClientState self.send({ :data => { :overview => overview_data[:overview], - :ticket_list => overview_data[:tickets], + :ticket_list => overview_data[:ticket_list], :tickets_count => overview_data[:tickets_count], :collections => { :User => users,