Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / sync.js @ c16d0b2c

History | View | Annotate | Download (19 kB)

1 00469232 Kostas Papadimitriou
// Copyright 2011 GRNET S.A. All rights reserved.
2 00469232 Kostas Papadimitriou
// 
3 00469232 Kostas Papadimitriou
// Redistribution and use in source and binary forms, with or
4 00469232 Kostas Papadimitriou
// without modification, are permitted provided that the following
5 00469232 Kostas Papadimitriou
// conditions are met:
6 00469232 Kostas Papadimitriou
// 
7 00469232 Kostas Papadimitriou
//   1. Redistributions of source code must retain the above
8 00469232 Kostas Papadimitriou
//      copyright notice, this list of conditions and the following
9 00469232 Kostas Papadimitriou
//      disclaimer.
10 00469232 Kostas Papadimitriou
// 
11 00469232 Kostas Papadimitriou
//   2. Redistributions in binary form must reproduce the above
12 00469232 Kostas Papadimitriou
//      copyright notice, this list of conditions and the following
13 00469232 Kostas Papadimitriou
//      disclaimer in the documentation and/or other materials
14 00469232 Kostas Papadimitriou
//      provided with the distribution.
15 00469232 Kostas Papadimitriou
// 
16 00469232 Kostas Papadimitriou
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 00469232 Kostas Papadimitriou
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 00469232 Kostas Papadimitriou
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 00469232 Kostas Papadimitriou
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 00469232 Kostas Papadimitriou
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 00469232 Kostas Papadimitriou
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 00469232 Kostas Papadimitriou
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 00469232 Kostas Papadimitriou
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 00469232 Kostas Papadimitriou
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 00469232 Kostas Papadimitriou
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 00469232 Kostas Papadimitriou
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 00469232 Kostas Papadimitriou
// POSSIBILITY OF SUCH DAMAGE.
28 00469232 Kostas Papadimitriou
// 
29 00469232 Kostas Papadimitriou
// The views and conclusions contained in the software and
30 00469232 Kostas Papadimitriou
// documentation are those of the authors and should not be
31 00469232 Kostas Papadimitriou
// interpreted as representing official policies, either expressed
32 00469232 Kostas Papadimitriou
// or implied, of GRNET S.A.
33 00469232 Kostas Papadimitriou
// 
34 00469232 Kostas Papadimitriou
35 8d08f18a Kostas Papadimitriou
;(function(root){
36 8d08f18a Kostas Papadimitriou
    
37 8d08f18a Kostas Papadimitriou
    // root
38 8d08f18a Kostas Papadimitriou
    var root = root;
39 8d08f18a Kostas Papadimitriou
    
40 8d08f18a Kostas Papadimitriou
    // setup namepsaces
41 8d08f18a Kostas Papadimitriou
    var snf = root.synnefo = root.synnefo || {};
42 8d08f18a Kostas Papadimitriou
    var sync = snf.sync = snf.sync || {};
43 8d08f18a Kostas Papadimitriou
    var api = snf.api = snf.api || {};
44 9ce969a7 Kostas Papadimitriou
    var storage = snf.storage = snf.storage || {};
45 8d08f18a Kostas Papadimitriou
46 8d08f18a Kostas Papadimitriou
    // shortcuts
47 8d08f18a Kostas Papadimitriou
    var bb = Backbone;
48 8d08f18a Kostas Papadimitriou
49 8d08f18a Kostas Papadimitriou
    // logging
50 8d08f18a Kostas Papadimitriou
    var logger = new snf.logging.logger("SNF-API");
51 8d08f18a Kostas Papadimitriou
    var debug = _.bind(logger.debug, logger)
52 8d08f18a Kostas Papadimitriou
    
53 8d08f18a Kostas Papadimitriou
    // method map
54 8d08f18a Kostas Papadimitriou
    var methodMap = {
55 8d08f18a Kostas Papadimitriou
        'create': 'POST',
56 8d08f18a Kostas Papadimitriou
        'update': 'PUT',
57 8d08f18a Kostas Papadimitriou
        'delete': 'DELETE',
58 258235f4 Kostas Papadimitriou
        'read'  : 'GET',
59 258235f4 Kostas Papadimitriou
        'head'  : 'HEAD'
60 8d08f18a Kostas Papadimitriou
    };
61 8d08f18a Kostas Papadimitriou
62 8d08f18a Kostas Papadimitriou
    // custom getUrl function
63 8d08f18a Kostas Papadimitriou
    // handles url retrieval based on the object passed
64 8d08f18a Kostas Papadimitriou
    // on most occasions in the synnefo api this will call
65 8d08f18a Kostas Papadimitriou
    // the model/collection url method
66 2cee7cb0 Kostas Papadimitriou
    var getUrl = function(object, options, method) {
67 8d08f18a Kostas Papadimitriou
        if (!(object && object.url)) return null;
68 2cee7cb0 Kostas Papadimitriou
        return _.isFunction(object.url) ? object.url(options, method) : object.url;
69 8d08f18a Kostas Papadimitriou
    };
70 8d08f18a Kostas Papadimitriou
    
71 8d08f18a Kostas Papadimitriou
    // Call history (set of api paths with the dates the path last called)
72 8d08f18a Kostas Papadimitriou
    var api_history = api.requests = api.requests || {};
73 f06ec46e Kostas Papadimitriou
    var addApiCallDate = function(url, d, method) {
74 8d08f18a Kostas Papadimitriou
        if (d === undefined) { d = Date() };
75 8d08f18a Kostas Papadimitriou
        var path = snf.util.parseUri(url).path;
76 9ce969a7 Kostas Papadimitriou
        var key = path + "_" + method;
77 9ce969a7 Kostas Papadimitriou
78 9ce969a7 Kostas Papadimitriou
        // TODO: check if d is very old date
79 9ce969a7 Kostas Papadimitriou
        api_history[key] = d;
80 8d08f18a Kostas Papadimitriou
        return api_history[path]
81 8d08f18a Kostas Papadimitriou
    }
82 9ce969a7 Kostas Papadimitriou
83 9ce969a7 Kostas Papadimitriou
    var clearApiCallDate = function(url, method) {
84 9ce969a7 Kostas Papadimitriou
        var path = snf.util.parseUri(url).path;
85 9ce969a7 Kostas Papadimitriou
        var key = path + "_" + method;
86 9ce969a7 Kostas Papadimitriou
        api_history[key] = false;
87 9ce969a7 Kostas Papadimitriou
        return api_history[path]
88 9ce969a7 Kostas Papadimitriou
    }
89 9ce969a7 Kostas Papadimitriou
90 8d08f18a Kostas Papadimitriou
    var api_errors = api.errors = api.errors || [];
91 8d08f18a Kostas Papadimitriou
    var add_api_error = function(settings, data) {
92 8d08f18a Kostas Papadimitriou
        api_errors.push({url:settings.url, date:new Date, settings:settings, data:data})
93 8d08f18a Kostas Papadimitriou
    }
94 8d08f18a Kostas Papadimitriou
95 f06ec46e Kostas Papadimitriou
    var setChangesSince = function(url, method) {
96 8d08f18a Kostas Papadimitriou
        var path = snf.util.parseUri(url).path;
97 f06ec46e Kostas Papadimitriou
        var d = api_history[path + "_" + method];
98 8d08f18a Kostas Papadimitriou
        if (d) {
99 8d08f18a Kostas Papadimitriou
            url = url + "?changes-since=" + snf.util.ISODateString(d)
100 8d08f18a Kostas Papadimitriou
        }
101 8d08f18a Kostas Papadimitriou
        return url;
102 8d08f18a Kostas Papadimitriou
    }
103 8d08f18a Kostas Papadimitriou
    
104 8d08f18a Kostas Papadimitriou
    // custom sync method
105 8d08f18a Kostas Papadimitriou
    // appends global ajax handlers
106 8d08f18a Kostas Papadimitriou
    // handles changed-since url parameter based on api path
107 8d08f18a Kostas Papadimitriou
    api.sync = function(method, model, options) {
108 2cee7cb0 Kostas Papadimitriou
109 8d08f18a Kostas Papadimitriou
        var type = methodMap[method];
110 8d08f18a Kostas Papadimitriou
        
111 8d08f18a Kostas Papadimitriou
        if (model && (model.skipMethods || []).indexOf(method) >= 0) {
112 8d08f18a Kostas Papadimitriou
            throw "Model does not support " + method + " calls";
113 8d08f18a Kostas Papadimitriou
        }
114 dbe026f9 Kostas Papadimitriou
115 8d08f18a Kostas Papadimitriou
        if (!options.url) {
116 dbe026f9 Kostas Papadimitriou
            var urlobject = model;
117 dbe026f9 Kostas Papadimitriou
118 dbe026f9 Kostas Papadimitriou
            // fallback to collection url for item creation
119 dbe026f9 Kostas Papadimitriou
            if (method == "create" && model.isNew && model.isNew()) {
120 dbe026f9 Kostas Papadimitriou
                urlobject = model.collection;
121 dbe026f9 Kostas Papadimitriou
            }
122 dbe026f9 Kostas Papadimitriou
123 2cee7cb0 Kostas Papadimitriou
            options.url = getUrl(urlobject, options, method) || urlError();
124 e97e7c44 Kostas Papadimitriou
            if (urlobject && urlobject.supportIncUpdates) {
125 16392650 Kostas Papadimitriou
                options.url = options.refresh ? options.url : setChangesSince(options.url, type);
126 16392650 Kostas Papadimitriou
            }
127 8d08f18a Kostas Papadimitriou
            if (!options.refresh && options.cache === undefined) {
128 8d08f18a Kostas Papadimitriou
                options.cache = true;
129 8d08f18a Kostas Papadimitriou
            }
130 8d08f18a Kostas Papadimitriou
        }
131 8d08f18a Kostas Papadimitriou
132 9ce969a7 Kostas Papadimitriou
        // default error options
133 9ce969a7 Kostas Papadimitriou
        options.critical = options.critical === undefined ? true : options.critical;
134 9ce969a7 Kostas Papadimitriou
        options.display = options.display === undefined ? true : options.display;
135 9ce969a7 Kostas Papadimitriou
136 9ce969a7 Kostas Papadimitriou
        if (api.stop_calls && !options.no_skip) {
137 8d08f18a Kostas Papadimitriou
            return;
138 8d08f18a Kostas Papadimitriou
        }
139 8d08f18a Kostas Papadimitriou
140 8d08f18a Kostas Papadimitriou
        var success = options.success || function(){};
141 8d08f18a Kostas Papadimitriou
        var error = options.error || function(){};
142 8d08f18a Kostas Papadimitriou
        var complete = options.complete || function(){};
143 8d08f18a Kostas Papadimitriou
        var before_send = options.beforeSend || function(){};
144 8d08f18a Kostas Papadimitriou
145 8d08f18a Kostas Papadimitriou
        // custom json data.
146 8d08f18a Kostas Papadimitriou
        if (options.data && model && (method == 'create' || method == 'update')) {
147 8d08f18a Kostas Papadimitriou
            options.contentType = 'application/json';
148 8d08f18a Kostas Papadimitriou
            options.data = JSON.stringify(options.data);
149 8d08f18a Kostas Papadimitriou
        }
150 258235f4 Kostas Papadimitriou
        options.data = _.isEmpty(options.data) ? undefined : options.data;
151 8d08f18a Kostas Papadimitriou
        var api_params = {};
152 8d08f18a Kostas Papadimitriou
        var api_options = _.extend(api_params, options, {
153 8d08f18a Kostas Papadimitriou
            success: api.handlerWrapper(api.successHandler, success, "success"),
154 8d08f18a Kostas Papadimitriou
            error: api.handlerWrapper(api.errorHandler, error, "error"),
155 8d08f18a Kostas Papadimitriou
            complete: api.handlerWrapper(api.completeHandler, complete, "complete"),
156 8d08f18a Kostas Papadimitriou
            beforeSend: api.handlerWrapper(api.beforeSendHandler, before_send, "beforeSend"),
157 8d08f18a Kostas Papadimitriou
            cache: options.cache || false,
158 66be390b Kostas Papadimitriou
            timeout: options.timeout || snf.config.ajax_timeout || window.TIMEOUT || 5000
159 8d08f18a Kostas Papadimitriou
        });
160 8d08f18a Kostas Papadimitriou
        return bb.sync(method, model, api_options);
161 8d08f18a Kostas Papadimitriou
    }
162 8d08f18a Kostas Papadimitriou
    
163 9ce969a7 Kostas Papadimitriou
    api.timeouts_occured = 0;
164 9ce969a7 Kostas Papadimitriou
165 8d08f18a Kostas Papadimitriou
    api.handlerWrapper = function(wrap, method, type) {
166 9ce969a7 Kostas Papadimitriou
        
167 8d08f18a Kostas Papadimitriou
        var cb_type = type;
168 9ce969a7 Kostas Papadimitriou
169 8d08f18a Kostas Papadimitriou
        return function() {
170 8d08f18a Kostas Papadimitriou
            
171 9ce969a7 Kostas Papadimitriou
            var xhr = undefined;
172 9ce969a7 Kostas Papadimitriou
            var handler_type = type;
173 9ce969a7 Kostas Papadimitriou
            var args = arguments;
174 9ce969a7 Kostas Papadimitriou
            var ajax_options = this;
175 9ce969a7 Kostas Papadimitriou
176 9ce969a7 Kostas Papadimitriou
            // save the request date to use it as a changes-since value
177 9ce969a7 Kostas Papadimitriou
            // for opera because we are not able to determine
178 9ce969a7 Kostas Papadimitriou
            // response date header for 304 requests
179 9ce969a7 Kostas Papadimitriou
            if (handler_type == "beforeSend" && $.browser.opera) {
180 9ce969a7 Kostas Papadimitriou
                this.date_send = new Date;
181 9ce969a7 Kostas Papadimitriou
            }
182 9ce969a7 Kostas Papadimitriou
183 85f1cd1e Kostas Papadimitriou
            if (handler_type == "beforeSend") {
184 85f1cd1e Kostas Papadimitriou
                arguments[0].setRequestHeader('X-Auth-Token', synnefo.user.token);
185 85f1cd1e Kostas Papadimitriou
            }
186 85f1cd1e Kostas Papadimitriou
187 9ce969a7 Kostas Papadimitriou
            // error with status code 0 in opera
188 9ce969a7 Kostas Papadimitriou
            // act as 304 response
189 9ce969a7 Kostas Papadimitriou
            if (handler_type == "error" && $.browser.opera) {
190 9ce969a7 Kostas Papadimitriou
                if (arguments[0].status === 0 && arguments[1] === "error") {
191 9ce969a7 Kostas Papadimitriou
                    arguments[0].status = 304;
192 9ce969a7 Kostas Papadimitriou
                    arguments[1] = "notmodified";
193 9ce969a7 Kostas Papadimitriou
                    response_type = "success";
194 9ce969a7 Kostas Papadimitriou
                    xhr = arguments[0];
195 9ce969a7 Kostas Papadimitriou
                }
196 9ce969a7 Kostas Papadimitriou
            }
197 9ce969a7 Kostas Papadimitriou
            
198 9ce969a7 Kostas Papadimitriou
            // add error in api errors registry
199 9ce969a7 Kostas Papadimitriou
            // api errors registry will be sent
200 9ce969a7 Kostas Papadimitriou
            // if user reports an error using feedback form
201 9ce969a7 Kostas Papadimitriou
            if (handler_type == "error") {
202 ec511098 Kostas Papadimitriou
                // skip logging requested ?
203 ec511098 Kostas Papadimitriou
                // if not log this error
204 ec511098 Kostas Papadimitriou
                if (this.log_error !== false) {
205 ec511098 Kostas Papadimitriou
                    add_api_error(this, arguments);
206 ec511098 Kostas Papadimitriou
                }
207 8d08f18a Kostas Papadimitriou
            }
208 6a3a5bf7 Kostas Papadimitriou
            
209 9ce969a7 Kostas Papadimitriou
            // identify response status
210 6a3a5bf7 Kostas Papadimitriou
            var status = 304;
211 6a3a5bf7 Kostas Papadimitriou
            if (arguments[0]) {
212 9ce969a7 Kostas Papadimitriou
                status = arguments[0].status;
213 6a3a5bf7 Kostas Papadimitriou
            }
214 8d08f18a Kostas Papadimitriou
            
215 9ce969a7 Kostas Papadimitriou
            // identify aborted request
216 8d08f18a Kostas Papadimitriou
            try {
217 8d08f18a Kostas Papadimitriou
                if (args[1] === "abort") {
218 8d08f18a Kostas Papadimitriou
                    api.trigger("abort");
219 8d08f18a Kostas Papadimitriou
                    return;
220 8d08f18a Kostas Papadimitriou
                }
221 8d08f18a Kostas Papadimitriou
            } catch(error) {
222 8d08f18a Kostas Papadimitriou
                console.error("error aborting", error);
223 8d08f18a Kostas Papadimitriou
            }
224 9ce969a7 Kostas Papadimitriou
            
225 9ce969a7 Kostas Papadimitriou
            // try to set the last request date
226 9ce969a7 Kostas Papadimitriou
            // only for notmodified or succeed responses
227 8d08f18a Kostas Papadimitriou
            try {
228 8d08f18a Kostas Papadimitriou
                // identify xhr object
229 9ce969a7 Kostas Papadimitriou
                xhr = xhr || args[2];
230 9ce969a7 Kostas Papadimitriou
                
231 9ce969a7 Kostas Papadimitriou
                // not modified response
232 9ce969a7 Kostas Papadimitriou
                if (args[1] === "notmodified") {
233 9ce969a7 Kostas Papadimitriou
                    if (xhr) {
234 9ce969a7 Kostas Papadimitriou
                        // use date_send if exists (opera browser)
235 9ce969a7 Kostas Papadimitriou
                        var d = this.date_send || xhr.getResponseHeader('Date');
236 9ce969a7 Kostas Papadimitriou
                        if (d) { addApiCallDate(this.url, new Date(d), ajax_options.type); };
237 8d08f18a Kostas Papadimitriou
                    }
238 aeadb7bf Kostas Papadimitriou
239 8d08f18a Kostas Papadimitriou
                    return;
240 8d08f18a Kostas Papadimitriou
                }
241 8d08f18a Kostas Papadimitriou
                
242 9ce969a7 Kostas Papadimitriou
                // success response
243 9ce969a7 Kostas Papadimitriou
                if (args[1] == "success" && handler_type == "success") {
244 9ce969a7 Kostas Papadimitriou
                    try {
245 9ce969a7 Kostas Papadimitriou
                        // use date_send if exists (opera browser)
246 9ce969a7 Kostas Papadimitriou
                        var d = this.date_send || args[2].getResponseHeader('Date');
247 9ce969a7 Kostas Papadimitriou
                        if (d) { addApiCallDate(this.url, new Date(d), ajax_options.type); };
248 9ce969a7 Kostas Papadimitriou
                    } catch (err) {
249 9ce969a7 Kostas Papadimitriou
                        console.error(err)
250 9ce969a7 Kostas Papadimitriou
                    }
251 9ce969a7 Kostas Papadimitriou
                }
252 8d08f18a Kostas Papadimitriou
            } catch (err) {
253 8d08f18a Kostas Papadimitriou
                console.error(err);
254 8d08f18a Kostas Papadimitriou
            }
255 9ce969a7 Kostas Papadimitriou
            
256 9ce969a7 Kostas Papadimitriou
            // dont call error callback for non modified responses
257 9ce969a7 Kostas Papadimitriou
            if (arguments[1] === "notmodified") {
258 9ce969a7 Kostas Papadimitriou
                return;
259 9ce969a7 Kostas Papadimitriou
            }
260 aeadb7bf Kostas Papadimitriou
261 aeadb7bf Kostas Papadimitriou
            if (["beforeSend", "complete"].indexOf(cb_type) == -1 && this.is_recurrent) {
262 aeadb7bf Kostas Papadimitriou
                // trigger event to notify that a recurrent event
263 aeadb7bf Kostas Papadimitriou
                // has returned status other than notmodified
264 aeadb7bf Kostas Papadimitriou
                snf.api.trigger("change:recurrent");
265 aeadb7bf Kostas Papadimitriou
            }
266 9ce969a7 Kostas Papadimitriou
            
267 9ce969a7 Kostas Papadimitriou
            // prepare arguments for error callbacks
268 9ce969a7 Kostas Papadimitriou
            var cb_args = _.toArray(arguments);
269 9ce969a7 Kostas Papadimitriou
            if (handler_type === "error") {
270 9ce969a7 Kostas Papadimitriou
                cb_args.push(_.clone(this));
271 9ce969a7 Kostas Papadimitriou
            }
272 9ce969a7 Kostas Papadimitriou
            
273 9ce969a7 Kostas Papadimitriou
            // determine if we need to call our callback wrapper
274 9ce969a7 Kostas Papadimitriou
            var call_api_handler = true;
275 c72a830d Kostas Papadimitriou
            
276 9ce969a7 Kostas Papadimitriou
            // request handles errors by itself, s
277 9ce969a7 Kostas Papadimitriou
            if (handler_type == "error" && this.skip_api_error) {
278 9ce969a7 Kostas Papadimitriou
                call_api_handler = false
279 9ce969a7 Kostas Papadimitriou
            }
280 9ce969a7 Kostas Papadimitriou
281 9ce969a7 Kostas Papadimitriou
            // aborted request, don't call error handler
282 9ce969a7 Kostas Papadimitriou
            if (handler_type === "error" && args[1] === "abort") {
283 9ce969a7 Kostas Papadimitriou
                call_api_handler = false;
284 9ce969a7 Kostas Papadimitriou
            }
285 9ce969a7 Kostas Papadimitriou
            
286 9ce969a7 Kostas Papadimitriou
            // reset api call date, next call will be sent without changes-since
287 9ce969a7 Kostas Papadimitriou
            // parameter set
288 9ce969a7 Kostas Papadimitriou
            if (handler_type === "error") {
289 9ce969a7 Kostas Papadimitriou
                if (args[1] === "error") {
290 9ce969a7 Kostas Papadimitriou
                    clearApiCallDate(this.url, this.type);
291 9ce969a7 Kostas Papadimitriou
                }
292 9ce969a7 Kostas Papadimitriou
            }
293 9ce969a7 Kostas Papadimitriou
            
294 9ce969a7 Kostas Papadimitriou
            // call api call back and retrieve params to
295 9ce969a7 Kostas Papadimitriou
            // be passed to the callback method set for
296 9ce969a7 Kostas Papadimitriou
            // this type of response
297 9ce969a7 Kostas Papadimitriou
            if (call_api_handler) {
298 9ce969a7 Kostas Papadimitriou
                cb_args = wrap.apply(this, cb_args);
299 9ce969a7 Kostas Papadimitriou
            }
300 9ce969a7 Kostas Papadimitriou
            
301 9ce969a7 Kostas Papadimitriou
            // call requested callback
302 9ce969a7 Kostas Papadimitriou
            method.apply(this, _.toArray(cb_args));
303 8d08f18a Kostas Papadimitriou
        }
304 8d08f18a Kostas Papadimitriou
    }
305 8d08f18a Kostas Papadimitriou
306 8d08f18a Kostas Papadimitriou
    api.successHandler = function(data, status, xhr) {
307 8d08f18a Kostas Papadimitriou
        //debug("ajax success", arguments)
308 8d08f18a Kostas Papadimitriou
        // on success, update the last date we called the api url
309 8d08f18a Kostas Papadimitriou
        return [data, status, xhr];
310 8d08f18a Kostas Papadimitriou
    }
311 8d08f18a Kostas Papadimitriou
312 8d08f18a Kostas Papadimitriou
    api.errorHandler = function(event, xhr, settings, error) {
313 e472ed67 Kostas Papadimitriou
        // dont trigger api error until timeouts occured
314 9ce969a7 Kostas Papadimitriou
        // exceed the skips_timeouts limit
315 9ce969a7 Kostas Papadimitriou
        //
316 9ce969a7 Kostas Papadimitriou
        // check only requests with skips_timeouts option set
317 aeadb7bf Kostas Papadimitriou
        
318 9ce969a7 Kostas Papadimitriou
        if (xhr === "timeout" && _.last(arguments).skips_timeouts) {
319 9ce969a7 Kostas Papadimitriou
            var skip_timeouts = snf.config.skip_timeouts || 1;
320 9ce969a7 Kostas Papadimitriou
            if (snf.api.timeouts_occured < skip_timeouts) {
321 9ce969a7 Kostas Papadimitriou
                snf.api.timeouts_occured++;
322 9ce969a7 Kostas Papadimitriou
                return;
323 9ce969a7 Kostas Papadimitriou
            } else {
324 40f6b82f Kostas Papadimitriou
                // reset trigger error
325 9ce969a7 Kostas Papadimitriou
                snf.api.timeouts_occured = 0;
326 40f6b82f Kostas Papadimitriou
                var args = _.toArray(arguments);
327 40f6b82f Kostas Papadimitriou
                api.trigger("error", args);
328 9ce969a7 Kostas Papadimitriou
            }
329 8d08f18a Kostas Papadimitriou
        }
330 09f90e6e Kostas Papadimitriou
331 09f90e6e Kostas Papadimitriou
        // if error occured and changes-since is set for the request
332 09f90e6e Kostas Papadimitriou
        // skip triggering the error and try again without the changes-since
333 09f90e6e Kostas Papadimitriou
        // parameter set
334 09f90e6e Kostas Papadimitriou
        var url = snf.util.parseUri(this.url);
335 09f90e6e Kostas Papadimitriou
        if (url.query.indexOf("changes-since") > -1) {
336 09f90e6e Kostas Papadimitriou
            clearApiCallDate(this.url, this.type);
337 09f90e6e Kostas Papadimitriou
            return _.toArray(arguments);
338 09f90e6e Kostas Papadimitriou
        }
339 9ce969a7 Kostas Papadimitriou
    
340 9ce969a7 Kostas Papadimitriou
        // skip aborts, notmodified (opera)
341 9ce969a7 Kostas Papadimitriou
        if (xhr === "error" || xhr === "timeout") {
342 9ce969a7 Kostas Papadimitriou
            var args = _.toArray(arguments);
343 9ce969a7 Kostas Papadimitriou
            api.trigger("error", args);
344 9ce969a7 Kostas Papadimitriou
        }
345 9ce969a7 Kostas Papadimitriou
346 9ce969a7 Kostas Papadimitriou
        return _.toArray(arguments);
347 8d08f18a Kostas Papadimitriou
    }
348 8d08f18a Kostas Papadimitriou
349 8d08f18a Kostas Papadimitriou
    api.completeHandler = function(xhr, status) {
350 8d08f18a Kostas Papadimitriou
        //debug("ajax complete", arguments)
351 8d08f18a Kostas Papadimitriou
        return arguments;
352 8d08f18a Kostas Papadimitriou
    }
353 8d08f18a Kostas Papadimitriou
354 8d08f18a Kostas Papadimitriou
    api.beforeSendHandler = function(xhr, settings) {
355 8d08f18a Kostas Papadimitriou
        //debug("ajax beforeSend", arguments)
356 8d08f18a Kostas Papadimitriou
        // ajax settings
357 8d08f18a Kostas Papadimitriou
        var ajax_settings = this;
358 8d08f18a Kostas Papadimitriou
        return arguments;
359 8d08f18a Kostas Papadimitriou
    }
360 8d08f18a Kostas Papadimitriou
361 9ce969a7 Kostas Papadimitriou
    // api call helper
362 9ce969a7 Kostas Papadimitriou
    api.call = function(url, method, data, complete, error, success, options) {
363 8d08f18a Kostas Papadimitriou
            var self = this;
364 8d08f18a Kostas Papadimitriou
            error = error || function(){};
365 8d08f18a Kostas Papadimitriou
            success = success || function(){};
366 8d08f18a Kostas Papadimitriou
            complete = complete || function(){};
367 adab5d39 Kostas Papadimitriou
            var extra = data ? data._options || {} : {};
368 bedfe884 Kostas Papadimitriou
369 9ce969a7 Kostas Papadimitriou
            // really ugly way to pass sync request options.
370 9ce969a7 Kostas Papadimitriou
            // it works though....
371 9ce969a7 Kostas Papadimitriou
            if (data && data._options) { delete data['_options'] };
372 9ce969a7 Kostas Papadimitriou
            
373 9ce969a7 Kostas Papadimitriou
            // prepare the params
374 8d08f18a Kostas Papadimitriou
            var params = {
375 47e34b99 Kostas Papadimitriou
                url: snf.config.api_urls[this.api_type] + "/" + url,
376 8d08f18a Kostas Papadimitriou
                data: data,
377 8d08f18a Kostas Papadimitriou
                success: success,
378 8d08f18a Kostas Papadimitriou
                complete: function() { api.trigger("call"); complete(this) },
379 8d08f18a Kostas Papadimitriou
                error: error
380 8d08f18a Kostas Papadimitriou
            }
381 f06ec46e Kostas Papadimitriou
382 9ce969a7 Kostas Papadimitriou
            params = _.extend(params, extra, options);
383 8d08f18a Kostas Papadimitriou
            this.sync(method, this, params);
384 8d08f18a Kostas Papadimitriou
        },
385 8d08f18a Kostas Papadimitriou
386 8d08f18a Kostas Papadimitriou
    _.extend(api, bb.Events);
387 8d08f18a Kostas Papadimitriou
    
388 9ce969a7 Kostas Papadimitriou
    // helper for callbacks that need to get called
389 9ce969a7 Kostas Papadimitriou
    // in fixed intervals
390 8d08f18a Kostas Papadimitriou
    api.updateHandler = function(options) {
391 8d08f18a Kostas Papadimitriou
        this.cb = options.callback;
392 258235f4 Kostas Papadimitriou
        this.handler_id = options.id;
393 8d08f18a Kostas Papadimitriou
394 aeadb7bf Kostas Papadimitriou
        // the interval with which we start
395 aeadb7bf Kostas Papadimitriou
        this.interval = this.normal_interval = options.interval || 4000;
396 8d08f18a Kostas Papadimitriou
397 aeadb7bf Kostas Papadimitriou
        // fast interval
398 aeadb7bf Kostas Papadimitriou
        // set when faster() gets called
399 aeadb7bf Kostas Papadimitriou
        this.fast_interval = options.fast || 1000;
400 aeadb7bf Kostas Papadimitriou
    
401 aeadb7bf Kostas Papadimitriou
        // after how many calls to increase the interval
402 1cc6e94f Kostas Papadimitriou
        this.interval_increase_count = options.increase_after_calls || 0;
403 aeadb7bf Kostas Papadimitriou
404 aeadb7bf Kostas Papadimitriou
        // increase the timer by this value after interval_increase_count calls
405 aeadb7bf Kostas Papadimitriou
        this.interval_increase = options.increase || 500;
406 aeadb7bf Kostas Papadimitriou
        
407 aeadb7bf Kostas Papadimitriou
        // maximum interval limit
408 aeadb7bf Kostas Papadimitriou
        this.maximum_interval = options.max || 60000;
409 aeadb7bf Kostas Papadimitriou
        
410 aeadb7bf Kostas Papadimitriou
        // make a call before interval starts
411 aeadb7bf Kostas Papadimitriou
        this.call_on_start = options.initial_call === undefined ? true : options.initial_call;
412 aeadb7bf Kostas Papadimitriou
            
413 aeadb7bf Kostas Papadimitriou
        this.increase_enabled = this.interval_increase_count === 0;
414 edd1d565 Kostas Papadimitriou
415 aeadb7bf Kostas Papadimitriou
        if (this.increase_enabled) {
416 aeadb7bf Kostas Papadimitriou
            this.maximum_interval = this.interval;
417 aeadb7bf Kostas Papadimitriou
            this.interval_increase_count = 1;
418 aeadb7bf Kostas Papadimitriou
        }
419 aeadb7bf Kostas Papadimitriou
        
420 aeadb7bf Kostas Papadimitriou
        // inner params
421 aeadb7bf Kostas Papadimitriou
        this._called = 0;
422 aeadb7bf Kostas Papadimitriou
        this._first_call_date = undefined;
423 aeadb7bf Kostas Papadimitriou
        this.window_interval = undefined;
424 aeadb7bf Kostas Papadimitriou
        
425 aeadb7bf Kostas Papadimitriou
        // state params
426 edd1d565 Kostas Papadimitriou
        this.running = false;
427 b1e6a2de Kostas Papadimitriou
        this.last_call = false;
428 8d08f18a Kostas Papadimitriou
        
429 aeadb7bf Kostas Papadimitriou
        // helper for api calls
430 aeadb7bf Kostas Papadimitriou
        // TODO: move this out of here :/
431 aeadb7bf Kostas Papadimitriou
        if (options.is_recurrent) {
432 aeadb7bf Kostas Papadimitriou
            snf.api.bind("change:recurrent", _.bind(function() {
433 40f6b82f Kostas Papadimitriou
                if (this.running) {
434 40f6b82f Kostas Papadimitriou
                    this.faster(true);
435 40f6b82f Kostas Papadimitriou
                }
436 aeadb7bf Kostas Papadimitriou
            }, this));
437 aeadb7bf Kostas Papadimitriou
        }
438 aeadb7bf Kostas Papadimitriou
        
439 aeadb7bf Kostas Papadimitriou
        // callback wrapper
440 258235f4 Kostas Papadimitriou
        this._cb = function() {
441 40f6b82f Kostas Papadimitriou
            if (!this.running) { this.stop() }
442 aeadb7bf Kostas Papadimitriou
            if (this._called >= this.interval_increase_count) {
443 aeadb7bf Kostas Papadimitriou
                this._called = 0;
444 aeadb7bf Kostas Papadimitriou
                this.slower(false);
445 8d08f18a Kostas Papadimitriou
            }
446 aeadb7bf Kostas Papadimitriou
            
447 8d08f18a Kostas Papadimitriou
            this.cb();
448 b1e6a2de Kostas Papadimitriou
            this.last_call = new Date;
449 aeadb7bf Kostas Papadimitriou
            this._called++;
450 8d08f18a Kostas Papadimitriou
        };
451 edd1d565 Kostas Papadimitriou
452 aeadb7bf Kostas Papadimitriou
        // start from faster timeout and start increasing
453 35855ff5 Kostas Papadimitriou
        this.faster = function(do_call) {
454 aeadb7bf Kostas Papadimitriou
            if (!this.running) { return }
455 1cc6e94f Kostas Papadimitriou
456 aeadb7bf Kostas Papadimitriou
            this.interval = this.fast_interval;
457 35855ff5 Kostas Papadimitriou
            this.setInterval(do_call);
458 8d08f18a Kostas Papadimitriou
        }
459 8d08f18a Kostas Papadimitriou
460 aeadb7bf Kostas Papadimitriou
        // slow down
461 aeadb7bf Kostas Papadimitriou
        this.slower = function(do_call) {
462 aeadb7bf Kostas Papadimitriou
            if (this.interval == this.maximum_interval) {
463 aeadb7bf Kostas Papadimitriou
                // no need to increase
464 aeadb7bf Kostas Papadimitriou
                return;
465 aeadb7bf Kostas Papadimitriou
            }
466 aeadb7bf Kostas Papadimitriou
            
467 1cc6e94f Kostas Papadimitriou
            this.interval = this.interval + this.interval_increase;
468 aeadb7bf Kostas Papadimitriou
            // increase timeout
469 aeadb7bf Kostas Papadimitriou
            if (this.interval > this.maximum_interval) {
470 aeadb7bf Kostas Papadimitriou
                this.interval = this.maximum_interval;
471 aeadb7bf Kostas Papadimitriou
            }
472 aeadb7bf Kostas Papadimitriou
            
473 aeadb7bf Kostas Papadimitriou
            this.setInterval(do_call);
474 aeadb7bf Kostas Papadimitriou
        }
475 aeadb7bf Kostas Papadimitriou
        
476 aeadb7bf Kostas Papadimitriou
        // reset internal
477 35855ff5 Kostas Papadimitriou
        this.setInterval = function(do_call) {
478 8d08f18a Kostas Papadimitriou
            this.trigger("clear");
479 b1e6a2de Kostas Papadimitriou
            
480 aeadb7bf Kostas Papadimitriou
            // reset times called
481 aeadb7bf Kostas Papadimitriou
            this._called = 0;
482 aeadb7bf Kostas Papadimitriou
            
483 aeadb7bf Kostas Papadimitriou
            window.clearInterval(this.window_interval);
484 258235f4 Kostas Papadimitriou
            this.window_interval = window.setInterval(_.bind(this._cb, this), this.interval);
485 aeadb7bf Kostas Papadimitriou
486 edd1d565 Kostas Papadimitriou
            this.running = true;
487 b1e6a2de Kostas Papadimitriou
            
488 aeadb7bf Kostas Papadimitriou
            // if no do_call set, fallback to object creation option
489 aeadb7bf Kostas Papadimitriou
            // else force what was requested
490 aeadb7bf Kostas Papadimitriou
            var call = do_call === undefined ? this.call_on_start : do_call;
491 b1e6a2de Kostas Papadimitriou
            
492 aeadb7bf Kostas Papadimitriou
            if (this.last_call && do_call !== false) {
493 aeadb7bf Kostas Papadimitriou
                var next_call = (this.interval - ((new Date) - this.last_call));
494 aeadb7bf Kostas Papadimitriou
                if (next_call < this.interval/2) {
495 b1e6a2de Kostas Papadimitriou
                    call = true;
496 b1e6a2de Kostas Papadimitriou
                } else {
497 b1e6a2de Kostas Papadimitriou
                    call = false;
498 b1e6a2de Kostas Papadimitriou
                }
499 b1e6a2de Kostas Papadimitriou
            }
500 aeadb7bf Kostas Papadimitriou
            
501 b1e6a2de Kostas Papadimitriou
            if (call) {
502 258235f4 Kostas Papadimitriou
                this._cb();
503 8d08f18a Kostas Papadimitriou
            }
504 aeadb7bf Kostas Papadimitriou
505 b1e6a2de Kostas Papadimitriou
            return this;
506 8d08f18a Kostas Papadimitriou
        }
507 8d08f18a Kostas Papadimitriou
508 2c9bfad1 Kostas Papadimitriou
        this.start = function (call_on_start) {
509 b1e6a2de Kostas Papadimitriou
            if (this.running) { this.stop() };
510 aeadb7bf Kostas Papadimitriou
            this.setInterval(call_on_start);
511 b1e6a2de Kostas Papadimitriou
            return this;
512 8d08f18a Kostas Papadimitriou
        }
513 8d08f18a Kostas Papadimitriou
514 8d08f18a Kostas Papadimitriou
        this.stop = function() {
515 8d08f18a Kostas Papadimitriou
            this.trigger("clear");
516 aeadb7bf Kostas Papadimitriou
            window.clearInterval(this.window_interval);
517 edd1d565 Kostas Papadimitriou
            this.running = false;
518 b1e6a2de Kostas Papadimitriou
            return this;
519 8d08f18a Kostas Papadimitriou
        }
520 8d08f18a Kostas Papadimitriou
    }
521 8d08f18a Kostas Papadimitriou
    
522 9ce969a7 Kostas Papadimitriou
    // api error state
523 8d08f18a Kostas Papadimitriou
    api.stop_calls = false;
524 9ce969a7 Kostas Papadimitriou
    api.STATES = { NORMAL:1, WARN:0, ERROR:-1 };
525 9ce969a7 Kostas Papadimitriou
    api.error_state = api.STATES.NORMAL;
526 9ce969a7 Kostas Papadimitriou
527 9ce969a7 Kostas Papadimitriou
    // on api error update the api error_state
528 9ce969a7 Kostas Papadimitriou
    api.bind("error", function() {
529 9ffd10ce Kostas Papadimitriou
        if (snf.api.error_state == snf.api.STATES.ERROR) { return };
530 9ffd10ce Kostas Papadimitriou
531 9ce969a7 Kostas Papadimitriou
        var args = _.toArray(_.toArray(arguments)[0]);
532 9ce969a7 Kostas Papadimitriou
        var params = _.last(args);
533 9ce969a7 Kostas Papadimitriou
        
534 9ce969a7 Kostas Papadimitriou
        if (params.critical) {
535 9ce969a7 Kostas Papadimitriou
            snf.api.error_state = api.STATES.ERROR;
536 9ce969a7 Kostas Papadimitriou
            snf.api.stop_calls = true;
537 9ce969a7 Kostas Papadimitriou
        } else {
538 9ffd10ce Kostas Papadimitriou
            snf.api.error_state = api.STATES.ERROR;
539 9ce969a7 Kostas Papadimitriou
        }
540 9ce969a7 Kostas Papadimitriou
        snf.api.trigger("change:error_state", snf.api.error_state);
541 9ce969a7 Kostas Papadimitriou
    });
542 9ce969a7 Kostas Papadimitriou
    
543 9ce969a7 Kostas Papadimitriou
    // reset api error state
544 9ce969a7 Kostas Papadimitriou
    api.bind("reset", function() {
545 9ce969a7 Kostas Papadimitriou
        snf.api.error_state = api.STATES.NORMAL;
546 9ce969a7 Kostas Papadimitriou
        snf.api.stop_calls = false;
547 9ce969a7 Kostas Papadimitriou
        snf.api.trigger("change:error_state", snf.api.error_state);
548 9ce969a7 Kostas Papadimitriou
    })
549 8d08f18a Kostas Papadimitriou
550 8d08f18a Kostas Papadimitriou
    // make it eventable
551 8d08f18a Kostas Papadimitriou
    _.extend(api.updateHandler.prototype, bb.Events);
552 8d08f18a Kostas Papadimitriou
    
553 8d08f18a Kostas Papadimitriou
})(this);