web_socket.rb 20 KB

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