Don't show timeout message for empty diffs (ie. blank file added)
[gitorious.git] / lib / record_throttling.rb
blob617c91867c9a1188f7c7ea18ee6d2bb3a537816b
1 # encoding: utf-8
2 #--
3 #   Copyright (C) 2012-2013 Gitorious AS
4 #   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
6 #   This program is free software: you can redistribute it and/or modify
7 #   it under the terms of the GNU Affero General Public License as published by
8 #   the Free Software Foundation, either version 3 of the License, or
9 #   (at your option) any later version.
11 #   This program is distributed in the hope that it will be useful,
12 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #   GNU Affero General Public License for more details.
16 #   You should have received a copy of the GNU Affero General Public License
17 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #++
20 module RecordThrottling
21   class LimitReachedError < StandardError; end
23   def self.disable
24     @@enabled = false
25   end
27   def self.enable
28     @@enabled = true
29   end
31   def self.disabled?
32     !@@enabled || !RecordThrottling::default_behavior
33   end
35   def self.reset_to_default
36     @@enabled = RecordThrottling::default_behavior
37   end
39   def self.default_behavior
40     Gitorious::Configuration.get("enable_record_throttling", true)
41   end
43   @@enabled = RecordThrottling::default_behavior
45   def self.included(base)
46     base.class_eval do
47       include RecordThrottlingInstanceMethods
49       # Thottles record creation/update.
50       # Raises RecordThrottling::RecordThrottleLimitReachedError if limit is
51       # reached.
52       #
53       # Options:
54       # +:limit+ the amount of records allowed within (eg. 5)
55       # +:timeframe+ the timeframe the limit should be within (eg. 5.minutes)
56       # +:counter+ A proc returning the value to compare +:limit+ against
57       # +:conditions+ A proc of the counts the last created_at query should use
58       # Both the +:counter+ and +:conditions+ procs will receive the record
59       # as argument.
60       #
61       # Example usage:
62       # throttle_records :create, :limit => 5,
63       #   :counter => proc{|record|
64       #      record.user.projects.where("created_at > ?", 5.minutes.ago).count
65       #   },
66       #   :conditions => proc{|record| {:user_id => record.user.id} },
67       #   :timeframe => 5.minutes
68       def self.throttle_records(create_or_update, options)
69         options.assert_valid_keys(:limit, :counter, :conditions, :timeframe, :actor)
70         class_attribute :creation_throttle_options
71         self.creation_throttle_options = options
72         send("before_#{create_or_update}", :check_throttle_limits)
73       end
74     end
75   end
77   module RecordThrottlingInstanceMethods
78     def check_throttle_limits
79       options = self.class.creation_throttle_options
80       scope = options[:scope] || self.class
81       actor = options[:actor].call(self)
82       raise LimitReachedError if !RecordThrottling.allowed?(scope, actor, options)
83     end
84   end
86   def self.allowed?(scope, actor, options = {})
87     return true if RecordThrottling.disabled?
88     return true if options[:counter].call(actor) < options[:limit]
89     cond = options[:conditions].call(actor)
90     last_create = scope.maximum(:created_at, :conditions => cond)
91     return true if !last_create || last_create < options[:timeframe].ago
92     false
93   end
94 end