web_socket.rb 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. require 'json'
  2. module Session
  3. @path = '/tmp/websocket'
  4. @@user_threads = {}
  5. @@client_threads = {}
  6. def self.create( client_id, session )
  7. path = @path + '/' + client_id.to_s
  8. FileUtils.mkpath path
  9. File.open( path + '/session', 'w' ) { |file|
  10. user = { :id => session['id'] }
  11. file.puts Marshal.dump(user)
  12. }
  13. # send update to browser
  14. if session['id']
  15. self.transaction( client_id, {
  16. :event => 'ws:login',
  17. :data => { :success => true },
  18. })
  19. end
  20. end
  21. def self.get( client_id )
  22. session_file = @path + '/' + client_id.to_s + '/session'
  23. data = nil
  24. return if !File.exist? session_file
  25. File.open( session_file, 'r' ) { |file|
  26. all = ''
  27. while line = file.gets
  28. all = all + line
  29. end
  30. begin
  31. data = Marshal.load( all )
  32. rescue
  33. return
  34. end
  35. }
  36. return data
  37. end
  38. def self.transaction( client_id, data )
  39. path = @path + '/' + client_id.to_s + '/'
  40. filename = 'transaction-' + Time.new().to_i.to_s + '-' + rand(999999).to_s
  41. if File::exists?( path + filename )
  42. filename = filename + '-1'
  43. if File::exists?( path + filename )
  44. filename = filename + '-1'
  45. if File::exists?( path + filename )
  46. filename = filename + '-1'
  47. if File::exists?( path + filename )
  48. filename = filename + '-' + rand(999999).to_s
  49. end
  50. end
  51. end
  52. end
  53. return false if !File.directory? path
  54. File.open( path + 'a-' + filename, 'w' ) { |file|
  55. file.puts data.to_json
  56. }
  57. FileUtils.mv( path + 'a-' + filename, path + filename)
  58. return true
  59. end
  60. def self.jobs
  61. Thread.abort_on_exception = true
  62. while true
  63. client_ids = self.sessions
  64. client_ids.each { |client_id|
  65. # connection already open
  66. next if @@client_threads[client_id]
  67. # get current user
  68. user_session = Session.get( client_id )
  69. next if !user_session
  70. next if !user_session[:id]
  71. user = User.find( user_session[:id] )
  72. next if !user
  73. # start user thread
  74. start_user_thread = false
  75. if !@@user_threads[user.id]
  76. start_user_thread = true
  77. @@user_threads[user.id] = Thread.new {
  78. UserState.new(user.id)
  79. @@user_threads[user.id] = nil
  80. # raise "Exception from thread"
  81. }
  82. end
  83. # wait with client thread unil user thread has done some little work
  84. if start_user_thread
  85. sleep 0.5
  86. end
  87. # start client thread
  88. if !@@client_threads[client_id]
  89. @@client_threads[client_id] = Thread.new {
  90. ClientState.new(client_id)
  91. @@client_threads[client_id] = nil
  92. # raise "Exception from thread"
  93. }
  94. end
  95. }
  96. # system settings
  97. sleep 0.4
  98. end
  99. end
  100. def self.sessions
  101. path = @path + '/'
  102. data = []
  103. Dir.foreach( path ) do |entry|
  104. if entry != '.' && entry != '..'
  105. data.push entry
  106. end
  107. end
  108. return data
  109. end
  110. def self.queue( client_id )
  111. path = @path + '/' + client_id.to_s + '/'
  112. data = []
  113. Dir.foreach( path ) do |entry|
  114. if /^transaction/.match( entry )
  115. data.push Session.queue_file( path, entry )
  116. end
  117. end
  118. return data
  119. end
  120. def self.queue_file( path, filename )
  121. file_old = path + filename
  122. file_new = path + 'a-' + filename
  123. FileUtils.mv( file_old, file_new )
  124. data = nil
  125. all = ''
  126. File.open( file_new, 'r' ) { |file|
  127. while line = file.gets
  128. all = all + line
  129. end
  130. }
  131. File.delete( file_new )
  132. data = JSON.parse( all )
  133. return data
  134. end
  135. def self.destory( client_id )
  136. path = @path + '/' + client_id.to_s
  137. FileUtils.rm_rf path
  138. end
  139. end
  140. module CacheIn
  141. @@data = {}
  142. @@data_time = {}
  143. @@expires_in = {}
  144. @@expires_in_ttl = {}
  145. def self.set( key, value, params = {} )
  146. # puts 'CacheIn.set:' + key + '-' + value.inspect
  147. if params[:expires_in]
  148. @@expires_in[key] = Time.now + params[:expires_in]
  149. @@expires_in_ttl[key] = params[:expires_in]
  150. end
  151. @@data[ key ] = value
  152. @@data_time[ key ] = Time.now
  153. end
  154. def self.expired( key, params = {} )
  155. # expire if value never was set
  156. return true if !@@data.include? key
  157. # ignore_expire
  158. return false if params[:ignore_expire]
  159. # set re_expire
  160. if params[:re_expire]
  161. if @@expires_in[key]
  162. @@expires_in[key] = Time.now + @@expires_in_ttl[key]
  163. end
  164. return false
  165. end
  166. # check if expired
  167. if @@expires_in[key]
  168. return true if @@expires_in[key] < Time.now
  169. return false
  170. end
  171. # return false if key was set without expires_in
  172. return false
  173. end
  174. def self.get_time( key, params = {} )
  175. data = self.get( key, params )
  176. if data
  177. return @@data_time[key]
  178. end
  179. return nil
  180. end
  181. def self.get( key, params = {} )
  182. # puts 'CacheIn.get:' + key + '-' + @@data[ key ].inspect
  183. return if self.expired( key, params )
  184. @@data[ key ]
  185. end
  186. end
  187. class UserState
  188. def initialize( user_id )
  189. @user_id = user_id
  190. @data = {}
  191. @cache_key = 'user_' + user_id.to_s
  192. self.log 'notify', "---user started user state"
  193. CacheIn.set( 'last_run_' + user_id.to_s , true, { :expires_in => 20.seconds } )
  194. self.fetch
  195. end
  196. def fetch
  197. user = User.find( @user_id )
  198. return if !user
  199. while true
  200. # check if user is still with min one open connection
  201. if !CacheIn.get( 'last_run_' + user.id.to_s )
  202. self.log 'notify', "---user - closeing thread - no open user connection"
  203. return
  204. end
  205. self.log 'notice', "---user - fetch user data"
  206. # overview
  207. cache_key = @cache_key + '_overview'
  208. if CacheIn.expired(cache_key)
  209. overview = Ticket.overview(
  210. :current_user => user,
  211. )
  212. overview_cache = CacheIn.get( cache_key, { :re_expire => true } )
  213. self.log 'notice', 'fetch overview - ' + cache_key
  214. if overview != overview_cache
  215. self.log 'notify', 'fetch overview changed - ' + cache_key
  216. # puts overview.inspect
  217. # puts '------'
  218. # puts overview_cache.inspect
  219. CacheIn.set( cache_key, overview, { :expires_in => 3.seconds } )
  220. end
  221. end
  222. # overview lists
  223. overviews = Ticket.overview_list(
  224. :current_user => user,
  225. )
  226. overviews.each { |overview|
  227. cache_key = @cache_key + '_overview_data_' + overview.meta[:url]
  228. if CacheIn.expired(cache_key)
  229. overview_data = Ticket.overview(
  230. :view => overview.meta[:url],
  231. # :view_mode => params[:view_mode],
  232. :current_user => user,
  233. :array => true,
  234. )
  235. overview_data_cache = CacheIn.get( cache_key, { :re_expire => true } )
  236. self.log 'notice', 'fetch overview_data - ' + cache_key
  237. if overview_data != overview_data_cache
  238. self.log 'notify', 'fetch overview_data changed - ' + cache_key
  239. CacheIn.set( cache_key, overview_data, { :expires_in => 5.seconds } )
  240. end
  241. end
  242. }
  243. # create_attributes
  244. cache_key = @cache_key + '_ticket_create_attributes'
  245. if CacheIn.expired(cache_key)
  246. ticket_create_attributes = Ticket.create_attributes(
  247. :current_user_id => user.id,
  248. )
  249. ticket_create_attributes_cache = CacheIn.get( cache_key, { :re_expire => true } )
  250. self.log 'notice', 'fetch ticket_create_attributes - ' + cache_key
  251. if ticket_create_attributes != ticket_create_attributes_cache
  252. self.log 'notify', 'fetch ticket_create_attributes changed - ' + cache_key
  253. CacheIn.set( cache_key, ticket_create_attributes, { :expires_in => 2.minutes } )
  254. end
  255. end
  256. # recent viewed
  257. cache_key = @cache_key + '_recent_viewed'
  258. if CacheIn.expired(cache_key)
  259. recent_viewed = History.recent_viewed(user)
  260. recent_viewed_cache = CacheIn.get( cache_key, { :re_expire => true } )
  261. self.log 'notice', 'fetch recent_viewed - ' + cache_key
  262. if recent_viewed != recent_viewed_cache
  263. self.log 'notify', 'fetch recent_viewed changed - ' + cache_key
  264. recent_viewed_full = History.recent_viewed_fulldata(user)
  265. CacheIn.set( cache_key, recent_viewed, { :expires_in => 5.seconds } )
  266. CacheIn.set( cache_key + '_push', recent_viewed_full )
  267. end
  268. end
  269. # activity steam
  270. cache_key = @cache_key + '_activity_stream'
  271. if CacheIn.expired(cache_key)
  272. activity_stream = History.activity_stream( user )
  273. activity_stream_cache = CacheIn.get( cache_key, { :re_expire => true } )
  274. self.log 'notice', 'fetch activity_stream - ' + cache_key
  275. if activity_stream != activity_stream_cache
  276. self.log 'notify', 'fetch activity_stream changed - ' + cache_key
  277. activity_stream_full = History.activity_stream_fulldata( user )
  278. CacheIn.set( cache_key, activity_stream, { :expires_in => 0.75.minutes } )
  279. CacheIn.set( cache_key + '_push', activity_stream_full )
  280. end
  281. end
  282. # rss
  283. cache_key = @cache_key + '_rss'
  284. if CacheIn.expired(cache_key)
  285. url = 'http://www.heise.de/newsticker/heise-atom.xml'
  286. rss_items = RSS.fetch( url, 8 )
  287. rss_items_cache = CacheIn.get( cache_key, { :re_expire => true } )
  288. self.log 'notice', 'fetch rss - ' + cache_key
  289. if rss_items != rss_items_cache
  290. self.log 'notify', 'fetch rss changed - ' + cache_key
  291. CacheIn.set( cache_key, rss_items, { :expires_in => 2.minutes } )
  292. CacheIn.set( cache_key + '_push', {
  293. head: 'Heise ATOM',
  294. items: rss_items,
  295. })
  296. end
  297. end
  298. # auto population of default collections
  299. self.log 'notice', "---user - fetch push_collection data"
  300. # get available collections
  301. cache_key = @cache_key + '_push_collections'
  302. collections = CacheIn.get( cache_key )
  303. if !collections
  304. collections = {}
  305. push_collection = SessionHelper::push_collections(user)
  306. push_collection.each { | key, value |
  307. collections[ key ] = true
  308. }
  309. CacheIn.set( cache_key, collections, { :expires_in => 2.minutes } )
  310. end
  311. # check all collections to push
  312. push_collection = {}
  313. collections.each { | key, v |
  314. cache_key = @cache_key + '_push_collections_' + key
  315. if CacheIn.expired(cache_key)
  316. if push_collection.empty?
  317. push_collection = SessionHelper::push_collections(user)
  318. end
  319. push_collection_cache = CacheIn.get( cache_key, { :re_expire => true } )
  320. self.log 'notice', "---user - fetch push_collection data " + cache_key
  321. if !push_collection[key] || !push_collection_cache || push_collection[key] != push_collection_cache || !push_collection[ key ].zip( push_collection_cache ).all? { |x, y| x.attributes == y.attributes }
  322. self.log 'notify', 'fetch push_collection changed - ' + cache_key
  323. CacheIn.set( cache_key, push_collection[key], { :expires_in => 1.minutes } )
  324. end
  325. end
  326. }
  327. self.log 'notice', "---/user-"
  328. sleep 1
  329. end
  330. end
  331. def log( level, data )
  332. return if level == 'notice'
  333. puts "#{Time.now}:user_id(#{ @user_id }) #{ data }"
  334. end
  335. end
  336. class ClientState
  337. def initialize( client_id )
  338. @client_id = client_id
  339. @cache_key = ''
  340. @data = {}
  341. @pushed = {}
  342. self.log 'notify', "---client start ws connection---"
  343. self.fetch
  344. self.log 'notify', "---client exiting ws connection---"
  345. end
  346. def fetch
  347. loop_count = 0
  348. while true
  349. # get connection user
  350. user_session = Session.get( @client_id )
  351. return if !user_session
  352. return if !user_session[:id]
  353. user = User.find( user_session[:id] )
  354. return if !user
  355. # set cache key
  356. @cache_key = 'user_' + user.id.to_s
  357. loop_count += 1
  358. self.log 'notice', "---client - looking for data of user #{user.id}"
  359. # remember last run
  360. CacheIn.set( 'last_run_' + user.id.to_s , true, { :expires_in => 20.seconds } )
  361. # overview
  362. cache_key = @cache_key + '_overview'
  363. overview_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  364. if overview_time && @data[:overview_time] != overview_time
  365. @data[:overview_time] = overview_time
  366. overview = CacheIn.get( cache_key, { :ignore_expire => true } )
  367. self.log 'notify', "push overview for user #{user.id}"
  368. # send update to browser
  369. self.transaction({
  370. :event => 'navupdate_ticket_overview',
  371. :data => overview,
  372. })
  373. end
  374. # overview_data
  375. overviews = Ticket.overview_list(
  376. :current_user => user,
  377. )
  378. overviews.each { |overview|
  379. cache_key = @cache_key + '_overview_data_' + overview.meta[:url]
  380. overview_data_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  381. if overview_data_time && @data[cache_key] != overview_data_time
  382. @data[cache_key] = overview_data_time
  383. overview_data = CacheIn.get( cache_key, { :ignore_expire => true } )
  384. self.log 'notify', "push overview_data #{overview.meta[:url]} for user #{user.id}"
  385. users = {}
  386. tickets = []
  387. overview_data[:tickets].each {|ticket_id|
  388. self.ticket( ticket_id, tickets, users )
  389. }
  390. # get groups
  391. group_ids = []
  392. Group.where( :active => true ).each { |group|
  393. group_ids.push group.id
  394. }
  395. agents = {}
  396. Ticket.agents.each { |user|
  397. agents[ user.id ] = 1
  398. }
  399. groups_users = {}
  400. groups_users[''] = []
  401. group_ids.each {|group_id|
  402. groups_users[ group_id ] = []
  403. Group.find(group_id).users.each {|user|
  404. next if !agents[ user.id ]
  405. groups_users[ group_id ].push user.id
  406. if !users[user.id]
  407. users[user.id] = User.user_data_full(user.id)
  408. end
  409. }
  410. }
  411. # send update to browser
  412. self.transaction({
  413. :data => {
  414. :overview => overview_data[:overview],
  415. :ticket_list => overview_data[:tickets],
  416. :tickets_count => overview_data[:tickets_count],
  417. :collections => {
  418. :User => users,
  419. :Ticket => tickets,
  420. },
  421. :bulk => {
  422. :group_id__owner_id => groups_users,
  423. :owner_id => [],
  424. },
  425. },
  426. :event => [ 'loadCollection', 'ticket_overview_rebuild' ],
  427. :collection => 'ticket_overview_' + overview.meta[:url].to_s,
  428. })
  429. end
  430. }
  431. # ticket_create_attributes
  432. cache_key = @cache_key + '_ticket_create_attributes'
  433. ticket_create_attributes_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  434. if ticket_create_attributes_time && @data[:ticket_create_attributes_time] != ticket_create_attributes_time
  435. @data[:ticket_create_attributes_time] = ticket_create_attributes_time
  436. create_attributes = CacheIn.get( cache_key, { :ignore_expire => true } )
  437. users = {}
  438. create_attributes[:owner_id].each {|user_id|
  439. if !users[user_id]
  440. users[user_id] = User.user_data_full(user_id)
  441. end
  442. }
  443. data = {
  444. :users => users,
  445. :edit_form => create_attributes,
  446. }
  447. self.log 'notify', "push ticket_create_attributes for user #{user.id}"
  448. # send update to browser
  449. self.transaction({
  450. :collection => 'ticket_create_attributes',
  451. :data => data,
  452. })
  453. end
  454. # recent viewed
  455. cache_key = @cache_key + '_recent_viewed'
  456. recent_viewed_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  457. if recent_viewed_time && @data[:recent_viewed_time] != recent_viewed_time
  458. @data[:recent_viewed_time] = recent_viewed_time
  459. recent_viewed = CacheIn.get( cache_key, { :ignore_expire => true } )
  460. self.log 'notify', "push recent_viewed for user #{user.id}"
  461. # send update to browser
  462. r = CacheIn.get( cache_key + '_push', { :ignore_expire => true } )
  463. self.transaction({
  464. :event => 'update_recent_viewed',
  465. :data => r,
  466. })
  467. end
  468. # activity stream
  469. cache_key = @cache_key + '_activity_stream'
  470. activity_stream_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  471. if activity_stream_time && @data[:activity_stream_time] != activity_stream_time
  472. @data[:activity_stream_time] = activity_stream_time
  473. activity_stream = CacheIn.get( cache_key, { :ignore_expire => true } )
  474. self.log 'notify', "push activity_stream for user #{user.id}"
  475. # send update to browser
  476. r = CacheIn.get( cache_key + '_push', { :ignore_expire => true } )
  477. self.transaction({
  478. :event => 'activity_stream_rebuild',
  479. :collection => 'activity_stream',
  480. :data => r,
  481. })
  482. end
  483. # rss
  484. cache_key = @cache_key + '_rss'
  485. rss_items_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  486. if rss_items_time && @data[:rss_time] != rss_items_time
  487. @data[:rss_time] = rss_items_time
  488. rss_items = CacheIn.get( cache_key, { :ignore_expire => true } )
  489. self.log 'notify', "push rss for user #{user.id}"
  490. # send update to browser
  491. r = CacheIn.get( cache_key + '_push', { :ignore_expire => true } )
  492. self.transaction({
  493. :event => 'rss_rebuild',
  494. :collection => 'dashboard_rss',
  495. :data => r,
  496. })
  497. end
  498. # push_collections
  499. cache_key = @cache_key + '_push_collections'
  500. collections = CacheIn.get( cache_key ) || {}
  501. collections.each { | key, v |
  502. collection_cache_key = @cache_key + '_push_collections_' + key
  503. collection_time = CacheIn.get_time( collection_cache_key, { :ignore_expire => true } )
  504. if collection_time && @data[ collection_cache_key + '_time' ] != collection_time
  505. @data[ collection_cache_key + '_time' ] = collection_time
  506. push_collections = CacheIn.get( collection_cache_key, { :ignore_expire => true } )
  507. self.log 'notify', "push push_collections #{key} for user #{user.id}"
  508. # send update to browser
  509. data = {}
  510. data['collections'] = {}
  511. data['collections'][key] = push_collections
  512. self.transaction({
  513. :event => 'resetCollection',
  514. :data => data,
  515. })
  516. end
  517. }
  518. self.log 'notice', "---/client-"
  519. # start faster in the beginnig
  520. if loop_count < 20
  521. sleep 0.6
  522. else
  523. sleep 1
  524. end
  525. end
  526. end
  527. # add ticket if needed
  528. def ticket( ticket_id, tickets, users )
  529. if !@pushed[:tickets]
  530. @pushed[:tickets] = {}
  531. end
  532. ticket = Ticket.full_data(ticket_id)
  533. if @pushed[:tickets][ticket_id] != ticket
  534. @pushed[:tickets][ticket_id] = ticket
  535. tickets.push ticket
  536. end
  537. # add users if needed
  538. self.user( ticket['owner_id'], users )
  539. self.user( ticket['customer_id'], users )
  540. self.user( ticket['created_by_id'], users )
  541. end
  542. # add user if needed
  543. def user( user_id, users )
  544. if !@pushed[:users]
  545. @pushed[:users] = {}
  546. end
  547. # get user
  548. user = User.user_data_full( user_id )
  549. # user is already on client and not changed
  550. return if @pushed[:users][ user_id ] == user
  551. @pushed[:users][user_id] = user
  552. # user not on client or different
  553. self.log 'notice', 'push user ... ' + user['login']
  554. users[ user_id ] = user
  555. end
  556. # send update to browser
  557. def transaction( data )
  558. Session.transaction( @client_id, data )
  559. end
  560. def log( level, data )
  561. return if level == 'notice'
  562. puts "#{Time.now}:client(#{ @client_id }) #{ data }"
  563. end
  564. end