Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (19.3 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 6de7fde5 Kostas Papadimitriou
            // subtract threshold
100 6de7fde5 Kostas Papadimitriou
            d = new Date(d - synnefo.config.changes_since_alignment);
101 6de7fde5 Kostas Papadimitriou
            url = url + "?changes-since=" + snf.util.ISODateString(d);
102 8d08f18a Kostas Papadimitriou
        }
103 8d08f18a Kostas Papadimitriou
        return url;
104 8d08f18a Kostas Papadimitriou
    }
105 8d08f18a Kostas Papadimitriou
    
106 8d08f18a Kostas Papadimitriou
    // custom sync method
107 8d08f18a Kostas Papadimitriou
    // appends global ajax handlers
108 8d08f18a Kostas Papadimitriou
    // handles changed-since url parameter based on api path
109 8d08f18a Kostas Papadimitriou
    api.sync = function(method, model, options) {
110 2cee7cb0 Kostas Papadimitriou
111 8d08f18a Kostas Papadimitriou
        var type = methodMap[method];
112 8d08f18a Kostas Papadimitriou
        
113 8d08f18a Kostas Papadimitriou
        if (model && (model.skipMethods || []).indexOf(method) >= 0) {
114 8d08f18a Kostas Papadimitriou
            throw "Model does not support " + method + " calls";
115 8d08f18a Kostas Papadimitriou
        }
116 dbe026f9 Kostas Papadimitriou
117 8d08f18a Kostas Papadimitriou
        if (!options.url) {
118 dbe026f9 Kostas Papadimitriou
            var urlobject = model;
119 dbe026f9 Kostas Papadimitriou
120 dbe026f9 Kostas Papadimitriou
            // fallback to collection url for item creation
121 dbe026f9 Kostas Papadimitriou
            if (method == "create" && model.isNew && model.isNew()) {
122 dbe026f9 Kostas Papadimitriou
                urlobject = model.collection;
123 dbe026f9 Kostas Papadimitriou
            }
124 dbe026f9 Kostas Papadimitriou
125 2cee7cb0 Kostas Papadimitriou
            options.url = getUrl(urlobject, options, method) || urlError();
126 e97e7c44 Kostas Papadimitriou
            if (urlobject && urlobject.supportIncUpdates) {
127 16392650 Kostas Papadimitriou
                options.url = options.refresh ? options.url : setChangesSince(options.url, type);
128 16392650 Kostas Papadimitriou
            }
129 8d08f18a Kostas Papadimitriou
            if (!options.refresh && options.cache === undefined) {
130 8d08f18a Kostas Papadimitriou
                options.cache = true;
131 8d08f18a Kostas Papadimitriou
            }
132 8d08f18a Kostas Papadimitriou
        }
133 8d08f18a Kostas Papadimitriou
134 9ce969a7 Kostas Papadimitriou
        // default error options
135 9ce969a7 Kostas Papadimitriou
        options.critical = options.critical === undefined ? true : options.critical;
136 9ce969a7 Kostas Papadimitriou
        options.display = options.display === undefined ? true : options.display;
137 9ce969a7 Kostas Papadimitriou
138 9ce969a7 Kostas Papadimitriou
        if (api.stop_calls && !options.no_skip) {
139 8d08f18a Kostas Papadimitriou
            return;
140 8d08f18a Kostas Papadimitriou
        }
141 8d08f18a Kostas Papadimitriou
142 8d08f18a Kostas Papadimitriou
        var success = options.success || function(){};
143 8d08f18a Kostas Papadimitriou
        var error = options.error || function(){};
144 8d08f18a Kostas Papadimitriou
        var complete = options.complete || function(){};
145 8d08f18a Kostas Papadimitriou
        var before_send = options.beforeSend || function(){};
146 8d08f18a Kostas Papadimitriou
147 8d08f18a Kostas Papadimitriou
        // custom json data.
148 8d08f18a Kostas Papadimitriou
        if (options.data && model && (method == 'create' || method == 'update')) {
149 8d08f18a Kostas Papadimitriou
            options.contentType = 'application/json';
150 8d08f18a Kostas Papadimitriou
            options.data = JSON.stringify(options.data);
151 8d08f18a Kostas Papadimitriou
        }
152 258235f4 Kostas Papadimitriou
        options.data = _.isEmpty(options.data) ? undefined : options.data;
153 8d08f18a Kostas Papadimitriou
        var api_params = {};
154 8d08f18a Kostas Papadimitriou
        var api_options = _.extend(api_params, options, {
155 8d08f18a Kostas Papadimitriou
            success: api.handlerWrapper(api.successHandler, success, "success"),
156 8d08f18a Kostas Papadimitriou
            error: api.handlerWrapper(api.errorHandler, error, "error"),
157 8d08f18a Kostas Papadimitriou
            complete: api.handlerWrapper(api.completeHandler, complete, "complete"),
158 8d08f18a Kostas Papadimitriou
            beforeSend: api.handlerWrapper(api.beforeSendHandler, before_send, "beforeSend"),
159 8d08f18a Kostas Papadimitriou
            cache: options.cache || false,
160 66be390b Kostas Papadimitriou
            timeout: options.timeout || snf.config.ajax_timeout || window.TIMEOUT || 5000
161 8d08f18a Kostas Papadimitriou
        });
162 8d08f18a Kostas Papadimitriou
        return bb.sync(method, model, api_options);
163 8d08f18a Kostas Papadimitriou
    }
