web_socket.rb 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  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. # get current user
  59. user_session = Session.get( client_id )
  60. next if !user_session
  61. next if !user_session[:id]
  62. user = User.find( user_session[:id] )
  63. next if !user
  64. # start user thread
  65. start_user_thread = false
  66. if !@@user_threads[user.id]
  67. start_user_thread = true
  68. @@user_threads[user.id] = Thread.new {
  69. UserState.new(user.id)
  70. @@user_threads[user.id] = nil
  71. # raise "Exception from thread"
  72. }
  73. end
  74. # wait with client thread unil user thread has done some little work
  75. if start_user_thread
  76. sleep 0.4
  77. end
  78. # start client thread
  79. if !@@client_threads[client_id]
  80. @@client_threads[client_id] = Thread.new {
  81. ClientState.new(client_id)
  82. @@client_threads[client_id] = nil
  83. # raise "Exception from thread"
  84. }
  85. end
  86. }
  87. # system settings
  88. sleep 0.4
  89. end
  90. end
  91. def self.sessions
  92. path = @path + '/'
  93. data = []
  94. Dir.foreach( path ) do |entry|
  95. if entry != '.' && entry != '..'
  96. data.push entry
  97. end
  98. end
  99. return data
  100. end
  101. def self.queue( client_id )
  102. path = @path + '/' + client_id.to_s + '/'
  103. data = []
  104. Dir.foreach( path ) do |entry|
  105. if /^transaction/.match( entry )
  106. data.push Session.queue_file( path, entry )
  107. end
  108. end
  109. return data
  110. end
  111. def self.queue_file( path, filename )
  112. file_old = path + filename
  113. file_new = path + 'a-' + filename
  114. FileUtils.mv( file_old, file_new )
  115. data = nil
  116. all = ''
  117. File.open( file_new, 'r' ) { |file|
  118. while line = file.gets
  119. all = all + line
  120. end
  121. }
  122. File.delete( file_new )
  123. data = JSON.parse( all )
  124. return data
  125. end
  126. def self.destory( client_id )
  127. path = @path + '/' + client_id.to_s
  128. FileUtils.rm_rf path
  129. end
  130. end
  131. module CacheIn
  132. @@data = {}
  133. @@data_time = {}
  134. @@expires_in = {}
  135. @@expires_in_ttl = {}
  136. def self.set( key, value, params = {} )
  137. # puts 'CacheIn.set:' + key + '-' + value.inspect
  138. if params[:expires_in]
  139. @@expires_in[key] = Time.now + params[:expires_in]
  140. @@expires_in_ttl[key] = params[:expires_in]
  141. end
  142. @@data[ key ] = value
  143. @@data_time[ key ] = Time.now
  144. end
  145. def self.expired( key, params = {} )
  146. # expire if value never was set
  147. return true if !@@data.include? key
  148. # ignore_expire
  149. return false if params[:ignore_expire]
  150. # set re_expire
  151. if params[:re_expire]
  152. if @@expires_in[key]
  153. @@expires_in[key] = Time.now + @@expires_in_ttl[key]
  154. end
  155. return false
  156. end
  157. # check if expired
  158. if @@expires_in[key]
  159. return true if @@expires_in[key] < Time.now
  160. return false
  161. end
  162. # return false if key was set without expires_in
  163. return false
  164. end
  165. def self.get_time( key, params = {} )
  166. data = self.get( key, params )
  167. if data
  168. return @@data_time[key]
  169. end
  170. return nil
  171. end
  172. def self.get( key, params = {} )
  173. # puts 'CacheIn.get:' + key + '-' + @@data[ key ].inspect
  174. return if self.expired( key, params )
  175. @@data[ key ]
  176. end
  177. end
  178. class UserState
  179. def initialize( user_id )
  180. @user_id = user_id
  181. @data = {}
  182. @cache_key = 'user_' + user_id.to_s
  183. self.log "---user started user state"
  184. CacheIn.set( 'last_run_' + user_id.to_s , true, { :expires_in => 20.seconds } )
  185. self.fetch
  186. end
  187. def fetch
  188. user = User.find( @user_id )
  189. return if !user
  190. while true
  191. # check if user is still with min one open connection
  192. if !CacheIn.get( 'last_run_' + user.id.to_s )
  193. self.log "---user - closeing thread - no open user connection"
  194. return
  195. end
  196. self.log "---user - fetch user data"
  197. # overview
  198. cache_key = @cache_key + '_overview'
  199. if CacheIn.expired(cache_key)
  200. overview = Ticket.overview(
  201. :current_user_id => user.id,
  202. )
  203. overview_cache = CacheIn.get( cache_key, { :re_expire => true } )
  204. self.log 'fetch overview - ' + cache_key
  205. if overview != overview_cache
  206. self.log 'fetch overview changed - ' + cache_key
  207. # puts overview.inspect
  208. # puts '------'
  209. # puts overview_cache.inspect
  210. CacheIn.set( cache_key, overview, { :expires_in => 3.seconds } )
  211. end
  212. end
  213. # overview lists
  214. overviews = Ticket.overview_list(
  215. :current_user_id => user.id,
  216. )
  217. overviews.each { |overview|
  218. cache_key = @cache_key + '_overview_data_' + overview.meta[:url]
  219. if CacheIn.expired(cache_key)
  220. overview_data = Ticket.overview(
  221. :view => overview.meta[:url],
  222. # :view_mode => params[:view_mode],
  223. :current_user_id => user.id,
  224. :array => true,
  225. )
  226. overview_data_cache = CacheIn.get( cache_key, { :re_expire => true } )
  227. self.log 'fetch overview_data - ' + cache_key
  228. if overview_data != overview_data_cache
  229. self.log 'fetch overview_data changed - ' + cache_key
  230. CacheIn.set( cache_key, overview_data, { :expires_in => 5.seconds } )
  231. end
  232. end
  233. }
  234. # create_attributes
  235. cache_key = @cache_key + '_ticket_create_attributes'
  236. if CacheIn.expired(cache_key)
  237. ticket_create_attributes = Ticket.create_attributes(
  238. :current_user_id => user.id,
  239. )
  240. ticket_create_attributes_cache = CacheIn.get( cache_key, { :re_expire => true } )
  241. self.log 'fetch ticket_create_attributes - ' + cache_key
  242. if ticket_create_attributes != ticket_create_attributes_cache
  243. self.log 'fetch ticket_create_attributes changed - ' + cache_key
  244. CacheIn.set( cache_key, ticket_create_attributes, { :expires_in => 2.minutes } )
  245. end
  246. end
  247. # recent viewed
  248. cache_key = @cache_key + '_recent_viewed'
  249. if CacheIn.expired(cache_key)
  250. recent_viewed = History.recent_viewed(user)
  251. recent_viewed_cache = CacheIn.get( cache_key, { :re_expire => true } )
  252. self.log 'fetch recent_viewed - ' + cache_key
  253. if recent_viewed != recent_viewed_cache
  254. self.log 'fetch recent_viewed changed - ' + cache_key
  255. recent_viewed_full = History.recent_viewed_fulldata(user)
  256. CacheIn.set( cache_key, recent_viewed, { :expires_in => 5.seconds } )
  257. CacheIn.set( cache_key + '_push', recent_viewed_full )
  258. end
  259. end
  260. # activity steam
  261. cache_key = @cache_key + '_activity_stream'
  262. if CacheIn.expired(cache_key)
  263. activity_stream = History.activity_stream( user )
  264. activity_stream_cache = CacheIn.get( cache_key, { :re_expire => true } )
  265. self.log 'fetch activity_stream - ' + cache_key
  266. if activity_stream != activity_stream_cache
  267. self.log 'fetch activity_stream changed - ' + cache_key
  268. activity_stream_full = History.activity_stream_fulldata( user )
  269. CacheIn.set( cache_key, activity_stream, { :expires_in => 0.75.minutes } )
  270. CacheIn.set( cache_key + '_push', activity_stream_full )
  271. end
  272. end
  273. # rss
  274. cache_key = @cache_key + '_rss'
  275. if CacheIn.expired(cache_key)
  276. url = 'http://www.heise.de/newsticker/heise-atom.xml'
  277. rss_items = RSS.fetch( url, 8 )
  278. rss_items_cache = CacheIn.get( cache_key, { :re_expire => true } )
  279. self.log 'fetch rss - ' + cache_key
  280. if rss_items != rss_items_cache
  281. self.log 'fetch rss changed - ' + cache_key
  282. CacheIn.set( cache_key, rss_items, { :expires_in => 2.minutes } )
  283. CacheIn.set( cache_key + '_push', {
  284. head: 'Heise ATOM',
  285. items: rss_items,
  286. })
  287. end
  288. end
  289. self.log "---/user-"
  290. sleep 1
  291. end
  292. end
  293. def log( data )
  294. puts "#{Time.now}:user_id(#{ @user_id }) #{ data }"
  295. end
  296. end
  297. class ClientState
  298. def initialize( client_id )
  299. @client_id = client_id
  300. @data = {}
  301. @pushed = {}
  302. self.log "---client start ws connection---"
  303. self.fetch
  304. self.log "---client exiting ws connection---"
  305. end
  306. def fetch
  307. loop_count = 0
  308. while true
  309. # get connection user
  310. user_session = Session.get( @client_id )
  311. return if !user_session
  312. return if !user_session[:id]
  313. user = User.find( user_session[:id] )
  314. return if !user
  315. loop_count += 1
  316. self.log "---client - looking for data of user #{user.id}"
  317. # remember last run
  318. CacheIn.set( 'last_run_' + user.id.to_s , true, { :expires_in => 20.seconds } )
  319. # overview
  320. cache_key = 'user_' + user.id.to_s + '_overview'
  321. overview_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  322. if overview_time && @data[:overview_time] != overview_time
  323. @data[:overview_time] = overview_time
  324. overview = CacheIn.get( cache_key, { :ignore_expire => true } )
  325. self.log "push overview for user #{user.id}"
  326. # send update to browser
  327. self.transaction({
  328. :event => 'navupdate_ticket_overview',
  329. :data => overview,
  330. })
  331. end
  332. # overview_data
  333. overviews = Ticket.overview_list(
  334. :current_user_id => user.id,
  335. )
  336. overviews.each { |overview|
  337. cache_key = 'user_' + user.id.to_s + '_overview_data_' + overview.meta[:url]
  338. overview_data_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  339. if overview_data_time && @data[cache_key] != overview_data_time
  340. @data[cache_key] = overview_data_time
  341. overview_data = CacheIn.get( cache_key, { :ignore_expire => true } )
  342. self.log "push overview_data for user #{user.id}"
  343. users = {}
  344. tickets = []
  345. overview_data[:tickets].each {|ticket_id|
  346. self.ticket( ticket_id, tickets, users )
  347. }
  348. # send update to browser
  349. self.transaction({
  350. :data => {
  351. :overview => overview_data[:overview],
  352. :ticket_list => overview_data[:tickets],
  353. :tickets_count => overview_data[:tickets_count],
  354. :collections => {
  355. :User => users,
  356. :Ticket => tickets,
  357. }
  358. },
  359. :event => [ 'loadCollection', 'ticket_overview_rebuild' ],
  360. :collection => 'ticket_overview_' + overview.meta[:url].to_s,
  361. })
  362. end
  363. }
  364. # ticket_create_attributes
  365. cache_key = 'user_' + user.id.to_s + '_ticket_create_attributes'
  366. ticket_create_attributes_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  367. if ticket_create_attributes_time && @data[:ticket_create_attributes_time] != ticket_create_attributes_time
  368. @data[:ticket_create_attributes_time] = ticket_create_attributes_time
  369. ticket_create_attributes = CacheIn.get( cache_key, { :ignore_expire => true } )
  370. self.log "push ticket_create_attributes for user #{user.id}"
  371. # send update to browser
  372. self.transaction({
  373. :collection => 'ticket_create_attributes',
  374. :data => ticket_create_attributes,
  375. })
  376. end
  377. # recent viewed
  378. cache_key = 'user_' + user.id.to_s + '_recent_viewed'
  379. recent_viewed_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  380. if recent_viewed_time && @data[:recent_viewed_time] != recent_viewed_time
  381. @data[:recent_viewed_time] = recent_viewed_time
  382. recent_viewed = CacheIn.get( cache_key, { :ignore_expire => true } )
  383. self.log "push recent_viewed for user #{user.id}"
  384. # send update to browser
  385. r = CacheIn.get( cache_key + '_push', { :ignore_expire => true } )
  386. self.transaction({
  387. :event => 'update_recent_viewed',
  388. :data => r,
  389. })
  390. end
  391. # activity stream
  392. cache_key = 'user_' + user.id.to_s + '_activity_stream'
  393. activity_stream_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  394. if activity_stream_time && @data[:activity_stream_time] != activity_stream_time
  395. @data[:activity_stream_time] = activity_stream_time
  396. activity_stream = CacheIn.get( cache_key, { :ignore_expire => true } )
  397. self.log "push activity_stream for user #{user.id}"
  398. # send update to browser
  399. r = CacheIn.get( cache_key + '_push', { :ignore_expire => true } )
  400. self.transaction({
  401. :event => 'activity_stream_rebuild',
  402. :collection => 'activity_stream',
  403. :data => r,
  404. })
  405. end
  406. # rss
  407. cache_key = 'user_' + user.id.to_s + '_rss'
  408. rss_items_time = CacheIn.get_time( cache_key, { :ignore_expire => true } )
  409. if rss_items_time && @data[:rss_time] != rss_items_time
  410. @data[:rss_time] = rss_items_time
  411. rss_items = CacheIn.get( cache_key, { :ignore_expire => true } )
  412. self.log "push rss for user #{user.id}"
  413. # send update to browser
  414. r = CacheIn.get( cache_key + '_push', { :ignore_expire => true } )
  415. self.transaction({
  416. :event => 'rss_rebuild',
  417. :collection => 'dashboard_rss',
  418. :data => r,
  419. })
  420. end
  421. self.log "---/client-"
  422. # start faster in the beginnig
  423. if loop_count < 20
  424. sleep 0.4
  425. else
  426. sleep 1
  427. end
  428. end
  429. end
  430. # add ticket if needed
  431. def ticket( ticket_id, tickets, users )
  432. if !@pushed[:tickets]
  433. @pushed[:tickets] = {}
  434. end
  435. ticket = Ticket.full_data(ticket_id)
  436. if @pushed[:tickets][ticket_id] != ticket
  437. @pushed[:tickets][ticket_id] = ticket
  438. tickets.push ticket
  439. end
  440. # add users if needed
  441. self.user( ticket['owner_id'], users )
  442. self.user( ticket['customer_id'], users )
  443. self.user( ticket['created_by_id'], users )
  444. end
  445. # add user if needed
  446. def user( user_id, users )
  447. if !@pushed[:users]
  448. @pushed[:users] = {}
  449. end
  450. # get user
  451. user = User.user_data_full( user_id )
  452. # user is already on client and not changed
  453. return if @pushed[:users][ user_id ] == user
  454. @pushed[:users][user_id] = user
  455. # user not on client or different
  456. self.log 'push user ... ' + user['login']
  457. users[ user_id ] = user
  458. end
  459. # send update to browser
  460. def transaction( data )
  461. Session.transaction( @client_id, data )
  462. end
  463. def log( data )
  464. puts "#{Time.now}:client(#{ @client_id }) #{ data }"
  465. end
  466. end