Improved fetching of records (race conditions).

This commit is contained in:
Martin Edenhofer 2013-11-02 22:32:00 +01:00
parent 3fefd7918e
commit 3b405562cc
11 changed files with 130 additions and 25 deletions

View file

@ -42,7 +42,8 @@ class App.ControllerGenericNew extends App.ControllerModal
object.save( object.save(
success: -> success: ->
if ui.callback if ui.callback
ui.callback( @ ) item = App[ ui.genericObject ].retrieve(@id)
ui.callback( item )
ui.modalHide() ui.modalHide()
error: -> error: ->
@ -88,7 +89,8 @@ class App.ControllerGenericEdit extends App.ControllerModal
@item.save( @item.save(
success: -> success: ->
if ui.callback if ui.callback
ui.callback( @ ) item = App[ ui.genericObject ].retrieve(@id)
ui.callback( item )
ui.modalHide() ui.modalHide()
error: => error: =>

View file

@ -21,8 +21,6 @@ class App.WidgetUser extends App.ControllerDrox
App.User.unsubscribe(@subscribeId) App.User.unsubscribe(@subscribeId)
render: (user) => render: (user) =>
if !user
user = @u
# get display data # get display data
userData = [] userData = []

View file

@ -72,8 +72,7 @@ class App.Auth
return false; return false;
# set avatar # set avatar
if !data.session.image data.session.image = App.Config.get('api_path') + '/users/image/' + data.session.image
data.session.image = 'http://placehold.it/48x48'
# update config # update config
for key, value of data.config for key, value of data.config

View file

@ -131,6 +131,7 @@ class App.Model extends Spine.Model
if @RETRIEVE_CALLBACK[ record.id ] if @RETRIEVE_CALLBACK[ record.id ]
for key, callback of @RETRIEVE_CALLBACK[ record.id ] for key, callback of @RETRIEVE_CALLBACK[ record.id ]
data = App[ @className ].find( record.id ) data = App[ @className ].find( record.id )
data = @_fillUp( data )
callback( data ) callback( data )
delete @RETRIEVE_CALLBACK[ record.id ][ key ] delete @RETRIEVE_CALLBACK[ record.id ][ key ]
if _.isEmpty @RETRIEVE_CALLBACK[ record.id ] if _.isEmpty @RETRIEVE_CALLBACK[ record.id ]

View file

@ -24,9 +24,9 @@ class App.TicketArticle extends App.Model
# add created & updated # add created & updated
if data.created_by_id if data.created_by_id
data.created_by = App.User.find( data.created_by_id ) data.created_by = App.User.retrieve( data.created_by_id )
if data.updated_by_id if data.updated_by_id
data.updated_by = App.User.find( data.updated_by_id ) data.updated_by = App.User.retrieve( data.updated_by_id )
# add relations # add relations
data.article_type = App.TicketArticleType.find( data.ticket_article_type_id ) data.article_type = App.TicketArticleType.find( data.ticket_article_type_id )

View file

@ -44,8 +44,7 @@ class App.User extends App.Model
data['accounts'][account]['link'] = 'https://www.facebook.com/profile.php?id=' + data['accounts'][account]['uid'] data['accounts'][account]['link'] = 'https://www.facebook.com/profile.php?id=' + data['accounts'][account]['uid']
# set image url # set image url
if !data.image data.image = @apiPath + '/users/image/' + data.image
data.image = 'http://placehold.it/48x48'
data data

View file

@ -570,4 +570,51 @@ curl http://localhost/api/v1/users/account.json -v -u #{login}:#{password} -H "C
render :json => { :message => 'ok' }, :status => :ok render :json => { :message => 'ok' }, :status => :ok
end end
=begin
Resource:
GET /api/v1/users/image/8d6cca1c6bdc226cf2ba131e264ca2c7
Response:
<IMAGE>
Test:
curl http://localhost/api/v1/users/image/8d6cca1c6bdc226cf2ba131e264ca2c7 -v -u #{login}:#{password}
=end
def image
# cache image
response.headers['Expires'] = 1.year.from_now.httpdate
response.headers["Cache-Control"] = "cache, store, max-age=31536000, must-revalidate"
response.headers["Pragma"] = "cache"
# serve user image
user = User.where( :image => params[:hash] ).first
if user
# find file
list = Store.list( :object => 'User::Image', :o_id => user.id )
if list && list[0]
file = Store.find( list[0] )
send_data(
file.store_file.data,
:filename => file.filename,
:type => file.preferences['Content-Type'] || file.preferences['Mime-Type'],
:disposition => 'inline'
)
return
end
end
# serve defalt image
image = 'R0lGODdhMAAwAOMAAMzMzJaWlr6+vqqqqqOjo8XFxbe3t7GxsZycnAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAMAAwAAAEcxDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru98TwuAA+KQAQqJK8EAgBAgMEqmkzUgBIeSwWGZtR5XhSqAULACCoGCJGwlm1MGQrq9RqgB8fm4ZTUgDBIEcRR9fz6HiImKi4yNjo+QkZKTlJWWkBEAOw=='
send_data(
Base64.decode64(image),
:filename => 'image.gif',
:type => 'image/gif',
:disposition => 'inline',
)
end
end end

View file

@ -1,13 +1,15 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
require 'digest/md5'
class User < ApplicationModel class User < ApplicationModel
include User::Assets include User::Assets
extend User::Search extend User::Search
before_create :check_name, :check_email, :check_login, :check_image, :check_password before_create :check_name, :check_email, :check_login, :check_image, :check_password
before_update :check_password, :check_image, :check_email, :check_login_update before_update :check_password, :check_image, :check_email, :check_login_update
after_create :notify_clients_after_create after_create :check_image_load, :notify_clients_after_create
after_update :notify_clients_after_update after_update :check_image_load, :notify_clients_after_update
after_destroy :notify_clients_after_destroy after_destroy :notify_clients_after_destroy
has_and_belongs_to_many :groups, :after_add => :cache_update, :after_remove => :cache_update has_and_belongs_to_many :groups, :after_add => :cache_update, :after_remove => :cache_update
@ -22,12 +24,15 @@ class User < ApplicationModel
activity_stream_support( activity_stream_support(
:role => 'Admin', :role => 'Admin',
:ignore_attributes => { :ignore_attributes => {
:last_login => true, :last_login => true,
} :image => true,
:image_source => true, }
) )
history_support( history_support(
:ignore_attributes => { :ignore_attributes => {
:password => true, :password => true,
:image => true,
:image_source => true,
} }
) )
@ -530,15 +535,49 @@ returns
end end
def check_image def check_image
require 'digest/md5' if !self.image_source || self.image_source == '' || self.image_source =~ /gravatar.com/i
if !self.image || self.image == ''
if self.email if self.email
hash = Digest::MD5.hexdigest(self.email) hash = Digest::MD5.hexdigest(self.email)
self.image = "http://www.gravatar.com/avatar/#{hash}?s=48" self.image_source = "http://www.gravatar.com/avatar/#{hash}?s=48&d=404"
end end
end end
end end
def check_image_load
return if !self.image_source
return if self.image_source !~ /http/i
# download image
response = UserAgent.request( self.image_source )
if !response.success?
self.update_column( :image, 'none' )
puts "WARNING: Can't fetch '#{url}', http code: #{response.code.to_s}"
#raise "Can't fetch '#{url}', http code: #{response.code.to_s}"
return
end
# store image local
hash = Digest::MD5.hexdigest( response.body )
# check if image has changed
return if self.image != hash
# save new image
self.update_column( :image, hash )
Store.remove( :object => 'User::Image', :o_id => self.id )
Store.add(
:object => 'User::Image',
:o_id => self.id,
:data => response.body,
:filename => 'image',
:preferences => {
'Content-Type' => response.content_type
},
:created_by_id => self.updated_by_id,
)
end
def check_password def check_password
# set old password again if not given # set old password again if not given

View file

@ -13,5 +13,6 @@ Zammad::Application.routes.draw do
match api_path + '/users/history/:id', :to => 'users#history', :via => :get match api_path + '/users/history/:id', :to => 'users#history', :via => :get
match api_path + '/users', :to => 'users#create', :via => :post match api_path + '/users', :to => 'users#create', :via => :post
match api_path + '/users/:id', :to => 'users#update', :via => :put match api_path + '/users/:id', :to => 'users#update', :via => :put
match api_path + '/users/image/:hash', :to => 'users#image', :via => :get
end end

View file

@ -0,0 +1,14 @@
class ChangeUser2 < ActiveRecord::Migration
def up
add_column :users, :image_source, :string, :limit => 200, :null => true
add_index :users, [:image]
User.all.each {|user|
puts "Update user #{user.login}"
user.image_source = user.image
user.save
}
end
def down
end
end

View file

@ -116,9 +116,10 @@ returns
when Net::HTTPOK when Net::HTTPOK
return Result.new( return Result.new(
:body => response.body, :body => response.body,
:success => true, :content_type => response['Content-Type'],
:code => response.code, :success => true,
:code => response.code,
) )
end end
@ -171,10 +172,11 @@ returns
class Result class Result
def initialize(options) def initialize(options)
@success = options[:success] @success = options[:success]
@body = options[:body] @body = options[:body]
@code = options[:code] @code = options[:code]
@error = options[:error] @content_type = options[:content_type]
@error = options[:error]
end end
def error def error
@error @error
@ -188,5 +190,8 @@ returns
def code def code
@code @code
end end
def content_type
@content_type
end
end end
end end