164 8d08f18a Kostas Papadimitriou
    
165 9ce969a7 Kostas Papadimitriou
    api.timeouts_occured = 0;
166 9ce969a7 Kostas Papadimitriou
167 8d08f18a Kostas Papadimitriou
    api.handlerWrapper = function(wrap, method, type) {
168 9ce969a7 Kostas Papadimitriou
        
169 8d08f18a Kostas Papadimitriou
        var cb_type = type;
170 9ce969a7 Kostas Papadimitriou
171 8d08f18a Kostas Papadimitriou
        return function() {
172 8d08f18a Kostas Papadimitriou
            
173 9ce969a7 Kostas Papadimitriou
            var xhr = undefined;
174 9ce969a7 Kostas Papadimitriou
            var handler_type = type;
175 9ce969a7 Kostas Papadimitriou
            var args = arguments;
176 9ce969a7 Kostas Papadimitriou
            var ajax_options = this;
177 9ce969a7 Kostas Papadimitriou
178 9ce969a7 Kostas Papadimitriou
            // save the request date to use it as a changes-since value
179 9ce969a7 Kostas Papadimitriou
            // for opera because we are not able to determine
180 9ce969a7 Kostas Papadimitriou
            // response date header for 304 requests
181 9ce969a7 Kostas Papadimitriou
            if (handler_type == "beforeSend" && $.browser.opera) {
182 9ce969a7 Kostas Papadimitriou
                this.date_send = new Date;
183 9ce969a7 Kostas Papadimitriou
            }
184 9ce969a7 Kostas Papadimitriou
185 85f1cd1e Kostas Papadimitriou
            if (handler_type == "beforeSend") {
186 afe9ca75 Kostas Papadimitriou
                arguments[0].setRequestHeader('X-Auth-Token', 
187 afe9ca75 Kostas Papadimitriou
                                              synnefo.user.get_token());
188 85f1cd1e Kostas Papadimitriou
            }
189 85f1cd1e Kostas Papadimitriou
190 9ce969a7 Kostas Papadimitriou
            // error with status code 0 in opera
191 9ce969a7 Kostas Papadimitriou
            // act as 304 response
192 9ce969a7 Kostas Papadimitriou
            if (handler_type == "error" && $.browser.opera) {
193 9ce969a7 Kostas Papadimitriou
                if (arguments[0].status === 0 && arguments[1] === "error") {
194 9ce969a7 Kostas Papadimitriou
                    arguments[0].status = 304;
195 9ce969a7 Kostas Papadimitriou
                    arguments[1] = "notmodified";
196 9ce969a7 Kostas Papadimitriou
                    response_type = "success";
197 9ce969a7 Kostas Papadimitriou
                    xhr = arguments[0];
198 9ce969a7 Kostas Papadimitriou
                }
199 9ce969a7 Kostas Papadimitriou
            }
200 9ce969a7 Kostas Papadimitriou
            
201 9ce969a7 Kostas Papadimitriou
            // add error in api errors registry
202 9ce969a7 Kostas Papadimitriou
            // api errors registry will be sent
203 9ce969a7 Kostas Papadimitriou
            // if user reports an error using feedback form
204 9ce969a7 Kostas Papadimitriou
            if (handler_type == "error") {
205 ec511098 Kostas Papadimitriou
                // skip logging requested ?
206 ec511098 Kostas Papadimitriou
                // if not log this error
207 ec511098 Kostas Papadimitriou
                if (this.log_error !== false) {
208 ec511098 Kostas Papadimitriou
                    add_api_error(this, arguments);
209 ec511098 Kostas Papadimitriou
                }
210 8d08f18a Kostas Papadimitriou
            }
211 6a3a5bf7 Kostas Papadimitriou
            
212 9ce969a7 Kostas Papadimitriou
            // identify response status
213 6a3a5bf7 Kostas Papadimitriou
            var status = 304;
214 6a3a5bf7 Kostas Papadimitriou
            if (arguments[0]) {
215 9ce969a7 Kostas Papadimitriou
                status = arguments[0].status;
216 6a3a5bf7 Kostas Papadimitriou
            }
217 8d08f18a Kostas Papadimitriou
            
218 9ce969a7 Kostas Papadimitriou
            // identify aborted request
219 8d08f18a Kostas Papadimitriou
            try {
220 8d08f18a Kostas Papadimitriou
                if (args[1] === "abort") {
221 8d08f18a Kostas Papadimitriou
                    api.trigger("abort");
222 8d08f18a Kostas Papadimitriou
                    return;
223 8d08f18a Kostas Papadimitriou
                }
224 8d08f18a Kostas Papadimitriou
            } catch(error) {
225 8d08f18a Kostas Papadimitriou
                console.error("error aborting", error);
226 8d08f18a Kostas Papadimitriou
            }
227 9ce969a7 Kostas Papadimitriou
            
228 9ce969a7 Kostas Papadimitriou
            // try to set the last request date
229 9ce969a7 Kostas Papadimitriou
            // only for notmodified or succeed responses
230 8d08f18a Kostas Papadimitriou
            try {
231 8d08f18a Kostas Papadimitriou
                // identify xhr object
232 9ce969a7 Kostas Papadimitriou
                xhr = xhr || args[2];
233 9ce969a7 Kostas Papadimitriou
                
234 9ce969a7 Kostas Papadimitriou
                // not modified response
235 9ce969a7 Kostas Papadimitriou
                if (args[1] === "notmodified") {
236 9ce969a7 Kostas Papadimitriou
                    if (xhr) {
237 9ce969a7 Kostas Papadimitriou
                        // use date_send if exists (opera browser)
238 9ce969a7 Kostas Papadimitriou
                        var d = this.date_send || xhr.getResponseHeader('Date');
239 9ce969a7 Kostas Papadimitriou
                        if (d) { addApiCallDate(this.url, new Date(d), ajax_options.type); };
240 8d08f18a Kostas Papadimitriou
                    }
241 aeadb7bf Kostas Papadimitriou
242 8d08f18a Kostas Papadimitriou
                    return;
243 8d08f18a Kostas Papadimitriou
                }
244 8d08f18a Kostas Papadimitriou
                
245 9ce969a7 Kostas Papadimitriou
                // success response
246 9ce969a7 Kostas Papadimitriou
                if (args[1] == "success" && handler_type == "success") {
247 9ce969a7 Kostas Papadimitriou
                    try {
248 9ce969a7 Kostas Papadimitriou
                        // use date_send if exists (opera browser)
249 9ce969a7 Kostas Papadimitriou
                        var d = this.date_send || args[2].getResponseHeader('Date');
250 9ce969a7 Kostas Papadimitriou
                        if (d) { addApiCallDate(this.url, new Date(d), ajax_options.type); };
251 9ce969a7 Kostas Papadimitriou
                    } catch (err) {
252 9ce969a7 Kostas Papadimitriou
                        console.error(err)
253 9ce969a7 Kostas Papadimitriou
                    }
254 9ce969a7 Kostas Papadimitriou
                }
255 8d08f18a Kostas Papadimitriou
            } catch (err) {
256 8d08f18a Kostas Papadimitriou
                console.error(err);
257 8d08f18a Kostas Papadimitriou
            }
258 9ce969a7 Kostas Papadimitriou
            
259 9ce969a7 Kostas Papadimitriou
            // dont call error callback for non modified responses
260 9ce969a7 Kostas Papadimitriou
            if (arguments[1] === "notmodified") {
261 9ce969a7 Kostas Papadimitriou
                return;
262 9ce969a7 Kostas Papadimitriou
            }
263 aeadb7bf Kostas Papadimitriou
264 aeadb7bf Kostas Papadimitriou
            if (["beforeSend", "complete"].indexOf(cb_type) == -1 && this.is_recurrent) {
265 aeadb7bf Kostas Papadimitriou
                // trigger event to notify that a recurrent event
266 aeadb7bf Kostas Papadimitriou
                // has returned status other than notmodified
267 aeadb7bf Kostas Papadimitriou
                snf.api.trigger("change:recurrent");
268 aeadb7bf Kostas Papadimitriou
            }
269 9ce969a7 Kostas Papadimitriou
            
270 9ce969a7 Kostas Papadimitriou
            // prepare arguments for error callbacks
271 9ce969a7 Kostas Papadimitriou
            var cb_args = _.toArray(arguments);
272 9ce969a7 Kostas Papadimitriou
            if (handler_type === "error") {
273 9ce969a7 Kostas Papadimitriou
                cb_args.push(_.clone(this));
274 9ce969a7 Kostas Papadimitriou
            }
275 9ce969a7 Kostas Papadimitriou
            
276 9ce969a7 Kostas Papadimitriou
            // determine if we need to call our callback wrapper
277 9ce969a7 Kostas Papadimitriou
            var call_api_handler = true;
278 c72a830d Kostas Papadimitriou
            
279 9ce969a7 Kostas Papadimitriou
            // request handles errors by itself, s
280 9ce969a7 Kostas Papadimitriou
            if (handler_type == "error" && this.skip_api_error) {
281 9ce969a7 Kostas Papadimitriou
                call_api_handler = false
282 9ce969a7 Kostas Papadimitriou
            }
283 9ce969a7 Kostas Papadimitriou
284 9ce969a7 Kostas Papadimitriou
            // aborted request, don't call error handler
285 9ce969a7 Kostas Papadimitriou
            if (handler_type === "error" && args[1] === "abort") {
286 9ce969a7 Kostas Papadimitriou
                call_api_handler = false;
287 9ce969a7 Kostas Papadimitriou
            }
288 9ce969a7 Kostas Papadimitriou
            
289 9ce969a7 Kostas Papadimitriou
            // reset api call date, next call will be sent without changes-since
290 9ce969a7 Kostas Papadimitriou
            // parameter set
291 9ce969a7 Kostas Papadimitriou
            if (handler_type === "error") {
292 9ce969a7 Kostas Papadimitriou
                if (args[1] === "error") {
293 9ce969a7 Kostas Papadimitriou
                    clearApiCallDate(this.url, this.type);
294 9ce969a7 Kostas Papadimitriou
                }
295 9ce969a7 Kostas Papadimitriou
            }
296 9ce969a7 Kostas Papadimitriou
            
297 9ce969a7 Kostas Papadimitriou
            // call api call back and retrieve params to
298 9ce969a7 Kostas Papadimitriou
            // be passed to the callback method set for
299 9ce969a7 Kostas Papadimitriou
            // this type of response
300 9ce969a7 Kostas Papadimitriou
            if (call_api_handler) {
301 9ce969a7 Kostas Papadimitriou
                cb_args = wrap.apply(this, cb_args);
302 9ce969a7 Kostas Papadimitriou
            }
303 9ce969a7 Kostas Papadimitriou
            
304 9ce969a7 Kostas Papadimitriou
            // call requested callback
305 9ce969a7 Kostas Papadimitriou
            method.apply(this, _.toArray(cb_args));
306 8d08f18a Kostas Papadimitriou
        }
307 8d08f18a Kostas Papadimitriou
    }
