Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / sync.js @ 885a592b

History | View | Annotate | Download (14.6 kB)

1 8d08f18a Kostas Papadimitriou
;(function(root){
2 8d08f18a Kostas Papadimitriou
    
3 8d08f18a Kostas Papadimitriou
    // root
4 8d08f18a Kostas Papadimitriou
    var root = root;
5 8d08f18a Kostas Papadimitriou
    
6 8d08f18a Kostas Papadimitriou
    // setup namepsaces
7 8d08f18a Kostas Papadimitriou
    var snf = root.synnefo = root.synnefo || {};
8 8d08f18a Kostas Papadimitriou
    var sync = snf.sync = snf.sync || {};
9 8d08f18a Kostas Papadimitriou
    var api = snf.api = snf.api || {};
10 9ce969a7 Kostas Papadimitriou
    var storage = snf.storage = snf.storage || {};
11 8d08f18a Kostas Papadimitriou
12 8d08f18a Kostas Papadimitriou
    // shortcuts
13 8d08f18a Kostas Papadimitriou
    var bb = Backbone;
14 8d08f18a Kostas Papadimitriou
15 8d08f18a Kostas Papadimitriou
    // logging
16 8d08f18a Kostas Papadimitriou
    var logger = new snf.logging.logger("SNF-API");
17 8d08f18a Kostas Papadimitriou
    var debug = _.bind(logger.debug, logger)
18 8d08f18a Kostas Papadimitriou
    
19 8d08f18a Kostas Papadimitriou
    // method map
20 8d08f18a Kostas Papadimitriou
    var methodMap = {
21 8d08f18a Kostas Papadimitriou
        'create': 'POST',
22 8d08f18a Kostas Papadimitriou
        'update': 'PUT',
23 8d08f18a Kostas Papadimitriou
        'delete': 'DELETE',
24 8d08f18a Kostas Papadimitriou
        'read'  : 'GET'
25 8d08f18a Kostas Papadimitriou
    };
26 8d08f18a Kostas Papadimitriou
27 8d08f18a Kostas Papadimitriou
    // custom getUrl function
28 8d08f18a Kostas Papadimitriou
    // handles url retrieval based on the object passed
29 8d08f18a Kostas Papadimitriou
    // on most occasions in the synnefo api this will call
30 8d08f18a Kostas Papadimitriou
    // the model/collection url method
31 8d08f18a Kostas Papadimitriou
    var getUrl = function(object, options) {
32 8d08f18a Kostas Papadimitriou
        if (!(object && object.url)) return null;
33 8d08f18a Kostas Papadimitriou
        return _.isFunction(object.url) ? object.url(options) : object.url;
34 8d08f18a Kostas Papadimitriou
    };
35 8d08f18a Kostas Papadimitriou
    
36 8d08f18a Kostas Papadimitriou
    // Call history (set of api paths with the dates the path last called)
37 8d08f18a Kostas Papadimitriou
    var api_history = api.requests = api.requests || {};
38 f06ec46e Kostas Papadimitriou
    var addApiCallDate = function(url, d, method) {
39 8d08f18a Kostas Papadimitriou
        if (d === undefined) { d = Date() };
40 8d08f18a Kostas Papadimitriou
        var path = snf.util.parseUri(url).path;
41 9ce969a7 Kostas Papadimitriou
        var key = path + "_" + method;
42 9ce969a7 Kostas Papadimitriou
43 9ce969a7 Kostas Papadimitriou
        // TODO: check if d is very old date
44 9ce969a7 Kostas Papadimitriou
        api_history[key] = d;
45 8d08f18a Kostas Papadimitriou
        return api_history[path]
46 8d08f18a Kostas Papadimitriou
    }
47 9ce969a7 Kostas Papadimitriou
48 9ce969a7 Kostas Papadimitriou
    var clearApiCallDate = function(url, method) {
49 9ce969a7 Kostas Papadimitriou
        var path = snf.util.parseUri(url).path;
50 9ce969a7 Kostas Papadimitriou
        var key = path + "_" + method;
51 9ce969a7 Kostas Papadimitriou
        api_history[key] = false;
52 9ce969a7 Kostas Papadimitriou
        return api_history[path]
53 9ce969a7 Kostas Papadimitriou
    }
54 9ce969a7 Kostas Papadimitriou
55 8d08f18a Kostas Papadimitriou
    var api_errors = api.errors = api.errors || [];
56 8d08f18a Kostas Papadimitriou
    var add_api_error = function(settings, data) {
57 8d08f18a Kostas Papadimitriou
        api_errors.push({url:settings.url, date:new Date, settings:settings, data:data})
58 8d08f18a Kostas Papadimitriou
    }
59 8d08f18a Kostas Papadimitriou
60 f06ec46e Kostas Papadimitriou
    var setChangesSince = function(url, method) {
61 8d08f18a Kostas Papadimitriou
        var path = snf.util.parseUri(url).path;
62 f06ec46e Kostas Papadimitriou
        var d = api_history[path + "_" + method];
63 8d08f18a Kostas Papadimitriou
        if (d) {
64 8d08f18a Kostas Papadimitriou
            url = url + "?changes-since=" + snf.util.ISODateString(d)
65 8d08f18a Kostas Papadimitriou
        }
66 8d08f18a Kostas Papadimitriou
        return url;
67 8d08f18a Kostas Papadimitriou
    }
68 8d08f18a Kostas Papadimitriou
    
69 8d08f18a Kostas Papadimitriou
    // custom sync method
70 8d08f18a Kostas Papadimitriou
    // appends global ajax handlers
71 8d08f18a Kostas Papadimitriou
    // handles changed-since url parameter based on api path
72 8d08f18a Kostas Papadimitriou
    api.sync = function(method, model, options) {
73 8d08f18a Kostas Papadimitriou
74 8d08f18a Kostas Papadimitriou
        var type = methodMap[method];
75 8d08f18a Kostas Papadimitriou
        
76 8d08f18a Kostas Papadimitriou
        if (model && (model.skipMethods || []).indexOf(method) >= 0) {
77 8d08f18a Kostas Papadimitriou
            throw "Model does not support " + method + " calls";
78 8d08f18a Kostas Papadimitriou
        }
79 8d08f18a Kostas Papadimitriou
        
80 8d08f18a Kostas Papadimitriou
        if (!options.url) {
81 8d08f18a Kostas Papadimitriou
            options.url = getUrl(model, options) || urlError();
82 f06ec46e Kostas Papadimitriou
            options.url = options.refresh ? options.url : setChangesSince(options.url, type);
83 8d08f18a Kostas Papadimitriou
            if (!options.refresh && options.cache === undefined) {
84 8d08f18a Kostas Papadimitriou
                options.cache = true;
85 8d08f18a Kostas Papadimitriou
            }
86 8d08f18a Kostas Papadimitriou
        }
87 8d08f18a Kostas Papadimitriou
88 9ce969a7 Kostas Papadimitriou
        // default error options
89 9ce969a7 Kostas Papadimitriou
        options.critical = options.critical === undefined ? true : options.critical;
90 9ce969a7 Kostas Papadimitriou
        options.display = options.display === undefined ? true : options.display;
91 9ce969a7 Kostas Papadimitriou
92 9ce969a7 Kostas Papadimitriou
        if (api.stop_calls && !options.no_skip) {
93 8d08f18a Kostas Papadimitriou
            return;
94 8d08f18a Kostas Papadimitriou
        }
95 8d08f18a Kostas Papadimitriou
96 8d08f18a Kostas Papadimitriou
        var success = options.success || function(){};
97 8d08f18a Kostas Papadimitriou
        var error = options.error || function(){};
98 8d08f18a Kostas Papadimitriou
        var complete = options.complete || function(){};
99 8d08f18a Kostas Papadimitriou
        var before_send = options.beforeSend || function(){};
100 8d08f18a Kostas Papadimitriou
101 8d08f18a Kostas Papadimitriou
        // custom json data.
102 8d08f18a Kostas Papadimitriou
        if (options.data && model && (method == 'create' || method == 'update')) {
103 8d08f18a Kostas Papadimitriou
            options.contentType = 'application/json';
104 8d08f18a Kostas Papadimitriou
            options.data = JSON.stringify(options.data);
105 8d08f18a Kostas Papadimitriou
        }
106 8d08f18a Kostas Papadimitriou
        
107 8d08f18a Kostas Papadimitriou
        var api_params = {};
108 8d08f18a Kostas Papadimitriou
        var api_options = _.extend(api_params, options, {
109 8d08f18a Kostas Papadimitriou
            success: api.handlerWrapper(api.successHandler, success, "success"),
110 8d08f18a Kostas Papadimitriou
            error: api.handlerWrapper(api.errorHandler, error, "error"),
111 8d08f18a Kostas Papadimitriou
            complete: api.handlerWrapper(api.completeHandler, complete, "complete"),
112 8d08f18a Kostas Papadimitriou
            beforeSend: api.handlerWrapper(api.beforeSendHandler, before_send, "beforeSend"),
113 8d08f18a Kostas Papadimitriou
            cache: options.cache || false,
114 66be390b Kostas Papadimitriou
            timeout: options.timeout || snf.config.ajax_timeout || window.TIMEOUT || 5000
115 8d08f18a Kostas Papadimitriou
        });
116 8d08f18a Kostas Papadimitriou
        return bb.sync(method, model, api_options);
117 8d08f18a Kostas Papadimitriou
    }
118 8d08f18a Kostas Papadimitriou
    
119 9ce969a7 Kostas Papadimitriou
    api.timeouts_occured = 0;
120 9ce969a7 Kostas Papadimitriou
121 8d08f18a Kostas Papadimitriou
    api.handlerWrapper = function(wrap, method, type) {
122 9ce969a7 Kostas Papadimitriou
        
123 8d08f18a Kostas Papadimitriou
        var cb_type = type;
124 9ce969a7 Kostas Papadimitriou
125 8d08f18a Kostas Papadimitriou
        return function() {
126 8d08f18a Kostas Papadimitriou
            
127 9ce969a7 Kostas Papadimitriou
            var xhr = undefined;
128 9ce969a7 Kostas Papadimitriou
            var handler_type = type;
129 9ce969a7 Kostas Papadimitriou
            var args = arguments;
130 9ce969a7 Kostas Papadimitriou
            var ajax_options = this;
131 9ce969a7 Kostas Papadimitriou
132 9ce969a7 Kostas Papadimitriou
            // save the request date to use it as a changes-since value
133 9ce969a7 Kostas Papadimitriou
            // for opera because we are not able to determine
134 9ce969a7 Kostas Papadimitriou
            // response date header for 304 requests
135 9ce969a7 Kostas Papadimitriou
            if (handler_type == "beforeSend" && $.browser.opera) {
136 9ce969a7 Kostas Papadimitriou
                this.date_send = new Date;
137 9ce969a7 Kostas Papadimitriou
            }
138 9ce969a7 Kostas Papadimitriou
139 9ce969a7 Kostas Papadimitriou
            // error with status code 0 in opera
140 9ce969a7 Kostas Papadimitriou
            // act as 304 response
141 9ce969a7 Kostas Papadimitriou
            if (handler_type == "error" && $.browser.opera) {
142 9ce969a7 Kostas Papadimitriou
                if (arguments[0].status === 0 && arguments[1] === "error") {
143 9ce969a7 Kostas Papadimitriou
                    arguments[0].status = 304;
144 9ce969a7 Kostas Papadimitriou
                    arguments[1] = "notmodified";
145 9ce969a7 Kostas Papadimitriou
                    response_type = "success";
146 9ce969a7 Kostas Papadimitriou
                    xhr = arguments[0];
147 9ce969a7 Kostas Papadimitriou
                }
148 9ce969a7 Kostas Papadimitriou
            }
149 9ce969a7 Kostas Papadimitriou
            
150 9ce969a7 Kostas Papadimitriou
            // add error in api errors registry
151 9ce969a7 Kostas Papadimitriou
            // api errors registry will be sent
152 9ce969a7 Kostas Papadimitriou
            // if user reports an error using feedback form
153 9ce969a7 Kostas Papadimitriou
            if (handler_type == "error") {
154 ec511098 Kostas Papadimitriou
                // skip logging requested ?
155 ec511098 Kostas Papadimitriou
                // if not log this error
156 ec511098 Kostas Papadimitriou
                if (this.log_error !== false) {
157 ec511098 Kostas Papadimitriou
                    add_api_error(this, arguments);
158 ec511098 Kostas Papadimitriou
                }
159 8d08f18a Kostas Papadimitriou
            }
160 6a3a5bf7 Kostas Papadimitriou
            
161 9ce969a7 Kostas Papadimitriou
            // identify response status
162 6a3a5bf7 Kostas Papadimitriou
            var status = 304;
163 6a3a5bf7 Kostas Papadimitriou
            if (arguments[0]) {
164 9ce969a7 Kostas Papadimitriou
                status = arguments[0].status;
165 6a3a5bf7 Kostas Papadimitriou
            }
166 8d08f18a Kostas Papadimitriou
            
167 9ce969a7 Kostas Papadimitriou
            // identify aborted request
168 8d08f18a Kostas Papadimitriou
            try {
169 8d08f18a Kostas Papadimitriou
                if (args[1] === "abort") {
170 8d08f18a Kostas Papadimitriou
                    api.trigger("abort");
171 8d08f18a Kostas Papadimitriou
                    return;
172 8d08f18a Kostas Papadimitriou
                }
173 8d08f18a Kostas Papadimitriou
            } catch(error) {
174 8d08f18a Kostas Papadimitriou
                console.error("error aborting", error);
175 8d08f18a Kostas Papadimitriou
            }
176 9ce969a7 Kostas Papadimitriou
            
177 9ce969a7 Kostas Papadimitriou
            // try to set the last request date
178 9ce969a7 Kostas Papadimitriou
            // only for notmodified or succeed responses
179 8d08f18a Kostas Papadimitriou
            try {
180 8d08f18a Kostas Papadimitriou
                // identify xhr object
181 9ce969a7 Kostas Papadimitriou
                xhr = xhr || args[2];
182 9ce969a7 Kostas Papadimitriou
                
183 9ce969a7 Kostas Papadimitriou
                // not modified response
184 9ce969a7 Kostas Papadimitriou
                if (args[1] === "notmodified") {
185 9ce969a7 Kostas Papadimitriou
                    if (xhr) {
186 9ce969a7 Kostas Papadimitriou
                        // use date_send if exists (opera browser)
187 9ce969a7 Kostas Papadimitriou
                        var d = this.date_send || xhr.getResponseHeader('Date');
188 9ce969a7 Kostas Papadimitriou
                        if (d) { addApiCallDate(this.url, new Date(d), ajax_options.type); };
189 8d08f18a Kostas Papadimitriou
                    }
190 8d08f18a Kostas Papadimitriou
                    return;
191 8d08f18a Kostas Papadimitriou
                }
192 8d08f18a Kostas Papadimitriou
                
193 9ce969a7 Kostas Papadimitriou
                // success response
194 9ce969a7 Kostas Papadimitriou
                if (args[1] == "success" && handler_type == "success") {
195 9ce969a7 Kostas Papadimitriou
                    try {
196 9ce969a7 Kostas Papadimitriou
                        // use date_send if exists (opera browser)
197 9ce969a7 Kostas Papadimitriou
                        var d = this.date_send || args[2].getResponseHeader('Date');
198 9ce969a7 Kostas Papadimitriou
                        if (d) { addApiCallDate(this.url, new Date(d), ajax_options.type); };
199 9ce969a7 Kostas Papadimitriou
                    } catch (err) {
200 9ce969a7 Kostas Papadimitriou
                        console.error(err)
201 9ce969a7 Kostas Papadimitriou
                    }
202 9ce969a7 Kostas Papadimitriou
                }
203 8d08f18a Kostas Papadimitriou
            } catch (err) {
204 8d08f18a Kostas Papadimitriou
                console.error(err);
205 8d08f18a Kostas Papadimitriou
            }
206 9ce969a7 Kostas Papadimitriou
            
207 9ce969a7 Kostas Papadimitriou
            // dont call error callback for non modified responses
208 9ce969a7 Kostas Papadimitriou
            if (arguments[1] === "notmodified") {
209 9ce969a7 Kostas Papadimitriou
                return;
210 9ce969a7 Kostas Papadimitriou
            }
211 9ce969a7 Kostas Papadimitriou
            
212 9ce969a7 Kostas Papadimitriou
            // prepare arguments for error callbacks
213 9ce969a7 Kostas Papadimitriou
            var cb_args = _.toArray(arguments);
214 9ce969a7 Kostas Papadimitriou
            if (handler_type === "error") {
215 9ce969a7 Kostas Papadimitriou
                cb_args.push(_.clone(this));
216 9ce969a7 Kostas Papadimitriou
            }
217 9ce969a7 Kostas Papadimitriou
            
218 9ce969a7 Kostas Papadimitriou
            // determine if we need to call our callback wrapper
219 9ce969a7 Kostas Papadimitriou
            var call_api_handler = true;
220 9ce969a7 Kostas Papadimitriou
221 9ce969a7 Kostas Papadimitriou
            // request handles errors by itself, s
222 9ce969a7 Kostas Papadimitriou
            if (handler_type == "error" && this.skip_api_error) {
223 9ce969a7 Kostas Papadimitriou
                call_api_handler = false
224 9ce969a7 Kostas Papadimitriou
            }
225 9ce969a7 Kostas Papadimitriou
226 9ce969a7 Kostas Papadimitriou
            // aborted request, don't call error handler
227 9ce969a7 Kostas Papadimitriou
            if (handler_type === "error" && args[1] === "abort") {
228 9ce969a7 Kostas Papadimitriou
                call_api_handler = false;
229 9ce969a7 Kostas Papadimitriou
            }
230 9ce969a7 Kostas Papadimitriou
            
231 9ce969a7 Kostas Papadimitriou
            // reset api call date, next call will be sent without changes-since
232 9ce969a7 Kostas Papadimitriou
            // parameter set
233 9ce969a7 Kostas Papadimitriou
            if (handler_type === "error") {
234 9ce969a7 Kostas Papadimitriou
                if (args[1] === "error") {
235 9ce969a7 Kostas Papadimitriou
                    clearApiCallDate(this.url, this.type);
236 9ce969a7 Kostas Papadimitriou
                }
237 9ce969a7 Kostas Papadimitriou
            }
238 9ce969a7 Kostas Papadimitriou
            
239 9ce969a7 Kostas Papadimitriou
            // call api call back and retrieve params to
240 9ce969a7 Kostas Papadimitriou
            // be passed to the callback method set for
241 9ce969a7 Kostas Papadimitriou
            // this type of response
242 9ce969a7 Kostas Papadimitriou
            if (call_api_handler) {
243 9ce969a7 Kostas Papadimitriou
                cb_args = wrap.apply(this, cb_args);
244 9ce969a7 Kostas Papadimitriou
            }
245 9ce969a7 Kostas Papadimitriou
            
246 9ce969a7 Kostas Papadimitriou
            // call requested callback
247 9ce969a7 Kostas Papadimitriou
            method.apply(this, _.toArray(cb_args));
248 8d08f18a Kostas Papadimitriou
        }
249 8d08f18a Kostas Papadimitriou
    }
250 8d08f18a Kostas Papadimitriou
251 8d08f18a Kostas Papadimitriou
    api.successHandler = function(data, status, xhr) {
252 8d08f18a Kostas Papadimitriou
        //debug("ajax success", arguments)
253 8d08f18a Kostas Papadimitriou
        // on success, update the last date we called the api url
254 8d08f18a Kostas Papadimitriou
        return [data, status, xhr];
255 8d08f18a Kostas Papadimitriou
    }
256 8d08f18a Kostas Papadimitriou
257 8d08f18a Kostas Papadimitriou
    api.errorHandler = function(event, xhr, settings, error) {
258 f06ec46e Kostas Papadimitriou
        
259 9ce969a7 Kostas Papadimitriou
        // dont trigger api error untill timeouts occured
260 9ce969a7 Kostas Papadimitriou
        // exceed the skips_timeouts limit
261 9ce969a7 Kostas Papadimitriou
        //
262 9ce969a7 Kostas Papadimitriou
        // check only requests with skips_timeouts option set
263 9ce969a7 Kostas Papadimitriou
        if (xhr === "timeout" && _.last(arguments).skips_timeouts) {
264 9ce969a7 Kostas Papadimitriou
            var skip_timeouts = snf.config.skip_timeouts || 1;
265 9ce969a7 Kostas Papadimitriou
            if (snf.api.timeouts_occured < skip_timeouts) {
266 9ce969a7 Kostas Papadimitriou
                snf.api.timeouts_occured++;
267 9ce969a7 Kostas Papadimitriou
                return;
268 9ce969a7 Kostas Papadimitriou
            } else {
269 9ce969a7 Kostas Papadimitriou
                // reset and continue to error trigger
270 9ce969a7 Kostas Papadimitriou
                snf.api.timeouts_occured = 0;
271 9ce969a7 Kostas Papadimitriou
            }
272 8d08f18a Kostas Papadimitriou
        }
273 09f90e6e Kostas Papadimitriou
274 09f90e6e Kostas Papadimitriou
        // if error occured and changes-since is set for the request
275 09f90e6e Kostas Papadimitriou
        // skip triggering the error and try again without the changes-since
276 09f90e6e Kostas Papadimitriou
        // parameter set
277 09f90e6e Kostas Papadimitriou
        var url = snf.util.parseUri(this.url);
278 09f90e6e Kostas Papadimitriou
        if (url.query.indexOf("changes-since") > -1) {
279 09f90e6e Kostas Papadimitriou
            clearApiCallDate(this.url, this.type);
280 09f90e6e Kostas Papadimitriou
            return _.toArray(arguments);
281 09f90e6e Kostas Papadimitriou
        }
282 9ce969a7 Kostas Papadimitriou
    
283 9ce969a7 Kostas Papadimitriou
        // skip aborts, notmodified (opera)
284 9ce969a7 Kostas Papadimitriou
        if (xhr === "error" || xhr === "timeout") {
285 9ce969a7 Kostas Papadimitriou
            var args = _.toArray(arguments);
286 9ce969a7 Kostas Papadimitriou
            api.trigger("error", args);
287 9ce969a7 Kostas Papadimitriou
        }
288 9ce969a7 Kostas Papadimitriou
289 9ce969a7 Kostas Papadimitriou
        return _.toArray(arguments);
290 8d08f18a Kostas Papadimitriou
    }
291 8d08f18a Kostas Papadimitriou
292 8d08f18a Kostas Papadimitriou
    api.completeHandler = function(xhr, status) {
293 8d08f18a Kostas Papadimitriou
        //debug("ajax complete", arguments)
294 8d08f18a Kostas Papadimitriou
        return arguments;
295 8d08f18a Kostas Papadimitriou
    }
296 8d08f18a Kostas Papadimitriou
297 8d08f18a Kostas Papadimitriou
    api.beforeSendHandler = function(xhr, settings) {
298 8d08f18a Kostas Papadimitriou
        //debug("ajax beforeSend", arguments)
299 8d08f18a Kostas Papadimitriou
        // ajax settings
300 8d08f18a Kostas Papadimitriou
        var ajax_settings = this;
301 8d08f18a Kostas Papadimitriou
        return arguments;
302 8d08f18a Kostas Papadimitriou
    }
303 8d08f18a Kostas Papadimitriou
304 9ce969a7 Kostas Papadimitriou
    // api call helper
305 9ce969a7 Kostas Papadimitriou
    api.call = function(url, method, data, complete, error, success, options) {
306 8d08f18a Kostas Papadimitriou
            var self = this;
307 8d08f18a Kostas Papadimitriou
            error = error || function(){};
308 8d08f18a Kostas Papadimitriou
            success = success || function(){};
309 8d08f18a Kostas Papadimitriou
            complete = complete || function(){};
310 adab5d39 Kostas Papadimitriou
            var extra = data ? data._options || {} : {};
311 bedfe884 Kostas Papadimitriou
312 9ce969a7 Kostas Papadimitriou
            // really ugly way to pass sync request options.
313 9ce969a7 Kostas Papadimitriou
            // it works though....
314 9ce969a7 Kostas Papadimitriou
            if (data && data._options) { delete data['_options'] };
315 9ce969a7 Kostas Papadimitriou
            
316 9ce969a7 Kostas Papadimitriou
            // prepare the params
317 8d08f18a Kostas Papadimitriou
            var params = {
318 8d08f18a Kostas Papadimitriou
                url: snf.config.api_url + "/" + url,
319 8d08f18a Kostas Papadimitriou
                data: data,
320 8d08f18a Kostas Papadimitriou
                success: success,
321 8d08f18a Kostas Papadimitriou
                complete: function() { api.trigger("call"); complete(this) },
322 8d08f18a Kostas Papadimitriou
                error: error
323 8d08f18a Kostas Papadimitriou
            }
324 f06ec46e Kostas Papadimitriou
325 9ce969a7 Kostas Papadimitriou
            params = _.extend(params, extra, options);
326 8d08f18a Kostas Papadimitriou
            this.sync(method, this, params);
327 8d08f18a Kostas Papadimitriou
        },
328 8d08f18a Kostas Papadimitriou
329 8d08f18a Kostas Papadimitriou
    _.extend(api, bb.Events);
330 8d08f18a Kostas Papadimitriou
    
331 9ce969a7 Kostas Papadimitriou
    // helper for callbacks that need to get called
332 9ce969a7 Kostas Papadimitriou
    // in fixed intervals
333 8d08f18a Kostas Papadimitriou
    api.updateHandler = function(options) {
334 8d08f18a Kostas Papadimitriou
        this.cb = options.callback;
335 8d08f18a Kostas Papadimitriou
        this.limit = options.limit;
336 8d08f18a Kostas Papadimitriou
        this.timeout = options.timeout;
337 8d08f18a Kostas Papadimitriou
338 8d08f18a Kostas Papadimitriou
        this.normal_timeout = options.timeout;
339 8d08f18a Kostas Papadimitriou
        this.fast_timeout = options.fast;
340 8d08f18a Kostas Papadimitriou
341 8d08f18a Kostas Papadimitriou
        this._called = 0;
342 8d08f18a Kostas Papadimitriou
        this.interval = undefined;
343 8d08f18a Kostas Papadimitriou
        this.call_on_start = options.call_on_start || true;
344 edd1d565 Kostas Papadimitriou
345 edd1d565 Kostas Papadimitriou
        this.running = false;
346 b1e6a2de Kostas Papadimitriou
        this.last_call = false;
347 8d08f18a Kostas Papadimitriou
        
348 8d08f18a Kostas Papadimitriou
        // wrapper
349 8d08f18a Kostas Papadimitriou
        function _cb() {
350 8d08f18a Kostas Papadimitriou
            if (this.fast_timeout == this.timeout){
351 8d08f18a Kostas Papadimitriou
                this._called++;
352 8d08f18a Kostas Papadimitriou
            }
353 8d08f18a Kostas Papadimitriou
354 8d08f18a Kostas Papadimitriou
            if (this._called >= this.limit && this.fast_timeout == this.timeout) {
355 8d08f18a Kostas Papadimitriou
                this.timeout = this.normal_timeout;
356 8d08f18a Kostas Papadimitriou
                this.setInterval()
357 8d08f18a Kostas Papadimitriou
            }
358 8d08f18a Kostas Papadimitriou
            this.cb();
359 b1e6a2de Kostas Papadimitriou
            this.last_call = new Date;
360 8d08f18a Kostas Papadimitriou
        };
361 edd1d565 Kostas Papadimitriou
362 8d08f18a Kostas Papadimitriou
        _cb = _.bind(_cb, this);
363 8d08f18a Kostas Papadimitriou
364 35855ff5 Kostas Papadimitriou
        this.faster = function(do_call) {
365 8d08f18a Kostas Papadimitriou
            this.timeout = this.fast_timeout;
366 8d08f18a Kostas Papadimitriou
            this._called = 0;
367 35855ff5 Kostas Papadimitriou
            this.setInterval(do_call);
368 8d08f18a Kostas Papadimitriou
        }
369 8d08f18a Kostas Papadimitriou
370 35855ff5 Kostas Papadimitriou
        this.setInterval = function(do_call) {
371 8d08f18a Kostas Papadimitriou
            this.trigger("clear");
372 8d08f18a Kostas Papadimitriou
            window.clearInterval(this.interval);
373 b1e6a2de Kostas Papadimitriou
            
374 edd1d565 Kostas Papadimitriou
            this.interval = window.setInterval(_cb, this.timeout);
375 edd1d565 Kostas Papadimitriou
            this.running = true;
376 b1e6a2de Kostas Papadimitriou
            
377 35855ff5 Kostas Papadimitriou
            var call = do_call || this.call_on_start;
378 b1e6a2de Kostas Papadimitriou
            
379 b1e6a2de Kostas Papadimitriou
            if (this.last_call) {
380 b1e6a2de Kostas Papadimitriou
                var next_call = (this.timeout - ((new Date) - this.last_call));
381 b1e6a2de Kostas Papadimitriou
                if (next_call < this.timeout/2) {
382 b1e6a2de Kostas Papadimitriou
                    call = true;
383 b1e6a2de Kostas Papadimitriou
                } else {
384 b1e6a2de Kostas Papadimitriou
                    call = false;
385 b1e6a2de Kostas Papadimitriou
                }
386 b1e6a2de Kostas Papadimitriou
            }
387 b1e6a2de Kostas Papadimitriou
388 b1e6a2de Kostas Papadimitriou
            if (call) {
389 8d08f18a Kostas Papadimitriou
                _cb();
390 8d08f18a Kostas Papadimitriou
            }
391 b1e6a2de Kostas Papadimitriou
            return this;
392 8d08f18a Kostas Papadimitriou
        }
393 8d08f18a Kostas Papadimitriou
394 2c9bfad1 Kostas Papadimitriou
        this.start = function (call_on_start) {
395 b1e6a2de Kostas Papadimitriou
            if (this.running) { this.stop() };
396 2c9bfad1 Kostas Papadimitriou
            this.call_on_start = call_on_start == undefined ? this.call_on_start : call_on_start;
397 8d08f18a Kostas Papadimitriou
            this.setInterval();
398 b1e6a2de Kostas Papadimitriou
            return this;
399 8d08f18a Kostas Papadimitriou
        }
400 8d08f18a Kostas Papadimitriou
401 8d08f18a Kostas Papadimitriou
        this.stop = function() {
402 8d08f18a Kostas Papadimitriou
            this.trigger("clear");
403 8d08f18a Kostas Papadimitriou
            window.clearInterval(this.interval);
404 edd1d565 Kostas Papadimitriou
            this.running = false;
405 b1e6a2de Kostas Papadimitriou
            return this;
406 8d08f18a Kostas Papadimitriou
        }
407 8d08f18a Kostas Papadimitriou
    }
408 8d08f18a Kostas Papadimitriou
    
409 9ce969a7 Kostas Papadimitriou
    // api error state
410 8d08f18a Kostas Papadimitriou
    api.stop_calls = false;
411 9ce969a7 Kostas Papadimitriou
    api.STATES = { NORMAL:1, WARN:0, ERROR:-1 };
412 9ce969a7 Kostas Papadimitriou
    api.error_state = api.STATES.NORMAL;
413 9ce969a7 Kostas Papadimitriou
414 9ce969a7 Kostas Papadimitriou
    // on api error update the api error_state
415 9ce969a7 Kostas Papadimitriou
    api.bind("error", function() {
416 9ffd10ce Kostas Papadimitriou
        if (snf.api.error_state == snf.api.STATES.ERROR) { return };
417 9ffd10ce Kostas Papadimitriou
418 9ce969a7 Kostas Papadimitriou
        var args = _.toArray(_.toArray(arguments)[0]);
419 9ce969a7 Kostas Papadimitriou
        var params = _.last(args);
420 9ce969a7 Kostas Papadimitriou
        
421 9ce969a7 Kostas Papadimitriou
        if (params.critical) {
422 9ce969a7 Kostas Papadimitriou
            snf.api.error_state = api.STATES.ERROR;
423 9ce969a7 Kostas Papadimitriou
            snf.api.stop_calls = true;
424 9ce969a7 Kostas Papadimitriou
        } else {
425 9ffd10ce Kostas Papadimitriou
            snf.api.error_state = api.STATES.ERROR;
426 9ce969a7 Kostas Papadimitriou
        }
427 9ce969a7 Kostas Papadimitriou
        snf.api.trigger("change:error_state", snf.api.error_state);
428 9ce969a7 Kostas Papadimitriou
    });
429 9ce969a7 Kostas Papadimitriou
    
430 9ce969a7 Kostas Papadimitriou
    // reset api error state
431 9ce969a7 Kostas Papadimitriou
    api.bind("reset", function() {
432 9ce969a7 Kostas Papadimitriou
        snf.api.error_state = api.STATES.NORMAL;
433 9ce969a7 Kostas Papadimitriou
        snf.api.stop_calls = false;
434 9ce969a7 Kostas Papadimitriou
        snf.api.trigger("change:error_state", snf.api.error_state);
435 9ce969a7 Kostas Papadimitriou
    })
436 8d08f18a Kostas Papadimitriou
437 8d08f18a Kostas Papadimitriou
    // make it eventable
438 8d08f18a Kostas Papadimitriou
    _.extend(api.updateHandler.prototype, bb.Events);
439 8d08f18a Kostas Papadimitriou
    
440 8d08f18a Kostas Papadimitriou
})(this);