308 8d08f18a Kostas Papadimitriou
309 8d08f18a Kostas Papadimitriou
    api.successHandler = function(data, status, xhr) {
310 8d08f18a Kostas Papadimitriou
        //debug("ajax success", arguments)
311 8d08f18a Kostas Papadimitriou
        // on success, update the last date we called the api url
312 8d08f18a Kostas Papadimitriou
        return [data, status, xhr];
313 8d08f18a Kostas Papadimitriou
    }
314 8d08f18a Kostas Papadimitriou
315 8d08f18a Kostas Papadimitriou
    api.errorHandler = function(event, xhr, settings, error) {
316 e472ed67 Kostas Papadimitriou
        // dont trigger api error until timeouts occured
317 9ce969a7 Kostas Papadimitriou
        // exceed the skips_timeouts limit
318 9ce969a7 Kostas Papadimitriou
        //
319 9ce969a7 Kostas Papadimitriou
        // check only requests with skips_timeouts option set
320 aeadb7bf Kostas Papadimitriou
        
321 9ce969a7 Kostas Papadimitriou
        if (xhr === "timeout" && _.last(arguments).skips_timeouts) {
322 9ce969a7 Kostas Papadimitriou
            var skip_timeouts = snf.config.skip_timeouts || 1;
323 9ce969a7 Kostas Papadimitriou
            if (snf.api.timeouts_occured < skip_timeouts) {
324 9ce969a7 Kostas Papadimitriou
                snf.api.timeouts_occured++;
325 9ce969a7 Kostas Papadimitriou
                return;
326 9ce969a7 Kostas Papadimitriou
            } else {
327 40f6b82f Kostas Papadimitriou
                // reset trigger error
328 9ce969a7 Kostas Papadimitriou
                snf.api.timeouts_occured = 0;
329 40f6b82f Kostas Papadimitriou
                var args = _.toArray(arguments);
330 40f6b82f Kostas Papadimitriou
                api.trigger("error", args);
331 9ce969a7 Kostas Papadimitriou
            }
332 8d08f18a Kostas Papadimitriou
        }
333 09f90e6e Kostas Papadimitriou
334 09f90e6e Kostas Papadimitriou
        // if error occured and changes-since is set for the request
335 09f90e6e Kostas Papadimitriou
        // skip triggering the error and try again without the changes-since
336 09f90e6e Kostas Papadimitriou
        // parameter set
337 09f90e6e Kostas Papadimitriou
        var url = snf.util.parseUri(this.url);
338 09f90e6e Kostas Papadimitriou
        if (url.query.indexOf("changes-since") > -1) {
339 09f90e6e Kostas Papadimitriou
            clearApiCallDate(this.url, this.type);
340 09f90e6e Kostas Papadimitriou
            return _.toArray(arguments);
341 09f90e6e Kostas Papadimitriou
        }
342 9ce969a7 Kostas Papadimitriou
    
343 9ce969a7 Kostas Papadimitriou
        // skip aborts, notmodified (opera)
344 9ce969a7 Kostas Papadimitriou
        if (xhr === "error" || xhr === "timeout") {
345 9ce969a7 Kostas Papadimitriou
            var args = _.toArray(arguments);
346 9ce969a7 Kostas Papadimitriou
            api.trigger("error", args);
347 9ce969a7 Kostas Papadimitriou
        }
348 9ce969a7 Kostas Papadimitriou
349 9ce969a7 Kostas Papadimitriou
        return _.toArray(arguments);
350 8d08f18a Kostas Papadimitriou
    }
351 8d08f18a Kostas Papadimitriou
352 8d08f18a Kostas Papadimitriou
    api.completeHandler = function(xhr, status) {
353 8d08f18a Kostas Papadimitriou
        //debug("ajax complete", arguments)
354 8d08f18a Kostas Papadimitriou
        return arguments;
355 8d08f18a Kostas Papadimitriou
    }
356 8d08f18a Kostas Papadimitriou
357 8d08f18a Kostas Papadimitriou
    api.beforeSendHandler = function(xhr, settings) {
358 8d08f18a Kostas Papadimitriou
        //debug("ajax beforeSend", arguments)
359 8d08f18a Kostas Papadimitriou
        // ajax settings
360 8d08f18a Kostas Papadimitriou
        var ajax_settings = this;
361 8d08f18a Kostas Papadimitriou
        return arguments;
362 8d08f18a Kostas Papadimitriou
    }
363 8d08f18a Kostas Papadimitriou
364 9ce969a7 Kostas Papadimitriou
    // api call helper
365 9ce969a7 Kostas Papadimitriou
    api.call = function(url, method, data, complete, error, success, options) {
366 8d08f18a Kostas Papadimitriou
            var self = this;
367 8d08f18a Kostas Papadimitriou
            error = error || function(){};
368 8d08f18a Kostas Papadimitriou
            success = success || function(){};
369 8d08f18a Kostas Papadimitriou
            complete = complete || function(){};
370 adab5d39 Kostas Papadimitriou
            var extra = data ? data._options || {} : {};
371 bedfe884 Kostas Papadimitriou
372 9ce969a7 Kostas Papadimitriou
            // really ugly way to pass sync request options.
373 9ce969a7 Kostas Papadimitriou
            // it works though....
374 9ce969a7 Kostas Papadimitriou
            if (data && data._options) { delete data['_options'] };
375 9ce969a7 Kostas Papadimitriou
            
376 87a459ff Kostas Papadimitriou
            var base_url = snf.config.api_urls[this.api_type];
377 87a459ff Kostas Papadimitriou
            var join = '/';
378 87a459ff Kostas Papadimitriou
            // do not append trailling slash if already exists in url
379 87a459ff Kostas Papadimitriou
            if (base_url[base_url.length - 1] == join) { join = '' }
380 87a459ff Kostas Papadimitriou
381 9ce969a7 Kostas Papadimitriou
            // prepare the params
382 8d08f18a Kostas Papadimitriou
            var params = {
383 87a459ff Kostas Papadimitriou
                url: base_url + join + url,
384 8d08f18a Kostas Papadimitriou
                data: data,
385 8d08f18a Kostas Papadimitriou
                success: success,
386 8d08f18a Kostas Papadimitriou
                complete: function() { api.trigger("call"); complete(this) },
387 8d08f18a Kostas Papadimitriou
                error: error
388 8d08f18a Kostas Papadimitriou
            }
389 f06ec46e Kostas Papadimitriou
390 9ce969a7 Kostas Papadimitriou
            params = _.extend(params, extra, options);
391 8d08f18a Kostas Papadimitriou
            this.sync(method, this, params);
392 8d08f18a Kostas Papadimitriou
        },
393 8d08f18a Kostas Papadimitriou
394 8d08f18a Kostas Papadimitriou
    _.extend(api, bb.Events);
395 8d08f18a Kostas Papadimitriou
    
396 9ce969a7 Kostas Papadimitriou
    // helper for callbacks that need to get called
397 9ce969a7 Kostas Papadimitriou
    // in fixed intervals
398 8d08f18a Kostas Papadimitriou
    api.updateHandler = function(options) {
399 8d08f18a Kostas Papadimitriou
        this.cb = options.callback;
400 258235f4 Kostas Papadimitriou
        this.handler_id = options.id;
401 8d08f18a Kostas Papadimitriou
402 aeadb7bf Kostas Papadimitriou
        // the interval with which we start
403 aeadb7bf Kostas Papadimitriou
        this.interval = this.normal_interval = options.interval || 4000;
404 8d08f18a Kostas Papadimitriou
405 aeadb7bf Kostas Papadimitriou
        // fast interval
406 aeadb7bf Kostas Papadimitriou
        // set when faster() gets called
407 aeadb7bf Kostas Papadimitriou
        this.fast_interval = options.fast || 1000;
408 aeadb7bf Kostas Papadimitriou
    
409 aeadb7bf Kostas Papadimitriou
        // after how many calls to increase the interval
410 1cc6e94f Kostas Papadimitriou
        this.interval_increase_count = options.increase_after_calls || 0;
411 aeadb7bf Kostas Papadimitriou
412 aeadb7bf Kostas Papadimitriou
        // increase the timer by this value after interval_increase_count calls
413 aeadb7bf Kostas Papadimitriou
        this.interval_increase = options.increase || 500;
414 aeadb7bf Kostas Papadimitriou
        
415 aeadb7bf Kostas Papadimitriou
        // maximum interval limit
416 aeadb7bf Kostas Papadimitriou
        this.maximum_interval = options.max || 60000;
417 aeadb7bf Kostas Papadimitriou
        
418 aeadb7bf Kostas Papadimitriou
        // make a call before interval starts
419 aeadb7bf Kostas Papadimitriou
        this.call_on_start = options.initial_call === undefined ? true : options.initial_call;
420 aeadb7bf Kostas Papadimitriou
            
421 aeadb7bf Kostas Papadimitriou
        this.increase_enabled = this.interval_increase_count === 0;
422 edd1d565 Kostas Papadimitriou
423 aeadb7bf Kostas Papadimitriou
        if (this.increase_enabled) {
424 aeadb7bf Kostas Papadimitriou
            this.maximum_interval = this.interval;
425 aeadb7bf Kostas Papadimitriou
            this.interval_increase_count = 1;
426 aeadb7bf Kostas Papadimitriou
        }
427 aeadb7bf Kostas Papadimitriou
        
428 aeadb7bf Kostas Papadimitriou
        // inner params
429 aeadb7bf Kostas Papadimitriou
        this._called = 0;
430 aeadb7bf Kostas Papadimitriou
        this._first_call_date = undefined;
431 aeadb7bf Kostas Papadimitriou
        this.window_interval = undefined;
432 aeadb7bf Kostas Papadimitriou
        
433 aeadb7bf Kostas Papadimitriou
        // state params
434 edd1d565 Kostas Papadimitriou
        this.running = false;
435 b1e6a2de Kostas Papadimitriou
        this.last_call = false;
436 8d08f18a Kostas Papadimitriou
        
437 aeadb7bf Kostas Papadimitriou
        // helper for api calls
438 aeadb7bf Kostas Papadimitriou
        // TODO: move this out of here :/
439 aeadb7bf Kostas Papadimitriou
        if (options.is_recurrent) {
440 aeadb7bf Kostas Papadimitriou
            snf.api.bind("change:recurrent", _.bind(function() {
441 40f6b82f Kostas Papadimitriou
                if (this.running) {
442 40f6b82f Kostas Papadimitriou
                    this.faster(true);
443 40f6b82f Kostas Papadimitriou
                }
444 aeadb7bf Kostas Papadimitriou
            }, this));
445 aeadb7bf Kostas Papadimitriou
        }
446 aeadb7bf Kostas Papadimitriou
        
447 aeadb7bf Kostas Papadimitriou
        // callback wrapper
448 258235f4 Kostas Papadimitriou
        this._cb = function() {
449 40f6b82f Kostas Papadimitriou
            if (!this.running) { this.stop() }
450 aeadb7bf Kostas Papadimitriou
            if (this._called >= this.interval_increase_count) {
451 aeadb7bf Kostas Papadimitriou
                this._called = 0;
452 aeadb7bf Kostas Papadimitriou
                this.slower(false);
453 8d08f18a Kostas Papadimitriou
            }
454 aeadb7bf Kostas Papadimitriou
            
455 8d08f18a Kostas Papadimitriou
            this.cb();
456 b1e6a2de Kostas Papadimitriou
            this.last_call = new Date;
457 aeadb7bf Kostas Papadimitriou
            this._called++;
458 8d08f18a Kostas Papadimitriou
        };
459 edd1d565 Kostas Papadimitriou
460 aeadb7bf Kostas Papadimitriou
        // start from faster timeout and start increasing
461 35855ff5 Kostas Papadimitriou
        this.faster = function(do_call) {
462 aeadb7bf Kostas Papadimitriou
            if (!this.running) { return }
463 1cc6e94f Kostas Papadimitriou
464 aeadb7bf Kostas Papadimitriou
            this.interval = this.fast_interval;
465 35855ff5 Kostas Papadimitriou
            this.setInterval(do_call);
466 8d08f18a Kostas Papadimitriou
        }
467 8d08f18a Kostas Papadimitriou
468 aeadb7bf Kostas Papadimitriou
        // slow down
469 aeadb7bf Kostas Papadimitriou
        this.slower = function(do_call) {
470 aeadb7bf Kostas Papadimitriou
            if (this.interval == this.maximum_interval) {
471 aeadb7bf Kostas Papadimitriou
                // no need to increase
472 aeadb7bf Kostas Papadimitriou
                return;
473 aeadb7bf Kostas Papadimitriou
            }
474 aeadb7bf Kostas Papadimitriou
            
475 1cc6e94f Kostas Papadimitriou
            this.interval = this.interval + this.interval_increase;
476 aeadb7bf Kostas Papadimitriou
            // increase timeout
477 aeadb7bf Kostas Papadimitriou
            if (this.interval > this.maximum_interval) {
478 aeadb7bf Kostas Papadimitriou
                this.interval = this.maximum_interval;
479 aeadb7bf Kostas Papadimitriou
            }
480 aeadb7bf Kostas Papadimitriou
            
481 aeadb7bf Kostas Papadimitriou
            this.setInterval(do_call);
482 aeadb7bf Kostas Papadimitriou
        }
483 aeadb7bf Kostas Papadimitriou
        
484 aeadb7bf Kostas Papadimitriou
        // reset internal
485 35855ff5 Kostas Papadimitriou
        this.setInterval = function(do_call) {
486 8d08f18a Kostas Papadimitriou
            this.trigger("clear");
487 b1e6a2de Kostas Papadimitriou
            
488 aeadb7bf Kostas Papadimitriou
            // reset times called
489 aeadb7bf Kostas Papadimitriou
            this._called = 0;
490 aeadb7bf Kostas Papadimitriou
            
491 aeadb7bf Kostas Papadimitriou
            window.clearInterval(this.window_interval);
492 258235f4 Kostas Papadimitriou
            this.window_interval = window.setInterval(_.bind(this._cb, this), this.interval);
493 aeadb7bf Kostas Papadimitriou
494 edd1d565 Kostas Papadimitriou
            this.running = true;
495 b1e6a2de Kostas Papadimitriou
            
496 aeadb7bf Kostas Papadimitriou
            // if no do_call set, fallback to object creation option
497 aeadb7bf Kostas Papadimitriou
            // else force what was requested
498 aeadb7bf Kostas Papadimitriou
            var call = do_call === undefined ? this.call_on_start : do_call;
499 b1e6a2de Kostas Papadimitriou
            
500 aeadb7bf Kostas Papadimitriou
            if (this.last_call && do_call !== false) {
501 aeadb7bf Kostas Papadimitriou
                var next_call = (this.interval - ((new Date) - this.last_call));
502 aeadb7bf Kostas Papadimitriou
                if (next_call < this.interval/2) {
503 b1e6a2de Kostas Papadimitriou
                    call = true;
504 b1e6a2de Kostas Papadimitriou
                } else {
505 b1e6a2de Kostas Papadimitriou
                    call = false;
506 b1e6a2de Kostas Papadimitriou
                }
507 b1e6a2de Kostas Papadimitriou
            }
508 aeadb7bf Kostas Papadimitriou
            
509 b1e6a2de Kostas Papadimitriou
            if (call) {
510 258235f4 Kostas Papadimitriou
                this._cb();
511 8d08f18a Kostas Papadimitriou
            }
512 aeadb7bf Kostas Papadimitriou
513 b1e6a2de Kostas Papadimitriou
            return this;
514 8d08f18a Kostas Papadimitriou
        }
515 8d08f18a Kostas Papadimitriou
516 2c9bfad1 Kostas Papadimitriou
        this.start = function (call_on_start) {
517 b1e6a2de Kostas Papadimitriou
            if (this.running) { this.stop() };
518 aeadb7bf Kostas Papadimitriou
            this.setInterval(call_on_start);
519 b1e6a2de Kostas Papadimitriou
            return this;
520 8d08f18a Kostas Papadimitriou
        }
521 8d08f18a Kostas Papadimitriou
522 8d08f18a Kostas Papadimitriou
        this.stop = function() {
523 8d08f18a Kostas Papadimitriou
            this.trigger("clear");
524 aeadb7bf Kostas Papadimitriou
            window.clearInterval(this.window_interval);
525 edd1d565 Kostas Papadimitriou
            this.running = false;
526 b1e6a2de Kostas Papadimitriou
            return this;
527 8d08f18a Kostas Papadimitriou
        }
528 8d08f18a Kostas Papadimitriou
    }
529 8d08f18a Kostas Papadimitriou
    
530 9ce969a7 Kostas Papadimitriou
    // api error state
531 8d08f18a Kostas Papadimitriou
    api.stop_calls = false;
532 9ce969a7 Kostas Papadimitriou
    api.STATES = { NORMAL:1, WARN:0, ERROR:-1 };
533 9ce969a7 Kostas Papadimitriou
    api.error_state = api.STATES.NORMAL;
534 9ce969a7 Kostas Papadimitriou
535 9ce969a7 Kostas Papadimitriou
    // on api error update the api error_state
536 9ce969a7 Kostas Papadimitriou
    api.bind("error", function() {
537 9ffd10ce Kostas Papadimitriou
        if (snf.api.error_state == snf.api.STATES.ERROR) { return };
538 9ffd10ce Kostas Papadimitriou
539 9ce969a7 Kostas Papadimitriou
        var args = _.toArray(_.toArray(arguments)[0]);
540 9ce969a7 Kostas Papadimitriou
        var params = _.last(args);
541 9ce969a7 Kostas Papadimitriou
        
542 9ce969a7 Kostas Papadimitriou
        if (params.critical) {
543 9ce969a7 Kostas Papadimitriou
            snf.api.error_state = api.STATES.ERROR;
544 9ce969a7 Kostas Papadimitriou
            snf.api.stop_calls = true;
545 9ce969a7 Kostas Papadimitriou
        } else {
546 9ffd10ce Kostas Papadimitriou
            snf.api.error_state = api.STATES.ERROR;
547 9ce969a7 Kostas Papadimitriou
        }
548 9ce969a7 Kostas Papadimitriou
        snf.api.trigger("change:error_state", snf.api.error_state);
549 9ce969a7 Kostas Papadimitriou
    });
550 9ce969a7 Kostas Papadimitriou
    
551 9ce969a7 Kostas Papadimitriou
    // reset api error state
552 9ce969a7 Kostas Papadimitriou
    api.bind("reset", function() {
553 9ce969a7 Kostas Papadimitriou
        snf.api.error_state = api.STATES.NORMAL;
554 9ce969a7 Kostas Papadimitriou
        snf.api.stop_calls = false;
555 9ce969a7 Kostas Papadimitriou
        snf.api.trigger("change:error_state", snf.api.error_state);
556 9ce969a7 Kostas Papadimitriou
    })
557 8d08f18a Kostas Papadimitriou
558 8d08f18a Kostas Papadimitriou
    // make it eventable
559 8d08f18a Kostas Papadimitriou
    _.extend(api.updateHandler.prototype, bb.Events);
560 8d08f18a Kostas Papadimitriou
    
561 8d08f18a Kostas Papadimitriou
})(this);