2150 |
mathias |
1 |
/*
|
|
|
2 |
Copyright (c) 2004-2007, The Dojo Foundation
|
|
|
3 |
All Rights Reserved.
|
|
|
4 |
|
|
|
5 |
Licensed under the Academic Free License version 2.1 or above OR the
|
|
|
6 |
modified BSD license. For more information on Dojo licensing, see:
|
|
|
7 |
|
|
|
8 |
http://dojotoolkit.org/book/dojo-book-0-9/introduction/licensing
|
|
|
9 |
*/
|
|
|
10 |
|
|
|
11 |
/*
|
|
|
12 |
This is a compiled version of Dojo, built for deployment and not for
|
|
|
13 |
development. To get an editable version, please visit:
|
|
|
14 |
|
|
|
15 |
http://dojotoolkit.org
|
|
|
16 |
|
|
|
17 |
for documentation and information on getting the source.
|
|
|
18 |
*/
|
|
|
19 |
|
|
|
20 |
if(!dojo._hasResource["dojox.storage.Provider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
21 |
dojo._hasResource["dojox.storage.Provider"] = true;
|
|
|
22 |
dojo.provide("dojox.storage.Provider");
|
|
|
23 |
|
|
|
24 |
dojo.declare("dojox.storage.Provider", null, {
|
|
|
25 |
// summary: A singleton for working with dojox.storage.
|
|
|
26 |
// description:
|
|
|
27 |
// dojox.storage exposes the current available storage provider on this
|
|
|
28 |
// platform. It gives you methods such as dojox.storage.put(),
|
|
|
29 |
// dojox.storage.get(), etc.
|
|
|
30 |
//
|
|
|
31 |
// For more details on dojox.storage, see the primary documentation
|
|
|
32 |
// page at
|
|
|
33 |
// http://manual.dojotoolkit.org/storage.html
|
|
|
34 |
//
|
|
|
35 |
// Note for storage provider developers who are creating subclasses-
|
|
|
36 |
// This is the base class for all storage providers Specific kinds of
|
|
|
37 |
// Storage Providers should subclass this and implement these methods.
|
|
|
38 |
// You should avoid initialization in storage provider subclass's
|
|
|
39 |
// constructor; instead, perform initialization in your initialize()
|
|
|
40 |
// method.
|
|
|
41 |
constructor: function(){
|
|
|
42 |
},
|
|
|
43 |
|
|
|
44 |
// SUCCESS: String
|
|
|
45 |
// Flag that indicates a put() call to a
|
|
|
46 |
// storage provider was succesful.
|
|
|
47 |
SUCCESS: "success",
|
|
|
48 |
|
|
|
49 |
// FAILED: String
|
|
|
50 |
// Flag that indicates a put() call to
|
|
|
51 |
// a storage provider failed.
|
|
|
52 |
FAILED: "failed",
|
|
|
53 |
|
|
|
54 |
// PENDING: String
|
|
|
55 |
// Flag that indicates a put() call to a
|
|
|
56 |
// storage provider is pending user approval.
|
|
|
57 |
PENDING: "pending",
|
|
|
58 |
|
|
|
59 |
// SIZE_NOT_AVAILABLE: String
|
|
|
60 |
// Returned by getMaximumSize() if this storage provider can not determine
|
|
|
61 |
// the maximum amount of data it can support.
|
|
|
62 |
SIZE_NOT_AVAILABLE: "Size not available",
|
|
|
63 |
|
|
|
64 |
// SIZE_NO_LIMIT: String
|
|
|
65 |
// Returned by getMaximumSize() if this storage provider has no theoretical
|
|
|
66 |
// limit on the amount of data it can store.
|
|
|
67 |
SIZE_NO_LIMIT: "No size limit",
|
|
|
68 |
|
|
|
69 |
// DEFAULT_NAMESPACE: String
|
|
|
70 |
// The namespace for all storage operations. This is useful if several
|
|
|
71 |
// applications want access to the storage system from the same domain but
|
|
|
72 |
// want different storage silos.
|
|
|
73 |
DEFAULT_NAMESPACE: "default",
|
|
|
74 |
|
|
|
75 |
// onHideSettingsUI: Function
|
|
|
76 |
// If a function is assigned to this property, then when the settings
|
|
|
77 |
// provider's UI is closed this function is called. Useful, for example,
|
|
|
78 |
// if the user has just cleared out all storage for this provider using
|
|
|
79 |
// the settings UI, and you want to update your UI.
|
|
|
80 |
onHideSettingsUI: null,
|
|
|
81 |
|
|
|
82 |
initialize: function(){
|
|
|
83 |
// summary:
|
|
|
84 |
// Allows this storage provider to initialize itself. This is
|
|
|
85 |
// called after the page has finished loading, so you can not do
|
|
|
86 |
// document.writes(). Storage Provider subclasses should initialize
|
|
|
87 |
// themselves inside of here rather than in their function
|
|
|
88 |
// constructor.
|
|
|
89 |
console.warn("dojox.storage.initialize not implemented");
|
|
|
90 |
},
|
|
|
91 |
|
|
|
92 |
isAvailable: function(){ /*Boolean*/
|
|
|
93 |
// summary:
|
|
|
94 |
// Returns whether this storage provider is available on this
|
|
|
95 |
// platform.
|
|
|
96 |
console.warn("dojox.storage.isAvailable not implemented");
|
|
|
97 |
},
|
|
|
98 |
|
|
|
99 |
put: function( /*string*/ key,
|
|
|
100 |
/*object*/ value,
|
|
|
101 |
/*function*/ resultsHandler,
|
|
|
102 |
/*string?*/ namespace){
|
|
|
103 |
// summary:
|
|
|
104 |
// Puts a key and value into this storage system.
|
|
|
105 |
// description:
|
|
|
106 |
// Example-
|
|
|
107 |
// var resultsHandler = function(status, key, message){
|
|
|
108 |
// alert("status="+status+", key="+key+", message="+message);
|
|
|
109 |
// };
|
|
|
110 |
// dojox.storage.put("test", "hello world", resultsHandler);
|
|
|
111 |
//
|
|
|
112 |
// Important note: if you are using Dojo Storage in conjunction with
|
|
|
113 |
// Dojo Offline, then you don't need to provide
|
|
|
114 |
// a resultsHandler; this is because for Dojo Offline we
|
|
|
115 |
// use Google Gears to persist data, which has unlimited data
|
|
|
116 |
// once the user has given permission. If you are using Dojo
|
|
|
117 |
// Storage apart from Dojo Offline, then under the covers hidden
|
|
|
118 |
// Flash might be used, which is both asychronous and which might
|
|
|
119 |
// get denied; in this case you must provide a resultsHandler.
|
|
|
120 |
// key:
|
|
|
121 |
// A string key to use when retrieving this value in the future.
|
|
|
122 |
// value:
|
|
|
123 |
// A value to store; this can be any JavaScript type.
|
|
|
124 |
// resultsHandler:
|
|
|
125 |
// A callback function that will receive three arguments. The
|
|
|
126 |
// first argument is one of three values: dojox.storage.SUCCESS,
|
|
|
127 |
// dojox.storage.FAILED, or dojox.storage.PENDING; these values
|
|
|
128 |
// determine how the put request went. In some storage systems
|
|
|
129 |
// users can deny a storage request, resulting in a
|
|
|
130 |
// dojox.storage.FAILED, while in other storage systems a storage
|
|
|
131 |
// request must wait for user approval, resulting in a
|
|
|
132 |
// dojox.storage.PENDING status until the request is either
|
|
|
133 |
// approved or denied, resulting in another call back with
|
|
|
134 |
// dojox.storage.SUCCESS.
|
|
|
135 |
// The second argument in the call back is the key name that was being stored.
|
|
|
136 |
// The third argument in the call back is an optional message that
|
|
|
137 |
// details possible error messages that might have occurred during
|
|
|
138 |
// the storage process.
|
|
|
139 |
// namespace:
|
|
|
140 |
// Optional string namespace that this value will be placed into;
|
|
|
141 |
// if left off, the value will be placed into dojox.storage.DEFAULT_NAMESPACE
|
|
|
142 |
|
|
|
143 |
console.warn("dojox.storage.put not implemented");
|
|
|
144 |
},
|
|
|
145 |
|
|
|
146 |
get: function(/*string*/ key, /*string?*/ namespace){ /*Object*/
|
|
|
147 |
// summary:
|
|
|
148 |
// Gets the value with the given key. Returns null if this key is
|
|
|
149 |
// not in the storage system.
|
|
|
150 |
// key:
|
|
|
151 |
// A string key to get the value of.
|
|
|
152 |
// namespace:
|
|
|
153 |
// Optional string namespace that this value will be retrieved from;
|
|
|
154 |
// if left off, the value will be retrieved from dojox.storage.DEFAULT_NAMESPACE
|
|
|
155 |
// return: Returns any JavaScript object type; null if the key is not present
|
|
|
156 |
console.warn("dojox.storage.get not implemented");
|
|
|
157 |
},
|
|
|
158 |
|
|
|
159 |
hasKey: function(/*string*/ key, /*string?*/ namespace){ /*Boolean*/
|
|
|
160 |
// summary: Determines whether the storage has the given key.
|
|
|
161 |
return (this.get(key) != null);
|
|
|
162 |
},
|
|
|
163 |
|
|
|
164 |
getKeys: function(/*string?*/ namespace){ /*Array*/
|
|
|
165 |
// summary: Enumerates all of the available keys in this storage system.
|
|
|
166 |
// return: Array of available keys
|
|
|
167 |
console.warn("dojox.storage.getKeys not implemented");
|
|
|
168 |
},
|
|
|
169 |
|
|
|
170 |
clear: function(/*string?*/ namespace){
|
|
|
171 |
// summary:
|
|
|
172 |
// Completely clears this storage system of all of it's values and
|
|
|
173 |
// keys. If 'namespace' is provided just clears the keys in that
|
|
|
174 |
// namespace.
|
|
|
175 |
console.warn("dojox.storage.clear not implemented");
|
|
|
176 |
},
|
|
|
177 |
|
|
|
178 |
remove: function(/*string*/ key, /*string?*/ namespace){
|
|
|
179 |
// summary: Removes the given key from this storage system.
|
|
|
180 |
console.warn("dojox.storage.remove not implemented");
|
|
|
181 |
},
|
|
|
182 |
|
|
|
183 |
getNamespaces: function(){ /*string[]*/
|
|
|
184 |
console.warn("dojox.storage.getNamespaces not implemented");
|
|
|
185 |
},
|
|
|
186 |
|
|
|
187 |
isPermanent: function(){ /*Boolean*/
|
|
|
188 |
// summary:
|
|
|
189 |
// Returns whether this storage provider's values are persisted
|
|
|
190 |
// when this platform is shutdown.
|
|
|
191 |
console.warn("dojox.storage.isPermanent not implemented");
|
|
|
192 |
},
|
|
|
193 |
|
|
|
194 |
getMaximumSize: function(){ /* mixed */
|
|
|
195 |
// summary: The maximum storage allowed by this provider
|
|
|
196 |
// returns:
|
|
|
197 |
// Returns the maximum storage size
|
|
|
198 |
// supported by this provider, in
|
|
|
199 |
// thousands of bytes (i.e., if it
|
|
|
200 |
// returns 60 then this means that 60K
|
|
|
201 |
// of storage is supported).
|
|
|
202 |
//
|
|
|
203 |
// If this provider can not determine
|
|
|
204 |
// it's maximum size, then
|
|
|
205 |
// dojox.storage.SIZE_NOT_AVAILABLE is
|
|
|
206 |
// returned; if there is no theoretical
|
|
|
207 |
// limit on the amount of storage
|
|
|
208 |
// this provider can return, then
|
|
|
209 |
// dojox.storage.SIZE_NO_LIMIT is
|
|
|
210 |
// returned
|
|
|
211 |
console.warn("dojox.storage.getMaximumSize not implemented");
|
|
|
212 |
},
|
|
|
213 |
|
|
|
214 |
putMultiple: function( /*array*/ keys,
|
|
|
215 |
/*array*/ values,
|
|
|
216 |
/*function*/ resultsHandler,
|
|
|
217 |
/*string?*/ namespace){
|
|
|
218 |
// summary:
|
|
|
219 |
// Puts multiple keys and values into this storage system.
|
|
|
220 |
// description:
|
|
|
221 |
// Example-
|
|
|
222 |
// var resultsHandler = function(status, key, message){
|
|
|
223 |
// alert("status="+status+", key="+key+", message="+message);
|
|
|
224 |
// };
|
|
|
225 |
// dojox.storage.put(["test"], ["hello world"], resultsHandler);
|
|
|
226 |
//
|
|
|
227 |
// Important note: if you are using Dojo Storage in conjunction with
|
|
|
228 |
// Dojo Offline, then you don't need to provide
|
|
|
229 |
// a resultsHandler; this is because for Dojo Offline we
|
|
|
230 |
// use Google Gears to persist data, which has unlimited data
|
|
|
231 |
// once the user has given permission. If you are using Dojo
|
|
|
232 |
// Storage apart from Dojo Offline, then under the covers hidden
|
|
|
233 |
// Flash might be used, which is both asychronous and which might
|
|
|
234 |
// get denied; in this case you must provide a resultsHandler.
|
|
|
235 |
// keys:
|
|
|
236 |
// An array of string keys to use when retrieving this value in the future,
|
|
|
237 |
// one per value to be stored
|
|
|
238 |
// values:
|
|
|
239 |
// An array of values to store; this can be any JavaScript type, though the
|
|
|
240 |
// performance of plain strings is considerably better
|
|
|
241 |
// resultsHandler:
|
|
|
242 |
// A callback function that will receive three arguments. The
|
|
|
243 |
// first argument is one of three values: dojox.storage.SUCCESS,
|
|
|
244 |
// dojox.storage.FAILED, or dojox.storage.PENDING; these values
|
|
|
245 |
// determine how the put request went. In some storage systems
|
|
|
246 |
// users can deny a storage request, resulting in a
|
|
|
247 |
// dojox.storage.FAILED, while in other storage systems a storage
|
|
|
248 |
// request must wait for user approval, resulting in a
|
|
|
249 |
// dojox.storage.PENDING status until the request is either
|
|
|
250 |
// approved or denied, resulting in another call back with
|
|
|
251 |
// dojox.storage.SUCCESS.
|
|
|
252 |
// The second argument in the call back is the key name that was being stored.
|
|
|
253 |
// The third argument in the call back is an optional message that
|
|
|
254 |
// details possible error messages that might have occurred during
|
|
|
255 |
// the storage process.
|
|
|
256 |
// namespace:
|
|
|
257 |
// Optional string namespace that this value will be placed into;
|
|
|
258 |
// if left off, the value will be placed into dojox.storage.DEFAULT_NAMESPACE
|
|
|
259 |
|
|
|
260 |
console.warn("dojox.storage.putMultiple not implemented");
|
|
|
261 |
// JAC: We could implement a 'default' puMultiple here by just doing each put individually
|
|
|
262 |
},
|
|
|
263 |
|
|
|
264 |
getMultiple: function(/*array*/ keys, /*string?*/ namespace){ /*Object*/
|
|
|
265 |
// summary:
|
|
|
266 |
// Gets the valuse corresponding to each of the given keys.
|
|
|
267 |
// Returns a null array element for each given key that is
|
|
|
268 |
// not in the storage system.
|
|
|
269 |
// keys:
|
|
|
270 |
// An array of string keys to get the value of.
|
|
|
271 |
// namespace:
|
|
|
272 |
// Optional string namespace that this value will be retrieved from;
|
|
|
273 |
// if left off, the value will be retrieved from dojox.storage.DEFAULT_NAMESPACE
|
|
|
274 |
// return: Returns any JavaScript object type; null if the key is not present
|
|
|
275 |
|
|
|
276 |
console.warn("dojox.storage.getMultiple not implemented");
|
|
|
277 |
// JAC: We could implement a 'default' getMultiple here by just doing each get individually
|
|
|
278 |
},
|
|
|
279 |
|
|
|
280 |
removeMultiple: function(/*array*/ keys, /*string?*/ namespace) {
|
|
|
281 |
// summary: Removes the given keys from this storage system.
|
|
|
282 |
|
|
|
283 |
// JAC: We could implement a 'default' removeMultiple here by just doing each remove individually
|
|
|
284 |
console.warn("dojox.storage.remove not implemented");
|
|
|
285 |
},
|
|
|
286 |
|
|
|
287 |
isValidKeyArray: function( keys) {
|
|
|
288 |
if(keys === null || typeof keys === "undefined" || ! keys instanceof Array){
|
|
|
289 |
return false;
|
|
|
290 |
}
|
|
|
291 |
|
|
|
292 |
// JAC: This could be optimized by running the key validity test directly over a joined string
|
|
|
293 |
for(var k=0;k<keys.length;k++){
|
|
|
294 |
if(!this.isValidKey(keys[k])){
|
|
|
295 |
return false;
|
|
|
296 |
}
|
|
|
297 |
}
|
|
|
298 |
return true;
|
|
|
299 |
},
|
|
|
300 |
|
|
|
301 |
hasSettingsUI: function(){ /*Boolean*/
|
|
|
302 |
// summary: Determines whether this provider has a settings UI.
|
|
|
303 |
return false;
|
|
|
304 |
},
|
|
|
305 |
|
|
|
306 |
showSettingsUI: function(){
|
|
|
307 |
// summary: If this provider has a settings UI, determined
|
|
|
308 |
// by calling hasSettingsUI(), it is shown.
|
|
|
309 |
console.warn("dojox.storage.showSettingsUI not implemented");
|
|
|
310 |
},
|
|
|
311 |
|
|
|
312 |
hideSettingsUI: function(){
|
|
|
313 |
// summary: If this provider has a settings UI, hides it.
|
|
|
314 |
console.warn("dojox.storage.hideSettingsUI not implemented");
|
|
|
315 |
},
|
|
|
316 |
|
|
|
317 |
isValidKey: function(/*string*/ keyName){ /*Boolean*/
|
|
|
318 |
// summary:
|
|
|
319 |
// Subclasses can call this to ensure that the key given is valid
|
|
|
320 |
// in a consistent way across different storage providers. We use
|
|
|
321 |
// the lowest common denominator for key values allowed: only
|
|
|
322 |
// letters, numbers, and underscores are allowed. No spaces.
|
|
|
323 |
if((keyName == null)||(typeof keyName == "undefined")){
|
|
|
324 |
return false;
|
|
|
325 |
}
|
|
|
326 |
|
|
|
327 |
return /^[0-9A-Za-z_]*$/.test(keyName);
|
|
|
328 |
},
|
|
|
329 |
|
|
|
330 |
getResourceList: function(){ /* Array[] */
|
|
|
331 |
// summary:
|
|
|
332 |
// Returns a list of URLs that this
|
|
|
333 |
// storage provider might depend on.
|
|
|
334 |
// description:
|
|
|
335 |
// This method returns a list of URLs that this
|
|
|
336 |
// storage provider depends on to do its work.
|
|
|
337 |
// This list is used by the Dojo Offline Toolkit
|
|
|
338 |
// to cache these resources to ensure the machinery
|
|
|
339 |
// used by this storage provider is available offline.
|
|
|
340 |
// What is returned is an array of URLs.
|
|
|
341 |
|
|
|
342 |
return [];
|
|
|
343 |
}
|
|
|
344 |
});
|
|
|
345 |
|
|
|
346 |
}
|
|
|
347 |
|
|
|
348 |
if(!dojo._hasResource["dojox.storage.manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
349 |
dojo._hasResource["dojox.storage.manager"] = true;
|
|
|
350 |
dojo.provide("dojox.storage.manager");
|
|
|
351 |
//dojo.require("dojo.AdapterRegistry");
|
|
|
352 |
// FIXME: refactor this to use an AdapterRegistry
|
|
|
353 |
|
|
|
354 |
dojox.storage.manager = new function(){
|
|
|
355 |
// summary: A singleton class in charge of the dojox.storage system
|
|
|
356 |
// description:
|
|
|
357 |
// Initializes the storage systems and figures out the best available
|
|
|
358 |
// storage options on this platform.
|
|
|
359 |
|
|
|
360 |
// currentProvider: Object
|
|
|
361 |
// The storage provider that was automagically chosen to do storage
|
|
|
362 |
// on this platform, such as dojox.storage.FlashStorageProvider.
|
|
|
363 |
this.currentProvider = null;
|
|
|
364 |
|
|
|
365 |
// available: Boolean
|
|
|
366 |
// Whether storage of some kind is available.
|
|
|
367 |
this.available = false;
|
|
|
368 |
|
|
|
369 |
this._initialized = false;
|
|
|
370 |
|
|
|
371 |
this._providers = [];
|
|
|
372 |
this._onLoadListeners = [];
|
|
|
373 |
|
|
|
374 |
this.initialize = function(){
|
|
|
375 |
// summary:
|
|
|
376 |
// Initializes the storage system and autodetects the best storage
|
|
|
377 |
// provider we can provide on this platform
|
|
|
378 |
this.autodetect();
|
|
|
379 |
};
|
|
|
380 |
|
|
|
381 |
this.register = function(/*string*/ name, /*Object*/ instance){
|
|
|
382 |
// summary:
|
|
|
383 |
// Registers the existence of a new storage provider; used by
|
|
|
384 |
// subclasses to inform the manager of their existence. The
|
|
|
385 |
// storage manager will select storage providers based on
|
|
|
386 |
// their ordering, so the order in which you call this method
|
|
|
387 |
// matters.
|
|
|
388 |
// name:
|
|
|
389 |
// The full class name of this provider, such as
|
|
|
390 |
// "dojox.storage.FlashStorageProvider".
|
|
|
391 |
// instance:
|
|
|
392 |
// An instance of this provider, which we will use to call
|
|
|
393 |
// isAvailable() on.
|
|
|
394 |
this._providers[this._providers.length] = instance; //FIXME: push?
|
|
|
395 |
this._providers[name] = instance; // FIXME: this._providers is an array, not a hash
|
|
|
396 |
};
|
|
|
397 |
|
|
|
398 |
this.setProvider = function(storageClass){
|
|
|
399 |
// summary:
|
|
|
400 |
// Instructs the storageManager to use the given storage class for
|
|
|
401 |
// all storage requests.
|
|
|
402 |
// description:
|
|
|
403 |
// Example-
|
|
|
404 |
// dojox.storage.setProvider(
|
|
|
405 |
// dojox.storage.IEStorageProvider)
|
|
|
406 |
|
|
|
407 |
};
|
|
|
408 |
|
|
|
409 |
this.autodetect = function(){
|
|
|
410 |
// summary:
|
|
|
411 |
// Autodetects the best possible persistent storage provider
|
|
|
412 |
// available on this platform.
|
|
|
413 |
|
|
|
414 |
//console.debug("dojox.storage.manager.autodetect");
|
|
|
415 |
|
|
|
416 |
if(this._initialized){ // already finished
|
|
|
417 |
//console.debug("dojox.storage.manager already initialized; returning");
|
|
|
418 |
return;
|
|
|
419 |
}
|
|
|
420 |
|
|
|
421 |
// a flag to force the storage manager to use a particular
|
|
|
422 |
// storage provider type, such as
|
|
|
423 |
// djConfig = {forceStorageProvider: "dojox.storage.WhatWGStorageProvider"};
|
|
|
424 |
var forceProvider = djConfig["forceStorageProvider"]||false;
|
|
|
425 |
|
|
|
426 |
// go through each provider, seeing if it can be used
|
|
|
427 |
var providerToUse;
|
|
|
428 |
//FIXME: use dojo.some
|
|
|
429 |
for(var i = 0; i < this._providers.length; i++){
|
|
|
430 |
providerToUse = this._providers[i];
|
|
|
431 |
if(forceProvider == providerToUse.declaredClass){
|
|
|
432 |
// still call isAvailable for this provider, since this helps some
|
|
|
433 |
// providers internally figure out if they are available
|
|
|
434 |
// FIXME: This should be refactored since it is non-intuitive
|
|
|
435 |
// that isAvailable() would initialize some state
|
|
|
436 |
providerToUse.isAvailable();
|
|
|
437 |
break;
|
|
|
438 |
}else if(providerToUse.isAvailable()){
|
|
|
439 |
break;
|
|
|
440 |
}
|
|
|
441 |
}
|
|
|
442 |
|
|
|
443 |
if(!providerToUse){ // no provider available
|
|
|
444 |
this._initialized = true;
|
|
|
445 |
this.available = false;
|
|
|
446 |
this.currentProvider = null;
|
|
|
447 |
console.warn("No storage provider found for this platform");
|
|
|
448 |
this.loaded();
|
|
|
449 |
return;
|
|
|
450 |
}
|
|
|
451 |
|
|
|
452 |
// create this provider and mix in it's properties
|
|
|
453 |
// so that developers can do dojox.storage.put rather
|
|
|
454 |
// than dojox.storage.currentProvider.put, for example
|
|
|
455 |
this.currentProvider = providerToUse;
|
|
|
456 |
dojo.mixin(dojox.storage, this.currentProvider);
|
|
|
457 |
|
|
|
458 |
// have the provider initialize itself
|
|
|
459 |
dojox.storage.initialize();
|
|
|
460 |
|
|
|
461 |
this._initialized = true;
|
|
|
462 |
this.available = true;
|
|
|
463 |
};
|
|
|
464 |
|
|
|
465 |
this.isAvailable = function(){ /*Boolean*/
|
|
|
466 |
// summary: Returns whether any storage options are available.
|
|
|
467 |
return this.available;
|
|
|
468 |
};
|
|
|
469 |
|
|
|
470 |
this.addOnLoad = function(func){ /* void */
|
|
|
471 |
// summary:
|
|
|
472 |
// Adds an onload listener to know when Dojo Offline can be used.
|
|
|
473 |
// description:
|
|
|
474 |
// Adds a listener to know when Dojo Offline can be used. This
|
|
|
475 |
// ensures that the Dojo Offline framework is loaded and that the
|
|
|
476 |
// local dojox.storage system is ready to be used. This method is
|
|
|
477 |
// useful if you don't want to have a dependency on Dojo Events
|
|
|
478 |
// when using dojox.storage.
|
|
|
479 |
// func: Function
|
|
|
480 |
// A function to call when Dojo Offline is ready to go
|
|
|
481 |
this._onLoadListeners.push(func);
|
|
|
482 |
|
|
|
483 |
if(this.isInitialized()){
|
|
|
484 |
this._fireLoaded();
|
|
|
485 |
}
|
|
|
486 |
};
|
|
|
487 |
|
|
|
488 |
this.removeOnLoad = function(func){ /* void */
|
|
|
489 |
// summary: Removes the given onLoad listener
|
|
|
490 |
for(var i = 0; i < this._onLoadListeners.length; i++){
|
|
|
491 |
if(func == this._onLoadListeners[i]){
|
|
|
492 |
this._onLoadListeners = this._onLoadListeners.splice(i, 1);
|
|
|
493 |
break;
|
|
|
494 |
}
|
|
|
495 |
}
|
|
|
496 |
};
|
|
|
497 |
|
|
|
498 |
this.isInitialized = function(){ /*Boolean*/
|
|
|
499 |
// summary:
|
|
|
500 |
// Returns whether the storage system is initialized and ready to
|
|
|
501 |
// be used.
|
|
|
502 |
|
|
|
503 |
// FIXME: This should REALLY not be in here, but it fixes a tricky
|
|
|
504 |
// Flash timing bug
|
|
|
505 |
if(this.currentProvider != null
|
|
|
506 |
&& this.currentProvider.declaredClass == "dojox.storage.FlashStorageProvider"
|
|
|
507 |
&& dojox.flash.ready == false){
|
|
|
508 |
return false;
|
|
|
509 |
}else{
|
|
|
510 |
return this._initialized;
|
|
|
511 |
}
|
|
|
512 |
};
|
|
|
513 |
|
|
|
514 |
this.supportsProvider = function(/*string*/ storageClass){ /* Boolean */
|
|
|
515 |
// summary: Determines if this platform supports the given storage provider.
|
|
|
516 |
// description:
|
|
|
517 |
// Example-
|
|
|
518 |
// dojox.storage.manager.supportsProvider(
|
|
|
519 |
// "dojox.storage.InternetExplorerStorageProvider");
|
|
|
520 |
|
|
|
521 |
// construct this class dynamically
|
|
|
522 |
try{
|
|
|
523 |
// dynamically call the given providers class level isAvailable()
|
|
|
524 |
// method
|
|
|
525 |
var provider = eval("new " + storageClass + "()");
|
|
|
526 |
var results = provider.isAvailable();
|
|
|
527 |
if(!results){ return false; }
|
|
|
528 |
return results;
|
|
|
529 |
}catch(e){
|
|
|
530 |
return false;
|
|
|
531 |
}
|
|
|
532 |
};
|
|
|
533 |
|
|
|
534 |
this.getProvider = function(){ /* Object */
|
|
|
535 |
// summary: Gets the current provider
|
|
|
536 |
return this.currentProvider;
|
|
|
537 |
};
|
|
|
538 |
|
|
|
539 |
this.loaded = function(){
|
|
|
540 |
// summary:
|
|
|
541 |
// The storage provider should call this method when it is loaded
|
|
|
542 |
// and ready to be used. Clients who will use the provider will
|
|
|
543 |
// connect to this method to know when they can use the storage
|
|
|
544 |
// system. You can either use dojo.connect to connect to this
|
|
|
545 |
// function, or can use dojox.storage.manager.addOnLoad() to add
|
|
|
546 |
// a listener that does not depend on the dojo.event package.
|
|
|
547 |
// description:
|
|
|
548 |
// Example 1-
|
|
|
549 |
// if(dojox.storage.manager.isInitialized() == false){
|
|
|
550 |
// dojo.connect(dojox.storage.manager, "loaded", TestStorage, "initialize");
|
|
|
551 |
// }else{
|
|
|
552 |
// dojo.connect(dojo, "loaded", TestStorage, "initialize");
|
|
|
553 |
// }
|
|
|
554 |
// Example 2-
|
|
|
555 |
// dojox.storage.manager.addOnLoad(someFunction);
|
|
|
556 |
|
|
|
557 |
|
|
|
558 |
// FIXME: we should just provide a Deferred for this. That way you
|
|
|
559 |
// don't care when this happens or has happened. Deferreds are in Base
|
|
|
560 |
this._fireLoaded();
|
|
|
561 |
};
|
|
|
562 |
|
|
|
563 |
this._fireLoaded = function(){
|
|
|
564 |
//console.debug("dojox.storage.manager._fireLoaded");
|
|
|
565 |
|
|
|
566 |
dojo.forEach(this._onLoadListeners, function(i){
|
|
|
567 |
try{
|
|
|
568 |
i();
|
|
|
569 |
}catch(e){ console.debug(e); }
|
|
|
570 |
});
|
|
|
571 |
};
|
|
|
572 |
|
|
|
573 |
this.getResourceList = function(){
|
|
|
574 |
// summary:
|
|
|
575 |
// Returns a list of whatever resources are necessary for storage
|
|
|
576 |
// providers to work.
|
|
|
577 |
// description:
|
|
|
578 |
// This will return all files needed by all storage providers for
|
|
|
579 |
// this particular environment type. For example, if we are in the
|
|
|
580 |
// browser environment, then this will return the hidden SWF files
|
|
|
581 |
// needed by the FlashStorageProvider, even if we don't need them
|
|
|
582 |
// for the particular browser we are working within. This is meant
|
|
|
583 |
// to faciliate Dojo Offline, which must retrieve all resources we
|
|
|
584 |
// need offline into the offline cache -- we retrieve everything
|
|
|
585 |
// needed, in case another browser that requires different storage
|
|
|
586 |
// mechanisms hits the local offline cache. For example, if we
|
|
|
587 |
// were to sync against Dojo Offline on Firefox 2, then we would
|
|
|
588 |
// not grab the FlashStorageProvider resources needed for Safari.
|
|
|
589 |
var results = [];
|
|
|
590 |
dojo.forEach(dojox.storage.manager._providers, function(currentProvider){
|
|
|
591 |
results = results.concat(currentProvider.getResourceList());
|
|
|
592 |
});
|
|
|
593 |
|
|
|
594 |
return results;
|
|
|
595 |
}
|
|
|
596 |
};
|
|
|
597 |
|
|
|
598 |
}
|
|
|
599 |
|
|
|
600 |
if(!dojo._hasResource["dojox._sql._crypto"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
601 |
dojo._hasResource["dojox._sql._crypto"] = true;
|
|
|
602 |
// Taken from http://www.movable-type.co.uk/scripts/aes.html by
|
|
|
603 |
// Chris Veness (CLA signed); adapted for Dojo and Google Gears Worker Pool
|
|
|
604 |
// by Brad Neuberg, bkn3@columbia.edu
|
|
|
605 |
|
|
|
606 |
dojo.provide("dojox._sql._crypto");
|
|
|
607 |
|
|
|
608 |
dojo.mixin(dojox._sql._crypto,{
|
|
|
609 |
// _POOL_SIZE:
|
|
|
610 |
// Size of worker pool to create to help with crypto
|
|
|
611 |
_POOL_SIZE: 100,
|
|
|
612 |
|
|
|
613 |
encrypt: function(plaintext, password, callback){
|
|
|
614 |
// summary:
|
|
|
615 |
// Use Corrected Block TEA to encrypt plaintext using password
|
|
|
616 |
// (note plaintext & password must be strings not string objects).
|
|
|
617 |
// Results will be returned to the 'callback' asychronously.
|
|
|
618 |
this._initWorkerPool();
|
|
|
619 |
|
|
|
620 |
var msg ={plaintext: plaintext, password: password};
|
|
|
621 |
msg = dojo.toJson(msg);
|
|
|
622 |
msg = "encr:" + String(msg);
|
|
|
623 |
|
|
|
624 |
this._assignWork(msg, callback);
|
|
|
625 |
},
|
|
|
626 |
|
|
|
627 |
decrypt: function(ciphertext, password, callback){
|
|
|
628 |
// summary:
|
|
|
629 |
// Use Corrected Block TEA to decrypt ciphertext using password
|
|
|
630 |
// (note ciphertext & password must be strings not string objects).
|
|
|
631 |
// Results will be returned to the 'callback' asychronously.
|
|
|
632 |
this._initWorkerPool();
|
|
|
633 |
|
|
|
634 |
var msg ={ciphertext: ciphertext, password: password};
|
|
|
635 |
msg = dojo.toJson(msg);
|
|
|
636 |
msg = "decr:" + String(msg);
|
|
|
637 |
|
|
|
638 |
this._assignWork(msg, callback);
|
|
|
639 |
},
|
|
|
640 |
|
|
|
641 |
_initWorkerPool: function(){
|
|
|
642 |
// bugs in Google Gears prevents us from dynamically creating
|
|
|
643 |
// and destroying workers as we need them -- the worker
|
|
|
644 |
// pool functionality stops working after a number of crypto
|
|
|
645 |
// cycles (probably related to a memory leak in Google Gears).
|
|
|
646 |
// this is too bad, since it results in much simpler code.
|
|
|
647 |
|
|
|
648 |
// instead, we have to create a pool of workers and reuse them. we
|
|
|
649 |
// keep a stack of 'unemployed' Worker IDs that are currently not working.
|
|
|
650 |
// if a work request comes in, we pop off the 'unemployed' stack
|
|
|
651 |
// and put them to work, storing them in an 'employed' hashtable,
|
|
|
652 |
// keyed by their Worker ID with the value being the callback function
|
|
|
653 |
// that wants the result. when an employed worker is done, we get
|
|
|
654 |
// a message in our 'manager' which adds this worker back to the
|
|
|
655 |
// unemployed stack and routes the result to the callback that
|
|
|
656 |
// wanted it. if all the workers were employed in the past but
|
|
|
657 |
// more work needed to be done (i.e. it's a tight labor pool ;)
|
|
|
658 |
// then the work messages are pushed onto
|
|
|
659 |
// a 'handleMessage' queue as an object tuple{msg: msg, callback: callback}
|
|
|
660 |
|
|
|
661 |
if(!this._manager){
|
|
|
662 |
try{
|
|
|
663 |
this._manager = google.gears.factory.create("beta.workerpool", "1.0");
|
|
|
664 |
this._unemployed = [];
|
|
|
665 |
this._employed ={};
|
|
|
666 |
this._handleMessage = [];
|
|
|
667 |
|
|
|
668 |
var self = this;
|
|
|
669 |
this._manager.onmessage = function(msg, sender){
|
|
|
670 |
// get the callback necessary to serve this result
|
|
|
671 |
var callback = self._employed["_" + sender];
|
|
|
672 |
|
|
|
673 |
// make this worker unemployed
|
|
|
674 |
self._employed["_" + sender] = undefined;
|
|
|
675 |
self._unemployed.push("_" + sender);
|
|
|
676 |
|
|
|
677 |
// see if we need to assign new work
|
|
|
678 |
// that was queued up needing to be done
|
|
|
679 |
if(self._handleMessage.length){
|
|
|
680 |
var handleMe = self._handleMessage.shift();
|
|
|
681 |
self._assignWork(handleMe.msg, handleMe.callback);
|
|
|
682 |
}
|
|
|
683 |
|
|
|
684 |
// return results
|
|
|
685 |
callback(msg);
|
|
|
686 |
}
|
|
|
687 |
|
|
|
688 |
var workerInit = "function _workerInit(){"
|
|
|
689 |
+ "gearsWorkerPool.onmessage = "
|
|
|
690 |
+ String(this._workerHandler)
|
|
|
691 |
+ ";"
|
|
|
692 |
+ "}";
|
|
|
693 |
|
|
|
694 |
var code = workerInit + " _workerInit();";
|
|
|
695 |
|
|
|
696 |
// create our worker pool
|
|
|
697 |
for(var i = 0; i < this._POOL_SIZE; i++){
|
|
|
698 |
this._unemployed.push("_" + this._manager.createWorker(code));
|
|
|
699 |
}
|
|
|
700 |
}catch(exp){
|
|
|
701 |
throw exp.message||exp;
|
|
|
702 |
}
|
|
|
703 |
}
|
|
|
704 |
},
|
|
|
705 |
|
|
|
706 |
_assignWork: function(msg, callback){
|
|
|
707 |
// can we immediately assign this work?
|
|
|
708 |
if(!this._handleMessage.length && this._unemployed.length){
|
|
|
709 |
// get an unemployed worker
|
|
|
710 |
var workerID = this._unemployed.shift().substring(1); // remove _
|
|
|
711 |
|
|
|
712 |
// list this worker as employed
|
|
|
713 |
this._employed["_" + workerID] = callback;
|
|
|
714 |
|
|
|
715 |
// do the worke
|
|
|
716 |
this._manager.sendMessage(msg, workerID);
|
|
|
717 |
}else{
|
|
|
718 |
// we have to queue it up
|
|
|
719 |
this._handleMessage ={msg: msg, callback: callback};
|
|
|
720 |
}
|
|
|
721 |
},
|
|
|
722 |
|
|
|
723 |
_workerHandler: function(msg, sender){
|
|
|
724 |
|
|
|
725 |
/* Begin AES Implementation */
|
|
|
726 |
|
|
|
727 |
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
728 |
|
|
|
729 |
// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1]
|
|
|
730 |
var Sbox = [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
|
|
|
731 |
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
|
|
|
732 |
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
|
|
|
733 |
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
|
|
|
734 |
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
|
|
|
735 |
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
|
|
|
736 |
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
|
|
|
737 |
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
|
|
|
738 |
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
|
|
|
739 |
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
|
|
|
740 |
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
|
|
|
741 |
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
|
|
|
742 |
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
|
|
|
743 |
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
|
|
|
744 |
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
|
|
|
745 |
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];
|
|
|
746 |
|
|
|
747 |
// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
|
|
|
748 |
var Rcon = [ [0x00, 0x00, 0x00, 0x00],
|
|
|
749 |
[0x01, 0x00, 0x00, 0x00],
|
|
|
750 |
[0x02, 0x00, 0x00, 0x00],
|
|
|
751 |
[0x04, 0x00, 0x00, 0x00],
|
|
|
752 |
[0x08, 0x00, 0x00, 0x00],
|
|
|
753 |
[0x10, 0x00, 0x00, 0x00],
|
|
|
754 |
[0x20, 0x00, 0x00, 0x00],
|
|
|
755 |
[0x40, 0x00, 0x00, 0x00],
|
|
|
756 |
[0x80, 0x00, 0x00, 0x00],
|
|
|
757 |
[0x1b, 0x00, 0x00, 0x00],
|
|
|
758 |
[0x36, 0x00, 0x00, 0x00] ];
|
|
|
759 |
|
|
|
760 |
/*
|
|
|
761 |
* AES Cipher function: encrypt 'input' with Rijndael algorithm
|
|
|
762 |
*
|
|
|
763 |
* takes byte-array 'input' (16 bytes)
|
|
|
764 |
* 2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
|
|
|
765 |
*
|
|
|
766 |
* applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
|
|
|
767 |
*
|
|
|
768 |
* returns byte-array encrypted value (16 bytes)
|
|
|
769 |
*/
|
|
|
770 |
function Cipher(input, w) { // main Cipher function [§5.1]
|
|
|
771 |
var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
|
|
|
772 |
var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
|
|
|
773 |
|
|
|
774 |
var state = [[],[],[],[]]; // initialise 4xNb byte-array 'state' with input [§3.4]
|
|
|
775 |
for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];
|
|
|
776 |
|
|
|
777 |
state = AddRoundKey(state, w, 0, Nb);
|
|
|
778 |
|
|
|
779 |
for (var round=1; round<Nr; round++) {
|
|
|
780 |
state = SubBytes(state, Nb);
|
|
|
781 |
state = ShiftRows(state, Nb);
|
|
|
782 |
state = MixColumns(state, Nb);
|
|
|
783 |
state = AddRoundKey(state, w, round, Nb);
|
|
|
784 |
}
|
|
|
785 |
|
|
|
786 |
state = SubBytes(state, Nb);
|
|
|
787 |
state = ShiftRows(state, Nb);
|
|
|
788 |
state = AddRoundKey(state, w, Nr, Nb);
|
|
|
789 |
|
|
|
790 |
var output = new Array(4*Nb); // convert state to 1-d array before returning [§3.4]
|
|
|
791 |
for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
|
|
|
792 |
return output;
|
|
|
793 |
}
|
|
|
794 |
|
|
|
795 |
|
|
|
796 |
function SubBytes(s, Nb) { // apply SBox to state S [§5.1.1]
|
|
|
797 |
for (var r=0; r<4; r++) {
|
|
|
798 |
for (var c=0; c<Nb; c++) s[r][c] = Sbox[s[r][c]];
|
|
|
799 |
}
|
|
|
800 |
return s;
|
|
|
801 |
}
|
|
|
802 |
|
|
|
803 |
|
|
|
804 |
function ShiftRows(s, Nb) { // shift row r of state S left by r bytes [§5.1.2]
|
|
|
805 |
var t = new Array(4);
|
|
|
806 |
for (var r=1; r<4; r++) {
|
|
|
807 |
for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb]; // shift into temp copy
|
|
|
808 |
for (var c=0; c<4; c++) s[r][c] = t[c]; // and copy back
|
|
|
809 |
} // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
|
|
|
810 |
return s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
|
|
|
811 |
}
|
|
|
812 |
|
|
|
813 |
|
|
|
814 |
function MixColumns(s, Nb) { // combine bytes of each col of state S [§5.1.3]
|
|
|
815 |
for (var c=0; c<4; c++) {
|
|
|
816 |
var a = new Array(4); // 'a' is a copy of the current column from 's'
|
|
|
817 |
var b = new Array(4); // 'b' is a•{02} in GF(2^8)
|
|
|
818 |
for (var i=0; i<4; i++) {
|
|
|
819 |
a[i] = s[i][c];
|
|
|
820 |
b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
|
|
|
821 |
}
|
|
|
822 |
// a[n] ^ b[n] is a•{03} in GF(2^8)
|
|
|
823 |
s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
|
|
|
824 |
s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
|
|
|
825 |
s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
|
|
|
826 |
s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
|
|
|
827 |
}
|
|
|
828 |
return s;
|
|
|
829 |
}
|
|
|
830 |
|
|
|
831 |
|
|
|
832 |
function AddRoundKey(state, w, rnd, Nb) { // xor Round Key into state S [§5.1.4]
|
|
|
833 |
for (var r=0; r<4; r++) {
|
|
|
834 |
for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
|
|
|
835 |
}
|
|
|
836 |
return state;
|
|
|
837 |
}
|
|
|
838 |
|
|
|
839 |
|
|
|
840 |
function KeyExpansion(key) { // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
|
|
|
841 |
var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
|
|
|
842 |
var Nk = key.length/4 // key length (in words): 4/6/8 for 128/192/256-bit keys
|
|
|
843 |
var Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys
|
|
|
844 |
|
|
|
845 |
var w = new Array(Nb*(Nr+1));
|
|
|
846 |
var temp = new Array(4);
|
|
|
847 |
|
|
|
848 |
for (var i=0; i<Nk; i++) {
|
|
|
849 |
var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
|
|
|
850 |
w[i] = r;
|
|
|
851 |
}
|
|
|
852 |
|
|
|
853 |
for (var i=Nk; i<(Nb*(Nr+1)); i++) {
|
|
|
854 |
w[i] = new Array(4);
|
|
|
855 |
for (var t=0; t<4; t++) temp[t] = w[i-1][t];
|
|
|
856 |
if (i % Nk == 0) {
|
|
|
857 |
temp = SubWord(RotWord(temp));
|
|
|
858 |
for (var t=0; t<4; t++) temp[t] ^= Rcon[i/Nk][t];
|
|
|
859 |
} else if (Nk > 6 && i%Nk == 4) {
|
|
|
860 |
temp = SubWord(temp);
|
|
|
861 |
}
|
|
|
862 |
for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
|
|
|
863 |
}
|
|
|
864 |
|
|
|
865 |
return w;
|
|
|
866 |
}
|
|
|
867 |
|
|
|
868 |
function SubWord(w) { // apply SBox to 4-byte word w
|
|
|
869 |
for (var i=0; i<4; i++) w[i] = Sbox[w[i]];
|
|
|
870 |
return w;
|
|
|
871 |
}
|
|
|
872 |
|
|
|
873 |
function RotWord(w) { // rotate 4-byte word w left by one byte
|
|
|
874 |
w[4] = w[0];
|
|
|
875 |
for (var i=0; i<4; i++) w[i] = w[i+1];
|
|
|
876 |
return w;
|
|
|
877 |
}
|
|
|
878 |
|
|
|
879 |
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
880 |
|
|
|
881 |
/*
|
|
|
882 |
* Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation
|
|
|
883 |
* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
|
|
884 |
* for each block
|
|
|
885 |
* - outputblock = cipher(counter, key)
|
|
|
886 |
* - cipherblock = plaintext xor outputblock
|
|
|
887 |
*/
|
|
|
888 |
function AESEncryptCtr(plaintext, password, nBits) {
|
|
|
889 |
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
|
|
|
890 |
|
|
|
891 |
// for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password;
|
|
|
892 |
// for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1
|
|
|
893 |
var nBytes = nBits/8; // no bytes in key
|
|
|
894 |
var pwBytes = new Array(nBytes);
|
|
|
895 |
for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
|
|
|
896 |
|
|
|
897 |
var key = Cipher(pwBytes, KeyExpansion(pwBytes));
|
|
|
898 |
|
|
|
899 |
key = key.concat(key.slice(0, nBytes-16)); // key is now 16/24/32 bytes long
|
|
|
900 |
|
|
|
901 |
// initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
|
|
|
902 |
// block counter in 2nd 8 bytes
|
|
|
903 |
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
|
|
|
904 |
var counterBlock = new Array(blockSize); // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
|
|
|
905 |
var nonce = (new Date()).getTime(); // milliseconds since 1-Jan-1970
|
|
|
906 |
|
|
|
907 |
// encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops
|
|
|
908 |
for (var i=0; i<4; i++) counterBlock[i] = (nonce >>> i*8) & 0xff;
|
|
|
909 |
for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff;
|
|
|
910 |
|
|
|
911 |
// generate key schedule - an expansion of the key into distinct Key Rounds for each round
|
|
|
912 |
var keySchedule = KeyExpansion(key);
|
|
|
913 |
|
|
|
914 |
var blockCount = Math.ceil(plaintext.length/blockSize);
|
|
|
915 |
var ciphertext = new Array(blockCount); // ciphertext as array of strings
|
|
|
916 |
|
|
|
917 |
for (var b=0; b<blockCount; b++) {
|
|
|
918 |
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
|
|
|
919 |
// again done in two stages for 32-bit ops
|
|
|
920 |
for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
|
|
|
921 |
for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)
|
|
|
922 |
|
|
|
923 |
var cipherCntr = Cipher(counterBlock, keySchedule); // -- encrypt counter block --
|
|
|
924 |
|
|
|
925 |
// calculate length of final block:
|
|
|
926 |
var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
|
|
|
927 |
|
|
|
928 |
var ct = '';
|
|
|
929 |
for (var i=0; i<blockLength; i++) { // -- xor plaintext with ciphered counter byte-by-byte --
|
|
|
930 |
var plaintextByte = plaintext.charCodeAt(b*blockSize+i);
|
|
|
931 |
var cipherByte = plaintextByte ^ cipherCntr[i];
|
|
|
932 |
ct += String.fromCharCode(cipherByte);
|
|
|
933 |
}
|
|
|
934 |
// ct is now ciphertext for this block
|
|
|
935 |
|
|
|
936 |
ciphertext[b] = escCtrlChars(ct); // escape troublesome characters in ciphertext
|
|
|
937 |
}
|
|
|
938 |
|
|
|
939 |
// convert the nonce to a string to go on the front of the ciphertext
|
|
|
940 |
var ctrTxt = '';
|
|
|
941 |
for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);
|
|
|
942 |
ctrTxt = escCtrlChars(ctrTxt);
|
|
|
943 |
|
|
|
944 |
// use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency
|
|
|
945 |
return ctrTxt + '-' + ciphertext.join('-');
|
|
|
946 |
}
|
|
|
947 |
|
|
|
948 |
|
|
|
949 |
/*
|
|
|
950 |
* Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation
|
|
|
951 |
*
|
|
|
952 |
* for each block
|
|
|
953 |
* - outputblock = cipher(counter, key)
|
|
|
954 |
* - cipherblock = plaintext xor outputblock
|
|
|
955 |
*/
|
|
|
956 |
function AESDecryptCtr(ciphertext, password, nBits) {
|
|
|
957 |
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
|
|
|
958 |
|
|
|
959 |
var nBytes = nBits/8; // no bytes in key
|
|
|
960 |
var pwBytes = new Array(nBytes);
|
|
|
961 |
for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
|
|
|
962 |
var pwKeySchedule = KeyExpansion(pwBytes);
|
|
|
963 |
var key = Cipher(pwBytes, pwKeySchedule);
|
|
|
964 |
key = key.concat(key.slice(0, nBytes-16)); // key is now 16/24/32 bytes long
|
|
|
965 |
|
|
|
966 |
var keySchedule = KeyExpansion(key);
|
|
|
967 |
|
|
|
968 |
ciphertext = ciphertext.split('-'); // split ciphertext into array of block-length strings
|
|
|
969 |
|
|
|
970 |
// recover nonce from 1st element of ciphertext
|
|
|
971 |
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
|
|
|
972 |
var counterBlock = new Array(blockSize);
|
|
|
973 |
var ctrTxt = unescCtrlChars(ciphertext[0]);
|
|
|
974 |
for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);
|
|
|
975 |
|
|
|
976 |
var plaintext = new Array(ciphertext.length-1);
|
|
|
977 |
|
|
|
978 |
for (var b=1; b<ciphertext.length; b++) {
|
|
|
979 |
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
|
|
|
980 |
for (var c=0; c<4; c++) counterBlock[15-c] = ((b-1) >>> c*8) & 0xff;
|
|
|
981 |
for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff;
|
|
|
982 |
|
|
|
983 |
var cipherCntr = Cipher(counterBlock, keySchedule); // encrypt counter block
|
|
|
984 |
|
|
|
985 |
ciphertext[b] = unescCtrlChars(ciphertext[b]);
|
|
|
986 |
|
|
|
987 |
var pt = '';
|
|
|
988 |
for (var i=0; i<ciphertext[b].length; i++) {
|
|
|
989 |
// -- xor plaintext with ciphered counter byte-by-byte --
|
|
|
990 |
var ciphertextByte = ciphertext[b].charCodeAt(i);
|
|
|
991 |
var plaintextByte = ciphertextByte ^ cipherCntr[i];
|
|
|
992 |
pt += String.fromCharCode(plaintextByte);
|
|
|
993 |
}
|
|
|
994 |
// pt is now plaintext for this block
|
|
|
995 |
|
|
|
996 |
plaintext[b-1] = pt; // b-1 'cos no initial nonce block in plaintext
|
|
|
997 |
}
|
|
|
998 |
|
|
|
999 |
return plaintext.join('');
|
|
|
1000 |
}
|
|
|
1001 |
|
|
|
1002 |
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
1003 |
|
|
|
1004 |
function escCtrlChars(str) { // escape control chars which might cause problems handling ciphertext
|
|
|
1005 |
return str.replace(/[\0\t\n\v\f\r\xa0!-]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
|
|
|
1006 |
} // \xa0 to cater for bug in Firefox; include '-' to leave it free for use as a block marker
|
|
|
1007 |
|
|
|
1008 |
function unescCtrlChars(str) { // unescape potentially problematic control characters
|
|
|
1009 |
return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); });
|
|
|
1010 |
}
|
|
|
1011 |
|
|
|
1012 |
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
1013 |
|
|
|
1014 |
function encrypt(plaintext, password){
|
|
|
1015 |
return AESEncryptCtr(plaintext, password, 256);
|
|
|
1016 |
}
|
|
|
1017 |
|
|
|
1018 |
function decrypt(ciphertext, password){
|
|
|
1019 |
return AESDecryptCtr(ciphertext, password, 256);
|
|
|
1020 |
}
|
|
|
1021 |
|
|
|
1022 |
/* End AES Implementation */
|
|
|
1023 |
|
|
|
1024 |
var cmd = msg.substr(0,4);
|
|
|
1025 |
var arg = msg.substr(5);
|
|
|
1026 |
if(cmd == "encr"){
|
|
|
1027 |
arg = eval("(" + arg + ")");
|
|
|
1028 |
var plaintext = arg.plaintext;
|
|
|
1029 |
var password = arg.password;
|
|
|
1030 |
var results = encrypt(plaintext, password);
|
|
|
1031 |
gearsWorkerPool.sendMessage(String(results), sender);
|
|
|
1032 |
}else if(cmd == "decr"){
|
|
|
1033 |
arg = eval("(" + arg + ")");
|
|
|
1034 |
var ciphertext = arg.ciphertext;
|
|
|
1035 |
var password = arg.password;
|
|
|
1036 |
var results = decrypt(ciphertext, password);
|
|
|
1037 |
gearsWorkerPool.sendMessage(String(results), sender);
|
|
|
1038 |
}
|
|
|
1039 |
}
|
|
|
1040 |
});
|
|
|
1041 |
|
|
|
1042 |
}
|
|
|
1043 |
|
|
|
1044 |
if(!dojo._hasResource["dojox._sql.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
1045 |
dojo._hasResource["dojox._sql.common"] = true;
|
|
|
1046 |
dojo.provide("dojox._sql.common");
|
|
|
1047 |
|
|
|
1048 |
|
|
|
1049 |
|
|
|
1050 |
// summary:
|
|
|
1051 |
// Executes a SQL expression.
|
|
|
1052 |
// description:
|
|
|
1053 |
// There are four ways to call this:
|
|
|
1054 |
// 1) Straight SQL: dojox.sql("SELECT * FROM FOOBAR");
|
|
|
1055 |
// 2) SQL with parameters: dojox.sql("INSERT INTO FOOBAR VALUES (?)", someParam)
|
|
|
1056 |
// 3) Encrypting particular values:
|
|
|
1057 |
// dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?))", someParam, "somePassword", callback)
|
|
|
1058 |
// 4) Decrypting particular values:
|
|
|
1059 |
// dojox.sql("SELECT DECRYPT(SOMECOL1), DECRYPT(SOMECOL2) FROM
|
|
|
1060 |
// FOOBAR WHERE SOMECOL3 = ?", someParam,
|
|
|
1061 |
// "somePassword", callback)
|
|
|
1062 |
//
|
|
|
1063 |
// For encryption and decryption the last two values should be the the password for
|
|
|
1064 |
// encryption/decryption, and the callback function that gets the result set.
|
|
|
1065 |
//
|
|
|
1066 |
// Note: We only support ENCRYPT(?) statements, and
|
|
|
1067 |
// and DECRYPT(*) statements for now -- you can not have a literal string
|
|
|
1068 |
// inside of these, such as ENCRYPT('foobar')
|
|
|
1069 |
//
|
|
|
1070 |
// Note: If you have multiple columns to encrypt and decrypt, you can use the following
|
|
|
1071 |
// convenience form to not have to type ENCRYPT(?)/DECRYPT(*) many times:
|
|
|
1072 |
//
|
|
|
1073 |
// dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?, ?, ?))",
|
|
|
1074 |
// someParam1, someParam2, someParam3,
|
|
|
1075 |
// "somePassword", callback)
|
|
|
1076 |
//
|
|
|
1077 |
// dojox.sql("SELECT DECRYPT(SOMECOL1, SOMECOL2) FROM
|
|
|
1078 |
// FOOBAR WHERE SOMECOL3 = ?", someParam,
|
|
|
1079 |
// "somePassword", callback)
|
|
|
1080 |
dojox.sql = new Function("return dojox.sql._exec(arguments);");
|
|
|
1081 |
|
|
|
1082 |
dojo.mixin(dojox.sql, {
|
|
|
1083 |
dbName: null,
|
|
|
1084 |
|
|
|
1085 |
// summary:
|
|
|
1086 |
// If true, then we print out any SQL that is executed
|
|
|
1087 |
// to the debug window
|
|
|
1088 |
debug: (dojo.exists("dojox.sql.debug")?dojox.sql.debug:false),
|
|
|
1089 |
|
|
|
1090 |
open: function(dbName){
|
|
|
1091 |
if(this._dbOpen && (!dbName || dbName == this.dbName)){
|
|
|
1092 |
return;
|
|
|
1093 |
}
|
|
|
1094 |
|
|
|
1095 |
if(!this.dbName){
|
|
|
1096 |
this.dbName = "dot_store_"
|
|
|
1097 |
+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
|
|
|
1098 |
//console.debug("Using Google Gears database " + this.dbName);
|
|
|
1099 |
}
|
|
|
1100 |
|
|
|
1101 |
if(!dbName){
|
|
|
1102 |
dbName = this.dbName;
|
|
|
1103 |
}
|
|
|
1104 |
|
|
|
1105 |
try{
|
|
|
1106 |
this._initDb();
|
|
|
1107 |
this.db.open(dbName);
|
|
|
1108 |
this._dbOpen = true;
|
|
|
1109 |
}catch(exp){
|
|
|
1110 |
throw exp.message||exp;
|
|
|
1111 |
}
|
|
|
1112 |
},
|
|
|
1113 |
|
|
|
1114 |
close: function(dbName){
|
|
|
1115 |
// on Internet Explorer, Google Gears throws an exception
|
|
|
1116 |
// "Object not a collection", when we try to close the
|
|
|
1117 |
// database -- just don't close it on this platform
|
|
|
1118 |
// since we are running into a Gears bug; the Gears team
|
|
|
1119 |
// said it's ok to not close a database connection
|
|
|
1120 |
if(dojo.isIE){ return; }
|
|
|
1121 |
|
|
|
1122 |
if(!this._dbOpen && (!dbName || dbName == this.dbName)){
|
|
|
1123 |
return;
|
|
|
1124 |
}
|
|
|
1125 |
|
|
|
1126 |
if(!dbName){
|
|
|
1127 |
dbName = this.dbName;
|
|
|
1128 |
}
|
|
|
1129 |
|
|
|
1130 |
try{
|
|
|
1131 |
this.db.close(dbName);
|
|
|
1132 |
this._dbOpen = false;
|
|
|
1133 |
}catch(exp){
|
|
|
1134 |
throw exp.message||exp;
|
|
|
1135 |
}
|
|
|
1136 |
},
|
|
|
1137 |
|
|
|
1138 |
_exec: function(params){
|
|
|
1139 |
try{
|
|
|
1140 |
// get the Gears Database object
|
|
|
1141 |
this._initDb();
|
|
|
1142 |
|
|
|
1143 |
// see if we need to open the db; if programmer
|
|
|
1144 |
// manually called dojox.sql.open() let them handle
|
|
|
1145 |
// it; otherwise we open and close automatically on
|
|
|
1146 |
// each SQL execution
|
|
|
1147 |
if(!this._dbOpen){
|
|
|
1148 |
this.open();
|
|
|
1149 |
this._autoClose = true;
|
|
|
1150 |
}
|
|
|
1151 |
|
|
|
1152 |
// determine our parameters
|
|
|
1153 |
var sql = null;
|
|
|
1154 |
var callback = null;
|
|
|
1155 |
var password = null;
|
|
|
1156 |
|
|
|
1157 |
var args = dojo._toArray(params);
|
|
|
1158 |
|
|
|
1159 |
sql = args.splice(0, 1)[0];
|
|
|
1160 |
|
|
|
1161 |
// does this SQL statement use the ENCRYPT or DECRYPT
|
|
|
1162 |
// keywords? if so, extract our callback and crypto
|
|
|
1163 |
// password
|
|
|
1164 |
if(this._needsEncrypt(sql) || this._needsDecrypt(sql)){
|
|
|
1165 |
callback = args.splice(args.length - 1, 1)[0];
|
|
|
1166 |
password = args.splice(args.length - 1, 1)[0];
|
|
|
1167 |
}
|
|
|
1168 |
|
|
|
1169 |
// 'args' now just has the SQL parameters
|
|
|
1170 |
|
|
|
1171 |
// print out debug SQL output if the developer wants that
|
|
|
1172 |
if(this.debug){
|
|
|
1173 |
this._printDebugSQL(sql, args);
|
|
|
1174 |
}
|
|
|
1175 |
|
|
|
1176 |
// handle SQL that needs encryption/decryption differently
|
|
|
1177 |
// do we have an ENCRYPT SQL statement? if so, handle that first
|
|
|
1178 |
if(this._needsEncrypt(sql)){
|
|
|
1179 |
var crypto = new dojox.sql._SQLCrypto("encrypt", sql,
|
|
|
1180 |
password, args,
|
|
|
1181 |
callback);
|
|
|
1182 |
return; // encrypted results will arrive asynchronously
|
|
|
1183 |
}else if(this._needsDecrypt(sql)){ // otherwise we have a DECRYPT statement
|
|
|
1184 |
var crypto = new dojox.sql._SQLCrypto("decrypt", sql,
|
|
|
1185 |
password, args,
|
|
|
1186 |
callback);
|
|
|
1187 |
return; // decrypted results will arrive asynchronously
|
|
|
1188 |
}
|
|
|
1189 |
|
|
|
1190 |
// execute the SQL and get the results
|
|
|
1191 |
var rs = this.db.execute(sql, args);
|
|
|
1192 |
|
|
|
1193 |
// Gears ResultSet object's are ugly -- normalize
|
|
|
1194 |
// these into something JavaScript programmers know
|
|
|
1195 |
// how to work with, basically an array of
|
|
|
1196 |
// JavaScript objects where each property name is
|
|
|
1197 |
// simply the field name for a column of data
|
|
|
1198 |
rs = this._normalizeResults(rs);
|
|
|
1199 |
|
|
|
1200 |
if(this._autoClose){
|
|
|
1201 |
this.close();
|
|
|
1202 |
}
|
|
|
1203 |
|
|
|
1204 |
return rs;
|
|
|
1205 |
}catch(exp){
|
|
|
1206 |
exp = exp.message||exp;
|
|
|
1207 |
|
|
|
1208 |
console.debug("SQL Exception: " + exp);
|
|
|
1209 |
|
|
|
1210 |
if(this._autoClose){
|
|
|
1211 |
try{
|
|
|
1212 |
this.close();
|
|
|
1213 |
}catch(e){
|
|
|
1214 |
console.debug("Error closing database: "
|
|
|
1215 |
+ e.message||e);
|
|
|
1216 |
}
|
|
|
1217 |
}
|
|
|
1218 |
|
|
|
1219 |
throw exp;
|
|
|
1220 |
}
|
|
|
1221 |
},
|
|
|
1222 |
|
|
|
1223 |
_initDb: function(){
|
|
|
1224 |
if(!this.db){
|
|
|
1225 |
try{
|
|
|
1226 |
this.db = google.gears.factory.create('beta.database', '1.0');
|
|
|
1227 |
}catch(exp){
|
|
|
1228 |
dojo.setObject("google.gears.denied", true);
|
|
|
1229 |
dojox.off.onFrameworkEvent("coreOperationFailed");
|
|
|
1230 |
throw "Google Gears must be allowed to run";
|
|
|
1231 |
}
|
|
|
1232 |
}
|
|
|
1233 |
},
|
|
|
1234 |
|
|
|
1235 |
_printDebugSQL: function(sql, args){
|
|
|
1236 |
var msg = "dojox.sql(\"" + sql + "\"";
|
|
|
1237 |
for(var i = 0; i < args.length; i++){
|
|
|
1238 |
if(typeof args[i] == "string"){
|
|
|
1239 |
msg += ", \"" + args[i] + "\"";
|
|
|
1240 |
}else{
|
|
|
1241 |
msg += ", " + args[i];
|
|
|
1242 |
}
|
|
|
1243 |
}
|
|
|
1244 |
msg += ")";
|
|
|
1245 |
|
|
|
1246 |
console.debug(msg);
|
|
|
1247 |
},
|
|
|
1248 |
|
|
|
1249 |
_normalizeResults: function(rs){
|
|
|
1250 |
var results = [];
|
|
|
1251 |
if(!rs){ return []; }
|
|
|
1252 |
|
|
|
1253 |
while(rs.isValidRow()){
|
|
|
1254 |
var row = {};
|
|
|
1255 |
|
|
|
1256 |
for(var i = 0; i < rs.fieldCount(); i++){
|
|
|
1257 |
var fieldName = rs.fieldName(i);
|
|
|
1258 |
var fieldValue = rs.field(i);
|
|
|
1259 |
row[fieldName] = fieldValue;
|
|
|
1260 |
}
|
|
|
1261 |
|
|
|
1262 |
results.push(row);
|
|
|
1263 |
|
|
|
1264 |
rs.next();
|
|
|
1265 |
}
|
|
|
1266 |
|
|
|
1267 |
rs.close();
|
|
|
1268 |
|
|
|
1269 |
return results;
|
|
|
1270 |
},
|
|
|
1271 |
|
|
|
1272 |
_needsEncrypt: function(sql){
|
|
|
1273 |
return /encrypt\([^\)]*\)/i.test(sql);
|
|
|
1274 |
},
|
|
|
1275 |
|
|
|
1276 |
_needsDecrypt: function(sql){
|
|
|
1277 |
return /decrypt\([^\)]*\)/i.test(sql);
|
|
|
1278 |
}
|
|
|
1279 |
});
|
|
|
1280 |
|
|
|
1281 |
// summary:
|
|
|
1282 |
// A private class encapsulating any cryptography that must be done
|
|
|
1283 |
// on a SQL statement. We instantiate this class and have it hold
|
|
|
1284 |
// it's state so that we can potentially have several encryption
|
|
|
1285 |
// operations happening at the same time by different SQL statements.
|
|
|
1286 |
dojo.declare("dojox.sql._SQLCrypto", null, {
|
|
|
1287 |
constructor: function(action, sql, password, args, callback){
|
|
|
1288 |
if(action == "encrypt"){
|
|
|
1289 |
this._execEncryptSQL(sql, password, args, callback);
|
|
|
1290 |
}else{
|
|
|
1291 |
this._execDecryptSQL(sql, password, args, callback);
|
|
|
1292 |
}
|
|
|
1293 |
},
|
|
|
1294 |
|
|
|
1295 |
_execEncryptSQL: function(sql, password, args, callback){
|
|
|
1296 |
// strip the ENCRYPT/DECRYPT keywords from the SQL
|
|
|
1297 |
var strippedSQL = this._stripCryptoSQL(sql);
|
|
|
1298 |
|
|
|
1299 |
// determine what arguments need encryption
|
|
|
1300 |
var encryptColumns = this._flagEncryptedArgs(sql, args);
|
|
|
1301 |
|
|
|
1302 |
// asynchronously encrypt each argument that needs it
|
|
|
1303 |
var self = this;
|
|
|
1304 |
this._encrypt(strippedSQL, password, args, encryptColumns, function(finalArgs){
|
|
|
1305 |
// execute the SQL
|
|
|
1306 |
var error = false;
|
|
|
1307 |
var resultSet = [];
|
|
|
1308 |
var exp = null;
|
|
|
1309 |
try{
|
|
|
1310 |
resultSet = dojox.sql.db.execute(strippedSQL, finalArgs);
|
|
|
1311 |
}catch(execError){
|
|
|
1312 |
error = true;
|
|
|
1313 |
exp = execError.message||execError;
|
|
|
1314 |
}
|
|
|
1315 |
|
|
|
1316 |
// was there an error during SQL execution?
|
|
|
1317 |
if(exp != null){
|
|
|
1318 |
if(dojox.sql._autoClose){
|
|
|
1319 |
try{ dojox.sql.close(); }catch(e){}
|
|
|
1320 |
}
|
|
|
1321 |
|
|
|
1322 |
callback(null, true, exp.toString());
|
|
|
1323 |
return;
|
|
|
1324 |
}
|
|
|
1325 |
|
|
|
1326 |
// normalize SQL results into a JavaScript object
|
|
|
1327 |
// we can work with
|
|
|
1328 |
resultSet = dojox.sql._normalizeResults(resultSet);
|
|
|
1329 |
|
|
|
1330 |
if(dojox.sql._autoClose){
|
|
|
1331 |
dojox.sql.close();
|
|
|
1332 |
}
|
|
|
1333 |
|
|
|
1334 |
// are any decryptions necessary on the result set?
|
|
|
1335 |
if(dojox.sql._needsDecrypt(sql)){
|
|
|
1336 |
// determine which of the result set columns needs decryption
|
|
|
1337 |
var needsDecrypt = self._determineDecryptedColumns(sql);
|
|
|
1338 |
|
|
|
1339 |
// now decrypt columns asynchronously
|
|
|
1340 |
// decrypt columns that need it
|
|
|
1341 |
self._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
|
|
|
1342 |
callback(finalResultSet, false, null);
|
|
|
1343 |
});
|
|
|
1344 |
}else{
|
|
|
1345 |
callback(resultSet, false, null);
|
|
|
1346 |
}
|
|
|
1347 |
});
|
|
|
1348 |
},
|
|
|
1349 |
|
|
|
1350 |
_execDecryptSQL: function(sql, password, args, callback){
|
|
|
1351 |
// strip the ENCRYPT/DECRYPT keywords from the SQL
|
|
|
1352 |
var strippedSQL = this._stripCryptoSQL(sql);
|
|
|
1353 |
|
|
|
1354 |
// determine which columns needs decryption; this either
|
|
|
1355 |
// returns the value *, which means all result set columns will
|
|
|
1356 |
// be decrypted, or it will return the column names that need
|
|
|
1357 |
// decryption set on a hashtable so we can quickly test a given
|
|
|
1358 |
// column name; the key is the column name that needs
|
|
|
1359 |
// decryption and the value is 'true' (i.e. needsDecrypt["someColumn"]
|
|
|
1360 |
// would return 'true' if it needs decryption, and would be 'undefined'
|
|
|
1361 |
// or false otherwise)
|
|
|
1362 |
var needsDecrypt = this._determineDecryptedColumns(sql);
|
|
|
1363 |
|
|
|
1364 |
// execute the SQL
|
|
|
1365 |
var error = false;
|
|
|
1366 |
var resultSet = [];
|
|
|
1367 |
var exp = null;
|
|
|
1368 |
try{
|
|
|
1369 |
resultSet = dojox.sql.db.execute(strippedSQL, args);
|
|
|
1370 |
}catch(execError){
|
|
|
1371 |
error = true;
|
|
|
1372 |
exp = execError.message||execError;
|
|
|
1373 |
}
|
|
|
1374 |
|
|
|
1375 |
// was there an error during SQL execution?
|
|
|
1376 |
if(exp != null){
|
|
|
1377 |
if(dojox.sql._autoClose){
|
|
|
1378 |
try{ dojox.sql.close(); }catch(e){}
|
|
|
1379 |
}
|
|
|
1380 |
|
|
|
1381 |
callback(resultSet, true, exp.toString());
|
|
|
1382 |
return;
|
|
|
1383 |
}
|
|
|
1384 |
|
|
|
1385 |
// normalize SQL results into a JavaScript object
|
|
|
1386 |
// we can work with
|
|
|
1387 |
resultSet = dojox.sql._normalizeResults(resultSet);
|
|
|
1388 |
|
|
|
1389 |
if(dojox.sql._autoClose){
|
|
|
1390 |
dojox.sql.close();
|
|
|
1391 |
}
|
|
|
1392 |
|
|
|
1393 |
// decrypt columns that need it
|
|
|
1394 |
this._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
|
|
|
1395 |
callback(finalResultSet, false, null);
|
|
|
1396 |
});
|
|
|
1397 |
},
|
|
|
1398 |
|
|
|
1399 |
_encrypt: function(sql, password, args, encryptColumns, callback){
|
|
|
1400 |
//console.debug("_encrypt, sql="+sql+", password="+password+", encryptColumns="+encryptColumns+", args="+args);
|
|
|
1401 |
|
|
|
1402 |
this._totalCrypto = 0;
|
|
|
1403 |
this._finishedCrypto = 0;
|
|
|
1404 |
this._finishedSpawningCrypto = false;
|
|
|
1405 |
this._finalArgs = args;
|
|
|
1406 |
|
|
|
1407 |
for(var i = 0; i < args.length; i++){
|
|
|
1408 |
if(encryptColumns[i]){
|
|
|
1409 |
// we have an encrypt() keyword -- get just the value inside
|
|
|
1410 |
// the encrypt() parantheses -- for now this must be a ?
|
|
|
1411 |
var sqlParam = args[i];
|
|
|
1412 |
var paramIndex = i;
|
|
|
1413 |
|
|
|
1414 |
// update the total number of encryptions we know must be done asynchronously
|
|
|
1415 |
this._totalCrypto++;
|
|
|
1416 |
|
|
|
1417 |
// FIXME: This currently uses DES as a proof-of-concept since the
|
|
|
1418 |
// DES code used is quite fast and was easy to work with. Modify dojox.sql
|
|
|
1419 |
// to be able to specify a different encryption provider through a
|
|
|
1420 |
// a SQL-like syntax, such as dojox.sql("SET ENCRYPTION BLOWFISH"),
|
|
|
1421 |
// and modify the dojox.crypto.Blowfish code to be able to work using
|
|
|
1422 |
// a Google Gears Worker Pool
|
|
|
1423 |
|
|
|
1424 |
// do the actual encryption now, asychronously on a Gears worker thread
|
|
|
1425 |
dojox._sql._crypto.encrypt(sqlParam, password, dojo.hitch(this, function(results){
|
|
|
1426 |
// set the new encrypted value
|
|
|
1427 |
this._finalArgs[paramIndex] = results;
|
|
|
1428 |
this._finishedCrypto++;
|
|
|
1429 |
// are we done with all encryption?
|
|
|
1430 |
if(this._finishedCrypto >= this._totalCrypto
|
|
|
1431 |
&& this._finishedSpawningCrypto){
|
|
|
1432 |
callback(this._finalArgs);
|
|
|
1433 |
}
|
|
|
1434 |
}));
|
|
|
1435 |
}
|
|
|
1436 |
}
|
|
|
1437 |
|
|
|
1438 |
this._finishedSpawningCrypto = true;
|
|
|
1439 |
},
|
|
|
1440 |
|
|
|
1441 |
_decrypt: function(resultSet, needsDecrypt, password, callback){
|
|
|
1442 |
//console.debug("decrypt, resultSet="+resultSet+", needsDecrypt="+needsDecrypt+", password="+password);
|
|
|
1443 |
|
|
|
1444 |
this._totalCrypto = 0;
|
|
|
1445 |
this._finishedCrypto = 0;
|
|
|
1446 |
this._finishedSpawningCrypto = false;
|
|
|
1447 |
this._finalResultSet = resultSet;
|
|
|
1448 |
|
|
|
1449 |
for(var i = 0; i < resultSet.length; i++){
|
|
|
1450 |
var row = resultSet[i];
|
|
|
1451 |
|
|
|
1452 |
// go through each of the column names in row,
|
|
|
1453 |
// seeing if they need decryption
|
|
|
1454 |
for(var columnName in row){
|
|
|
1455 |
if(needsDecrypt == "*" || needsDecrypt[columnName]){
|
|
|
1456 |
this._totalCrypto++;
|
|
|
1457 |
var columnValue = row[columnName];
|
|
|
1458 |
|
|
|
1459 |
// forming a closure here can cause issues, with values not cleanly
|
|
|
1460 |
// saved on Firefox/Mac OS X for some of the values above that
|
|
|
1461 |
// are needed in the callback below; call a subroutine that will form
|
|
|
1462 |
// a closure inside of itself instead
|
|
|
1463 |
this._decryptSingleColumn(columnName, columnValue, password, i,
|
|
|
1464 |
function(finalResultSet){
|
|
|
1465 |
callback(finalResultSet);
|
|
|
1466 |
});
|
|
|
1467 |
}
|
|
|
1468 |
}
|
|
|
1469 |
}
|
|
|
1470 |
|
|
|
1471 |
this._finishedSpawningCrypto = true;
|
|
|
1472 |
},
|
|
|
1473 |
|
|
|
1474 |
_stripCryptoSQL: function(sql){
|
|
|
1475 |
// replace all DECRYPT(*) occurrences with a *
|
|
|
1476 |
sql = sql.replace(/DECRYPT\(\*\)/ig, "*");
|
|
|
1477 |
|
|
|
1478 |
// match any ENCRYPT(?, ?, ?, etc) occurrences,
|
|
|
1479 |
// then replace with just the question marks in the
|
|
|
1480 |
// middle
|
|
|
1481 |
var matches = sql.match(/ENCRYPT\([^\)]*\)/ig);
|
|
|
1482 |
if(matches != null){
|
|
|
1483 |
for(var i = 0; i < matches.length; i++){
|
|
|
1484 |
var encryptStatement = matches[i];
|
|
|
1485 |
var encryptValue = encryptStatement.match(/ENCRYPT\(([^\)]*)\)/i)[1];
|
|
|
1486 |
sql = sql.replace(encryptStatement, encryptValue);
|
|
|
1487 |
}
|
|
|
1488 |
}
|
|
|
1489 |
|
|
|
1490 |
// match any DECRYPT(COL1, COL2, etc) occurrences,
|
|
|
1491 |
// then replace with just the column names
|
|
|
1492 |
// in the middle
|
|
|
1493 |
matches = sql.match(/DECRYPT\([^\)]*\)/ig);
|
|
|
1494 |
if(matches != null){
|
|
|
1495 |
for(var i = 0; i < matches.length; i++){
|
|
|
1496 |
var decryptStatement = matches[i];
|
|
|
1497 |
var decryptValue = decryptStatement.match(/DECRYPT\(([^\)]*)\)/i)[1];
|
|
|
1498 |
sql = sql.replace(decryptStatement, decryptValue);
|
|
|
1499 |
}
|
|
|
1500 |
}
|
|
|
1501 |
|
|
|
1502 |
return sql;
|
|
|
1503 |
},
|
|
|
1504 |
|
|
|
1505 |
_flagEncryptedArgs: function(sql, args){
|
|
|
1506 |
// capture literal strings that have question marks in them,
|
|
|
1507 |
// and also capture question marks that stand alone
|
|
|
1508 |
var tester = new RegExp(/([\"][^\"]*\?[^\"]*[\"])|([\'][^\']*\?[^\']*[\'])|(\?)/ig);
|
|
|
1509 |
var matches;
|
|
|
1510 |
var currentParam = 0;
|
|
|
1511 |
var results = [];
|
|
|
1512 |
while((matches = tester.exec(sql)) != null){
|
|
|
1513 |
var currentMatch = RegExp.lastMatch+"";
|
|
|
1514 |
|
|
|
1515 |
// are we a literal string? then ignore it
|
|
|
1516 |
if(/^[\"\']/.test(currentMatch)){
|
|
|
1517 |
continue;
|
|
|
1518 |
}
|
|
|
1519 |
|
|
|
1520 |
// do we have an encrypt keyword to our left?
|
|
|
1521 |
var needsEncrypt = false;
|
|
|
1522 |
if(/ENCRYPT\([^\)]*$/i.test(RegExp.leftContext)){
|
|
|
1523 |
needsEncrypt = true;
|
|
|
1524 |
}
|
|
|
1525 |
|
|
|
1526 |
// set the encrypted flag
|
|
|
1527 |
results[currentParam] = needsEncrypt;
|
|
|
1528 |
|
|
|
1529 |
currentParam++;
|
|
|
1530 |
}
|
|
|
1531 |
|
|
|
1532 |
return results;
|
|
|
1533 |
},
|
|
|
1534 |
|
|
|
1535 |
_determineDecryptedColumns: function(sql){
|
|
|
1536 |
var results = {};
|
|
|
1537 |
|
|
|
1538 |
if(/DECRYPT\(\*\)/i.test(sql)){
|
|
|
1539 |
results = "*";
|
|
|
1540 |
}else{
|
|
|
1541 |
var tester = /DECRYPT\((?:\s*\w*\s*\,?)*\)/ig;
|
|
|
1542 |
var matches;
|
|
|
1543 |
while(matches = tester.exec(sql)){
|
|
|
1544 |
var lastMatch = new String(RegExp.lastMatch);
|
|
|
1545 |
var columnNames = lastMatch.replace(/DECRYPT\(/i, "");
|
|
|
1546 |
columnNames = columnNames.replace(/\)/, "");
|
|
|
1547 |
columnNames = columnNames.split(/\s*,\s*/);
|
|
|
1548 |
dojo.forEach(columnNames, function(column){
|
|
|
1549 |
if(/\s*\w* AS (\w*)/i.test(column)){
|
|
|
1550 |
column = column.match(/\s*\w* AS (\w*)/i)[1];
|
|
|
1551 |
}
|
|
|
1552 |
results[column] = true;
|
|
|
1553 |
});
|
|
|
1554 |
}
|
|
|
1555 |
}
|
|
|
1556 |
|
|
|
1557 |
return results;
|
|
|
1558 |
},
|
|
|
1559 |
|
|
|
1560 |
_decryptSingleColumn: function(columnName, columnValue, password, currentRowIndex,
|
|
|
1561 |
callback){
|
|
|
1562 |
//console.debug("decryptSingleColumn, columnName="+columnName+", columnValue="+columnValue+", currentRowIndex="+currentRowIndex)
|
|
|
1563 |
dojox._sql._crypto.decrypt(columnValue, password, dojo.hitch(this, function(results){
|
|
|
1564 |
// set the new decrypted value
|
|
|
1565 |
this._finalResultSet[currentRowIndex][columnName] = results;
|
|
|
1566 |
this._finishedCrypto++;
|
|
|
1567 |
|
|
|
1568 |
// are we done with all encryption?
|
|
|
1569 |
if(this._finishedCrypto >= this._totalCrypto
|
|
|
1570 |
&& this._finishedSpawningCrypto){
|
|
|
1571 |
//console.debug("done with all decrypts");
|
|
|
1572 |
callback(this._finalResultSet);
|
|
|
1573 |
}
|
|
|
1574 |
}));
|
|
|
1575 |
}
|
|
|
1576 |
});
|
|
|
1577 |
|
|
|
1578 |
}
|
|
|
1579 |
|
|
|
1580 |
if(!dojo._hasResource["dojox.sql"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
1581 |
dojo._hasResource["dojox.sql"] = true;
|
|
|
1582 |
|
|
|
1583 |
dojo.provide("dojox.sql");
|
|
|
1584 |
|
|
|
1585 |
}
|
|
|
1586 |
|
|
|
1587 |
if(!dojo._hasResource["dojox.storage.GearsStorageProvider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
1588 |
dojo._hasResource["dojox.storage.GearsStorageProvider"] = true;
|
|
|
1589 |
dojo.provide("dojox.storage.GearsStorageProvider");
|
|
|
1590 |
|
|
|
1591 |
|
|
|
1592 |
|
|
|
1593 |
|
|
|
1594 |
if(dojo.isGears){
|
|
|
1595 |
|
|
|
1596 |
(function(){
|
|
|
1597 |
// make sure we don't define the gears provider if we're not gears
|
|
|
1598 |
// enabled
|
|
|
1599 |
|
|
|
1600 |
dojo.declare("dojox.storage.GearsStorageProvider", dojox.storage.Provider, {
|
|
|
1601 |
// summary:
|
|
|
1602 |
// Storage provider that uses the features of Google Gears
|
|
|
1603 |
// to store data (it is saved into the local SQL database
|
|
|
1604 |
// provided by Gears, using dojox.sql)
|
|
|
1605 |
// description:
|
|
|
1606 |
//
|
|
|
1607 |
//
|
|
|
1608 |
// You can disable this storage provider with the following djConfig
|
|
|
1609 |
// variable:
|
|
|
1610 |
// var djConfig = { disableGearsStorage: true };
|
|
|
1611 |
//
|
|
|
1612 |
// Authors of this storage provider-
|
|
|
1613 |
// Brad Neuberg, bkn3@columbia.edu
|
|
|
1614 |
constructor: function(){
|
|
|
1615 |
},
|
|
|
1616 |
// instance methods and properties
|
|
|
1617 |
TABLE_NAME: "__DOJO_STORAGE",
|
|
|
1618 |
initialized: false,
|
|
|
1619 |
|
|
|
1620 |
_available: null,
|
|
|
1621 |
|
|
|
1622 |
initialize: function(){
|
|
|
1623 |
//console.debug("dojox.storage.GearsStorageProvider.initialize");
|
|
|
1624 |
if(djConfig["disableGearsStorage"] == true){
|
|
|
1625 |
return;
|
|
|
1626 |
}
|
|
|
1627 |
|
|
|
1628 |
// partition our storage data so that multiple apps
|
|
|
1629 |
// on the same host won't collide
|
|
|
1630 |
this.TABLE_NAME = "__DOJO_STORAGE";
|
|
|
1631 |
|
|
|
1632 |
// create the table that holds our data
|
|
|
1633 |
try{
|
|
|
1634 |
dojox.sql("CREATE TABLE IF NOT EXISTS " + this.TABLE_NAME + "( "
|
|
|
1635 |
+ " namespace TEXT, "
|
|
|
1636 |
+ " key TEXT, "
|
|
|
1637 |
+ " value TEXT "
|
|
|
1638 |
+ ")"
|
|
|
1639 |
);
|
|
|
1640 |
dojox.sql("CREATE UNIQUE INDEX IF NOT EXISTS namespace_key_index"
|
|
|
1641 |
+ " ON " + this.TABLE_NAME
|
|
|
1642 |
+ " (namespace, key)");
|
|
|
1643 |
}catch(e){
|
|
|
1644 |
console.debug("dojox.storage.GearsStorageProvider.initialize:", e);
|
|
|
1645 |
|
|
|
1646 |
this.initialized = false; // we were unable to initialize
|
|
|
1647 |
dojox.storage.manager.loaded();
|
|
|
1648 |
return;
|
|
|
1649 |
}
|
|
|
1650 |
|
|
|
1651 |
// indicate that this storage provider is now loaded
|
|
|
1652 |
this.initialized = true;
|
|
|
1653 |
dojox.storage.manager.loaded();
|
|
|
1654 |
},
|
|
|
1655 |
|
|
|
1656 |
isAvailable: function(){
|
|
|
1657 |
// is Google Gears available and defined?
|
|
|
1658 |
return this._available = dojo.isGears;
|
|
|
1659 |
},
|
|
|
1660 |
|
|
|
1661 |
put: function(key, value, resultsHandler, namespace){
|
|
|
1662 |
if(this.isValidKey(key) == false){
|
|
|
1663 |
throw new Error("Invalid key given: " + key);
|
|
|
1664 |
}
|
|
|
1665 |
namespace = namespace||this.DEFAULT_NAMESPACE;
|
|
|
1666 |
|
|
|
1667 |
// serialize the value;
|
|
|
1668 |
// handle strings differently so they have better performance
|
|
|
1669 |
if(dojo.isString(value)){
|
|
|
1670 |
value = "string:" + value;
|
|
|
1671 |
}else{
|
|
|
1672 |
value = dojo.toJson(value);
|
|
|
1673 |
}
|
|
|
1674 |
|
|
|
1675 |
// try to store the value
|
|
|
1676 |
try{
|
|
|
1677 |
dojox.sql("DELETE FROM " + this.TABLE_NAME
|
|
|
1678 |
+ " WHERE namespace = ? AND key = ?",
|
|
|
1679 |
namespace, key);
|
|
|
1680 |
dojox.sql("INSERT INTO " + this.TABLE_NAME
|
|
|
1681 |
+ " VALUES (?, ?, ?)",
|
|
|
1682 |
namespace, key, value);
|
|
|
1683 |
}catch(e){
|
|
|
1684 |
// indicate we failed
|
|
|
1685 |
console.debug("dojox.storage.GearsStorageProvider.put:", e);
|
|
|
1686 |
resultsHandler(this.FAILED, key, e.toString());
|
|
|
1687 |
return;
|
|
|
1688 |
}
|
|
|
1689 |
|
|
|
1690 |
if(resultsHandler){
|
|
|
1691 |
resultsHandler(dojox.storage.SUCCESS, key, null);
|
|
|
1692 |
}
|
|
|
1693 |
},
|
|
|
1694 |
|
|
|
1695 |
get: function(key, namespace){
|
|
|
1696 |
if(this.isValidKey(key) == false){
|
|
|
1697 |
throw new Error("Invalid key given: " + key);
|
|
|
1698 |
}
|
|
|
1699 |
namespace = namespace||this.DEFAULT_NAMESPACE;
|
|
|
1700 |
|
|
|
1701 |
// try to find this key in the database
|
|
|
1702 |
var results = dojox.sql("SELECT * FROM " + this.TABLE_NAME
|
|
|
1703 |
+ " WHERE namespace = ? AND "
|
|
|
1704 |
+ " key = ?",
|
|
|
1705 |
namespace, key);
|
|
|
1706 |
if(!results.length){
|
|
|
1707 |
return null;
|
|
|
1708 |
}else{
|
|
|
1709 |
results = results[0].value;
|
|
|
1710 |
}
|
|
|
1711 |
|
|
|
1712 |
// destringify the content back into a
|
|
|
1713 |
// real JavaScript object;
|
|
|
1714 |
// handle strings differently so they have better performance
|
|
|
1715 |
if(dojo.isString(results) && (/^string:/.test(results))){
|
|
|
1716 |
results = results.substring("string:".length);
|
|
|
1717 |
}else{
|
|
|
1718 |
results = dojo.fromJson(results);
|
|
|
1719 |
}
|
|
|
1720 |
|
|
|
1721 |
return results;
|
|
|
1722 |
},
|
|
|
1723 |
|
|
|
1724 |
getNamespaces: function(){
|
|
|
1725 |
var results = [ dojox.storage.DEFAULT_NAMESPACE ];
|
|
|
1726 |
|
|
|
1727 |
var rs = dojox.sql("SELECT namespace FROM " + this.TABLE_NAME
|
|
|
1728 |
+ " DESC GROUP BY namespace");
|
|
|
1729 |
for(var i = 0; i < rs.length; i++){
|
|
|
1730 |
if(rs[i].namespace != dojox.storage.DEFAULT_NAMESPACE){
|
|
|
1731 |
results.push(rs[i].namespace);
|
|
|
1732 |
}
|
|
|
1733 |
}
|
|
|
1734 |
|
|
|
1735 |
return results;
|
|
|
1736 |
},
|
|
|
1737 |
|
|
|
1738 |
getKeys: function(namespace){
|
|
|
1739 |
namespace = namespace||this.DEFAULT_NAMESPACE;
|
|
|
1740 |
if(this.isValidKey(namespace) == false){
|
|
|
1741 |
throw new Error("Invalid namespace given: " + namespace);
|
|
|
1742 |
}
|
|
|
1743 |
|
|
|
1744 |
var rs = dojox.sql("SELECT key FROM " + this.TABLE_NAME
|
|
|
1745 |
+ " WHERE namespace = ?",
|
|
|
1746 |
namespace);
|
|
|
1747 |
|
|
|
1748 |
var results = [];
|
|
|
1749 |
for(var i = 0; i < rs.length; i++){
|
|
|
1750 |
results.push(rs[i].key);
|
|
|
1751 |
}
|
|
|
1752 |
|
|
|
1753 |
return results;
|
|
|
1754 |
},
|
|
|
1755 |
|
|
|
1756 |
clear: function(namespace){
|
|
|
1757 |
if(this.isValidKey(namespace) == false){
|
|
|
1758 |
throw new Error("Invalid namespace given: " + namespace);
|
|
|
1759 |
}
|
|
|
1760 |
namespace = namespace||this.DEFAULT_NAMESPACE;
|
|
|
1761 |
|
|
|
1762 |
dojox.sql("DELETE FROM " + this.TABLE_NAME
|
|
|
1763 |
+ " WHERE namespace = ?",
|
|
|
1764 |
namespace);
|
|
|
1765 |
},
|
|
|
1766 |
|
|
|
1767 |
remove: function(key, namespace){
|
|
|
1768 |
namespace = namespace||this.DEFAULT_NAMESPACE;
|
|
|
1769 |
|
|
|
1770 |
dojox.sql("DELETE FROM " + this.TABLE_NAME
|
|
|
1771 |
+ " WHERE namespace = ? AND"
|
|
|
1772 |
+ " key = ?",
|
|
|
1773 |
namespace,
|
|
|
1774 |
key);
|
|
|
1775 |
},
|
|
|
1776 |
|
|
|
1777 |
putMultiple: function(keys, values, resultsHandler, namespace) {
|
|
|
1778 |
if(this.isValidKeyArray(keys) === false
|
|
|
1779 |
|| ! values instanceof Array
|
|
|
1780 |
|| keys.length != values.length){
|
|
|
1781 |
throw new Error("Invalid arguments: keys = ["
|
|
|
1782 |
+ keys + "], values = [" + values + "]");
|
|
|
1783 |
}
|
|
|
1784 |
|
|
|
1785 |
if(namespace == null || typeof namespace == "undefined"){
|
|
|
1786 |
namespace = dojox.storage.DEFAULT_NAMESPACE;
|
|
|
1787 |
}
|
|
|
1788 |
|
|
|
1789 |
if(this.isValidKey(namespace) == false){
|
|
|
1790 |
throw new Error("Invalid namespace given: " + namespace);
|
|
|
1791 |
}
|
|
|
1792 |
|
|
|
1793 |
this._statusHandler = resultsHandler;
|
|
|
1794 |
|
|
|
1795 |
// try to store the value
|
|
|
1796 |
try{
|
|
|
1797 |
dojox.sql.open();
|
|
|
1798 |
dojox.sql.db.execute("BEGIN TRANSACTION");
|
|
|
1799 |
var _stmt = "REPLACE INTO " + this.TABLE_NAME + " VALUES (?, ?, ?)";
|
|
|
1800 |
for(var i=0;i<keys.length;i++) {
|
|
|
1801 |
// serialize the value;
|
|
|
1802 |
// handle strings differently so they have better performance
|
|
|
1803 |
var value = values[i];
|
|
|
1804 |
if(dojo.isString(value)){
|
|
|
1805 |
value = "string:" + value;
|
|
|
1806 |
}else{
|
|
|
1807 |
value = dojo.toJson(value);
|
|
|
1808 |
}
|
|
|
1809 |
|
|
|
1810 |
dojox.sql.db.execute( _stmt,
|
|
|
1811 |
[namespace, keys[i], value]);
|
|
|
1812 |
}
|
|
|
1813 |
dojox.sql.db.execute("COMMIT TRANSACTION");
|
|
|
1814 |
dojox.sql.close();
|
|
|
1815 |
}catch(e){
|
|
|
1816 |
// indicate we failed
|
|
|
1817 |
console.debug("dojox.storage.GearsStorageProvider.putMultiple:", e);
|
|
|
1818 |
if(resultsHandler){
|
|
|
1819 |
resultsHandler(this.FAILED, keys, e.toString());
|
|
|
1820 |
}
|
|
|
1821 |
return;
|
|
|
1822 |
}
|
|
|
1823 |
|
|
|
1824 |
if(resultsHandler){
|
|
|
1825 |
resultsHandler(dojox.storage.SUCCESS, key, null);
|
|
|
1826 |
}
|
|
|
1827 |
},
|
|
|
1828 |
|
|
|
1829 |
getMultiple: function(keys, namespace){
|
|
|
1830 |
// TODO: Maybe use SELECT IN instead
|
|
|
1831 |
|
|
|
1832 |
if(this.isValidKeyArray(keys) === false){
|
|
|
1833 |
throw new ("Invalid key array given: " + keys);
|
|
|
1834 |
}
|
|
|
1835 |
|
|
|
1836 |
if(namespace == null || typeof namespace == "undefined"){
|
|
|
1837 |
namespace = dojox.storage.DEFAULT_NAMESPACE;
|
|
|
1838 |
}
|
|
|
1839 |
|
|
|
1840 |
if(this.isValidKey(namespace) == false){
|
|
|
1841 |
throw new Error("Invalid namespace given: " + namespace);
|
|
|
1842 |
}
|
|
|
1843 |
|
|
|
1844 |
var _stmt = "SELECT * FROM " + this.TABLE_NAME +
|
|
|
1845 |
" WHERE namespace = ? AND " + " key = ?";
|
|
|
1846 |
|
|
|
1847 |
var results = [];
|
|
|
1848 |
for(var i=0;i<keys.length;i++){
|
|
|
1849 |
var result = dojox.sql( _stmt, namespace, keys[i]);
|
|
|
1850 |
|
|
|
1851 |
if( ! result.length){
|
|
|
1852 |
results[i] = null;
|
|
|
1853 |
}else{
|
|
|
1854 |
result = result[0].value;
|
|
|
1855 |
|
|
|
1856 |
// destringify the content back into a
|
|
|
1857 |
// real JavaScript object;
|
|
|
1858 |
// handle strings differently so they have better performance
|
|
|
1859 |
if(dojo.isString(result) && (/^string:/.test(result))){
|
|
|
1860 |
results[i] = result.substring("string:".length);
|
|
|
1861 |
}else{
|
|
|
1862 |
results[i] = dojo.fromJson(result);
|
|
|
1863 |
}
|
|
|
1864 |
}
|
|
|
1865 |
}
|
|
|
1866 |
|
|
|
1867 |
return results;
|
|
|
1868 |
},
|
|
|
1869 |
|
|
|
1870 |
removeMultiple: function(keys, namespace){
|
|
|
1871 |
namespace = namespace||this.DEFAULT_NAMESPACE;
|
|
|
1872 |
|
|
|
1873 |
dojox.sql.open();
|
|
|
1874 |
dojox.sql.db.execute("BEGIN TRANSACTION");
|
|
|
1875 |
var _stmt = "DELETE FROM " + this.TABLE_NAME + " WHERE namespace = ? AND key = ?";
|
|
|
1876 |
|
|
|
1877 |
for(var i=0;i<keys.length;i++){
|
|
|
1878 |
dojox.sql.db.execute( _stmt,
|
|
|
1879 |
[namespace, keys[i]]);
|
|
|
1880 |
}
|
|
|
1881 |
dojox.sql.db.execute("COMMIT TRANSACTION");
|
|
|
1882 |
dojox.sql.close();
|
|
|
1883 |
},
|
|
|
1884 |
|
|
|
1885 |
isPermanent: function(){ return true; },
|
|
|
1886 |
|
|
|
1887 |
getMaximumSize: function(){ return this.SIZE_NO_LIMIT; },
|
|
|
1888 |
|
|
|
1889 |
hasSettingsUI: function(){ return false; },
|
|
|
1890 |
|
|
|
1891 |
showSettingsUI: function(){
|
|
|
1892 |
throw new Error(this.declaredClass
|
|
|
1893 |
+ " does not support a storage settings user-interface");
|
|
|
1894 |
},
|
|
|
1895 |
|
|
|
1896 |
hideSettingsUI: function(){
|
|
|
1897 |
throw new Error(this.declaredClass
|
|
|
1898 |
+ " does not support a storage settings user-interface");
|
|
|
1899 |
}
|
|
|
1900 |
});
|
|
|
1901 |
|
|
|
1902 |
// register the existence of our storage providers
|
|
|
1903 |
dojox.storage.manager.register("dojox.storage.GearsStorageProvider",
|
|
|
1904 |
new dojox.storage.GearsStorageProvider());
|
|
|
1905 |
|
|
|
1906 |
dojox.storage.manager.initialize();
|
|
|
1907 |
})();
|
|
|
1908 |
}
|
|
|
1909 |
|
|
|
1910 |
}
|
|
|
1911 |
|
|
|
1912 |
if(!dojo._hasResource["dojox.storage._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
1913 |
dojo._hasResource["dojox.storage._common"] = true;
|
|
|
1914 |
dojo.provide("dojox.storage._common");
|
|
|
1915 |
|
|
|
1916 |
|
|
|
1917 |
|
|
|
1918 |
|
|
|
1919 |
|
|
|
1920 |
// FIXME: Find way to set isGears from offline.profile.js file; it didn't
|
|
|
1921 |
// work for me
|
|
|
1922 |
//dojo.requireIf(!dojo.isGears, "dojox.storage.FlashStorageProvider");
|
|
|
1923 |
//dojo.requireIf(!dojo.isGears, "dojox.storage.WhatWGStorageProvider");
|
|
|
1924 |
|
|
|
1925 |
// now that we are loaded and registered tell the storage manager to
|
|
|
1926 |
// initialize itself
|
|
|
1927 |
dojox.storage.manager.initialize();
|
|
|
1928 |
|
|
|
1929 |
}
|
|
|
1930 |
|
|
|
1931 |
if(!dojo._hasResource["dojox.storage"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
1932 |
dojo._hasResource["dojox.storage"] = true;
|
|
|
1933 |
dojo.provide("dojox.storage");
|
|
|
1934 |
|
|
|
1935 |
|
|
|
1936 |
}
|
|
|
1937 |
|
|
|
1938 |
if(!dojo._hasResource["dojox.off.files"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
1939 |
dojo._hasResource["dojox.off.files"] = true;
|
|
|
1940 |
dojo.provide("dojox.off.files");
|
|
|
1941 |
|
|
|
1942 |
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
|
|
|
1943 |
|
|
|
1944 |
// summary:
|
|
|
1945 |
// Helps maintain resources that should be
|
|
|
1946 |
// available offline, such as CSS files.
|
|
|
1947 |
// description:
|
|
|
1948 |
// dojox.off.files makes it easy to indicate
|
|
|
1949 |
// what resources should be available offline,
|
|
|
1950 |
// such as CSS files, JavaScript, HTML, etc.
|
|
|
1951 |
dojox.off.files = {
|
|
|
1952 |
// versionURL: String
|
|
|
1953 |
// An optional file, that if present, records the version
|
|
|
1954 |
// of our bundle of files to make available offline. If this
|
|
|
1955 |
// file is present, and we are not currently debugging,
|
|
|
1956 |
// then we only refresh our offline files if the version has
|
|
|
1957 |
// changed.
|
|
|
1958 |
versionURL: "version.js",
|
|
|
1959 |
|
|
|
1960 |
// listOfURLs: Array
|
|
|
1961 |
// For advanced usage; most developers can ignore this.
|
|
|
1962 |
// Our list of URLs that will be cached and made available
|
|
|
1963 |
// offline.
|
|
|
1964 |
listOfURLs: [],
|
|
|
1965 |
|
|
|
1966 |
// refreshing: boolean
|
|
|
1967 |
// For advanced usage; most developers can ignore this.
|
|
|
1968 |
// Whether we are currently in the middle
|
|
|
1969 |
// of refreshing our list of offline files.
|
|
|
1970 |
refreshing: false,
|
|
|
1971 |
|
|
|
1972 |
_cancelID: null,
|
|
|
1973 |
|
|
|
1974 |
_error: false,
|
|
|
1975 |
_errorMessages: [],
|
|
|
1976 |
_currentFileIndex: 0,
|
|
|
1977 |
_store: null,
|
|
|
1978 |
_doSlurp: false,
|
|
|
1979 |
|
|
|
1980 |
slurp: function(){
|
|
|
1981 |
// summary:
|
|
|
1982 |
// Autoscans the page to find all resources to
|
|
|
1983 |
// cache. This includes scripts, images, CSS, and hyperlinks
|
|
|
1984 |
// to pages that are in the same scheme/port/host as this
|
|
|
1985 |
// page. We also scan the embedded CSS of any stylesheets
|
|
|
1986 |
// to find @import statements and url()'s.
|
|
|
1987 |
// You should call this method from the top-level, outside of
|
|
|
1988 |
// any functions and before the page loads:
|
|
|
1989 |
//
|
|
|
1990 |
// <script>
|
|
|
1991 |
//
|
|
|
1992 |
//
|
|
|
1993 |
//
|
|
|
1994 |
//
|
|
|
1995 |
//
|
|
|
1996 |
// // configure how we should work offline
|
|
|
1997 |
//
|
|
|
1998 |
// // set our application name
|
|
|
1999 |
// dojox.off.ui.appName = "Moxie";
|
|
|
2000 |
//
|
|
|
2001 |
// // automatically "slurp" the page and
|
|
|
2002 |
// // capture the resources we need offline
|
|
|
2003 |
// dojox.off.files.slurp();
|
|
|
2004 |
//
|
|
|
2005 |
// // tell Dojo Offline we are ready for it to initialize itself now
|
|
|
2006 |
// // that we have finished configuring it for our application
|
|
|
2007 |
// dojox.off.initialize();
|
|
|
2008 |
// </script>
|
|
|
2009 |
//
|
|
|
2010 |
// Note that inline styles on elements are not handled (i.e.
|
|
|
2011 |
// if you somehow have an inline style that uses a URL);
|
|
|
2012 |
// object and embed tags are not scanned since their format
|
|
|
2013 |
// differs based on type; and elements created by JavaScript
|
|
|
2014 |
// after page load are not found. For these you must manually
|
|
|
2015 |
// add them with a dojox.off.files.cache() method call.
|
|
|
2016 |
|
|
|
2017 |
// just schedule the slurp once the page is loaded and
|
|
|
2018 |
// Dojo Offline is ready to slurp; dojox.off will call
|
|
|
2019 |
// our _slurp() method before indicating it is finished
|
|
|
2020 |
// loading
|
|
|
2021 |
this._doSlurp = true;
|
|
|
2022 |
},
|
|
|
2023 |
|
|
|
2024 |
cache: function(urlOrList){ /* void */
|
|
|
2025 |
// summary:
|
|
|
2026 |
// Caches a file or list of files to be available offline. This
|
|
|
2027 |
// can either be a full URL, such as http://foobar.com/index.html,
|
|
|
2028 |
// or a relative URL, such as ../index.html. This URL is not
|
|
|
2029 |
// actually cached until dojox.off.sync.synchronize() is called.
|
|
|
2030 |
// urlOrList: String or Array[]
|
|
|
2031 |
// A URL of a file to cache or an Array of Strings of files to
|
|
|
2032 |
// cache
|
|
|
2033 |
|
|
|
2034 |
//console.debug("dojox.off.files.cache, urlOrList="+urlOrList);
|
|
|
2035 |
|
|
|
2036 |
if(dojo.isString(urlOrList)){
|
|
|
2037 |
var url = this._trimAnchor(urlOrList+"");
|
|
|
2038 |
if(!this.isAvailable(url)){
|
|
|
2039 |
this.listOfURLs.push(url);
|
|
|
2040 |
}
|
|
|
2041 |
}else if(urlOrList instanceof dojo._Url){
|
|
|
2042 |
var url = this._trimAnchor(urlOrList.uri);
|
|
|
2043 |
if(!this.isAvailable(url)){
|
|
|
2044 |
this.listOfURLs.push(url);
|
|
|
2045 |
}
|
|
|
2046 |
}else{
|
|
|
2047 |
dojo.forEach(urlOrList, function(url){
|
|
|
2048 |
url = this._trimAnchor(url);
|
|
|
2049 |
if(!this.isAvailable(url)){
|
|
|
2050 |
this.listOfURLs.push(url);
|
|
|
2051 |
}
|
|
|
2052 |
}, this);
|
|
|
2053 |
}
|
|
|
2054 |
},
|
|
|
2055 |
|
|
|
2056 |
printURLs: function(){
|
|
|
2057 |
// summary:
|
|
|
2058 |
// A helper function that will dump and print out
|
|
|
2059 |
// all of the URLs that are cached for offline
|
|
|
2060 |
// availability. This can help with debugging if you
|
|
|
2061 |
// are trying to make sure that all of your URLs are
|
|
|
2062 |
// available offline
|
|
|
2063 |
console.debug("The following URLs are cached for offline use:");
|
|
|
2064 |
dojo.forEach(this.listOfURLs, function(i){
|
|
|
2065 |
console.debug(i);
|
|
|
2066 |
});
|
|
|
2067 |
},
|
|
|
2068 |
|
|
|
2069 |
remove: function(url){ /* void */
|
|
|
2070 |
// summary:
|
|
|
2071 |
// Removes a URL from the list of files to cache.
|
|
|
2072 |
// description:
|
|
|
2073 |
// Removes a URL from the list of URLs to cache. Note that this
|
|
|
2074 |
// does not actually remove the file from the offline cache;
|
|
|
2075 |
// instead, it just prevents us from refreshing this file at a
|
|
|
2076 |
// later time, so that it will naturally time out and be removed
|
|
|
2077 |
// from the offline cache
|
|
|
2078 |
// url: String
|
|
|
2079 |
// The URL to remove
|
|
|
2080 |
for(var i = 0; i < this.listOfURLs.length; i++){
|
|
|
2081 |
if(this.listOfURLs[i] == url){
|
|
|
2082 |
this.listOfURLs = this.listOfURLs.splice(i, 1);
|
|
|
2083 |
break;
|
|
|
2084 |
}
|
|
|
2085 |
}
|
|
|
2086 |
},
|
|
|
2087 |
|
|
|
2088 |
isAvailable: function(url){ /* boolean */
|
|
|
2089 |
// summary:
|
|
|
2090 |
// Determines whether the given resource is available offline.
|
|
|
2091 |
// url: String
|
|
|
2092 |
// The URL to check
|
|
|
2093 |
for(var i = 0; i < this.listOfURLs.length; i++){
|
|
|
2094 |
if(this.listOfURLs[i] == url){
|
|
|
2095 |
return true;
|
|
|
2096 |
}
|
|
|
2097 |
}
|
|
|
2098 |
|
|
|
2099 |
return false;
|
|
|
2100 |
},
|
|
|
2101 |
|
|
|
2102 |
refresh: function(callback){ /* void */
|
|
|
2103 |
//console.debug("dojox.off.files.refresh");
|
|
|
2104 |
// summary:
|
|
|
2105 |
// For advanced usage; most developers can ignore this.
|
|
|
2106 |
// Refreshes our list of offline resources,
|
|
|
2107 |
// making them available offline.
|
|
|
2108 |
// callback: Function
|
|
|
2109 |
// A callback that receives two arguments: whether an error
|
|
|
2110 |
// occurred, which is a boolean; and an array of error message strings
|
|
|
2111 |
// with details on errors encountered. If no error occured then message is
|
|
|
2112 |
// empty array with length 0.
|
|
|
2113 |
try{
|
|
|
2114 |
if(djConfig.isDebug){
|
|
|
2115 |
this.printURLs();
|
|
|
2116 |
}
|
|
|
2117 |
|
|
|
2118 |
this.refreshing = true;
|
|
|
2119 |
|
|
|
2120 |
if(this.versionURL){
|
|
|
2121 |
this._getVersionInfo(function(oldVersion, newVersion, justDebugged){
|
|
|
2122 |
//console.warn("getVersionInfo, oldVersion="+oldVersion+", newVersion="+newVersion
|
|
|
2123 |
// + ", justDebugged="+justDebugged+", isDebug="+djConfig.isDebug);
|
|
|
2124 |
if(djConfig.isDebug || !newVersion || justDebugged
|
|
|
2125 |
|| !oldVersion || oldVersion != newVersion){
|
|
|
2126 |
console.warn("Refreshing offline file list");
|
|
|
2127 |
this._doRefresh(callback, newVersion);
|
|
|
2128 |
}else{
|
|
|
2129 |
console.warn("No need to refresh offline file list");
|
|
|
2130 |
callback(false, []);
|
|
|
2131 |
}
|
|
|
2132 |
});
|
|
|
2133 |
}else{
|
|
|
2134 |
console.warn("Refreshing offline file list");
|
|
|
2135 |
this._doRefresh(callback);
|
|
|
2136 |
}
|
|
|
2137 |
}catch(e){
|
|
|
2138 |
this.refreshing = false;
|
|
|
2139 |
|
|
|
2140 |
// can't refresh files -- core operation --
|
|
|
2141 |
// fail fast
|
|
|
2142 |
dojox.off.coreOpFailed = true;
|
|
|
2143 |
dojox.off.enabled = false;
|
|
|
2144 |
dojox.off.onFrameworkEvent("coreOperationFailed");
|
|
|
2145 |
}
|
|
|
2146 |
},
|
|
|
2147 |
|
|
|
2148 |
abortRefresh: function(){
|
|
|
2149 |
// summary:
|
|
|
2150 |
// For advanced usage; most developers can ignore this.
|
|
|
2151 |
// Aborts and cancels a refresh.
|
|
|
2152 |
if(!this.refreshing){
|
|
|
2153 |
return;
|
|
|
2154 |
}
|
|
|
2155 |
|
|
|
2156 |
this._store.abortCapture(this._cancelID);
|
|
|
2157 |
this.refreshing = false;
|
|
|
2158 |
},
|
|
|
2159 |
|
|
|
2160 |
_slurp: function(){
|
|
|
2161 |
if(!this._doSlurp){
|
|
|
2162 |
return;
|
|
|
2163 |
}
|
|
|
2164 |
|
|
|
2165 |
var handleUrl = dojo.hitch(this, function(url){
|
|
|
2166 |
if(this._sameLocation(url)){
|
|
|
2167 |
this.cache(url);
|
|
|
2168 |
}
|
|
|
2169 |
});
|
|
|
2170 |
|
|
|
2171 |
handleUrl(window.location.href);
|
|
|
2172 |
|
|
|
2173 |
dojo.query("script").forEach(function(i){
|
|
|
2174 |
try{
|
|
|
2175 |
handleUrl(i.getAttribute("src"));
|
|
|
2176 |
}catch(exp){
|
|
|
2177 |
//console.debug("dojox.off.files.slurp 'script' error: "
|
|
|
2178 |
// + exp.message||exp);
|
|
|
2179 |
}
|
|
|
2180 |
});
|
|
|
2181 |
|
|
|
2182 |
dojo.query("link").forEach(function(i){
|
|
|
2183 |
try{
|
|
|
2184 |
if(!i.getAttribute("rel")
|
|
|
2185 |
|| i.getAttribute("rel").toLowerCase() != "stylesheet"){
|
|
|
2186 |
return;
|
|
|
2187 |
}
|
|
|
2188 |
|
|
|
2189 |
handleUrl(i.getAttribute("href"));
|
|
|
2190 |
}catch(exp){
|
|
|
2191 |
//console.debug("dojox.off.files.slurp 'link' error: "
|
|
|
2192 |
// + exp.message||exp);
|
|
|
2193 |
}
|
|
|
2194 |
});
|
|
|
2195 |
|
|
|
2196 |
dojo.query("img").forEach(function(i){
|
|
|
2197 |
try{
|
|
|
2198 |
handleUrl(i.getAttribute("src"));
|
|
|
2199 |
}catch(exp){
|
|
|
2200 |
//console.debug("dojox.off.files.slurp 'img' error: "
|
|
|
2201 |
// + exp.message||exp);
|
|
|
2202 |
}
|
|
|
2203 |
});
|
|
|
2204 |
|
|
|
2205 |
dojo.query("a").forEach(function(i){
|
|
|
2206 |
try{
|
|
|
2207 |
handleUrl(i.getAttribute("href"));
|
|
|
2208 |
}catch(exp){
|
|
|
2209 |
//console.debug("dojox.off.files.slurp 'a' error: "
|
|
|
2210 |
// + exp.message||exp);
|
|
|
2211 |
}
|
|
|
2212 |
});
|
|
|
2213 |
|
|
|
2214 |
// FIXME: handle 'object' and 'embed' tag
|
|
|
2215 |
|
|
|
2216 |
// parse our style sheets for inline URLs and imports
|
|
|
2217 |
dojo.forEach(document.styleSheets, function(sheet){
|
|
|
2218 |
try{
|
|
|
2219 |
if(sheet.cssRules){ // Firefox
|
|
|
2220 |
dojo.forEach(sheet.cssRules, function(rule){
|
|
|
2221 |
var text = rule.cssText;
|
|
|
2222 |
if(text){
|
|
|
2223 |
var matches = text.match(/url\(\s*([^\) ]*)\s*\)/i);
|
|
|
2224 |
if(!matches){
|
|
|
2225 |
return;
|
|
|
2226 |
}
|
|
|
2227 |
|
|
|
2228 |
for(var i = 1; i < matches.length; i++){
|
|
|
2229 |
handleUrl(matches[i])
|
|
|
2230 |
}
|
|
|
2231 |
}
|
|
|
2232 |
});
|
|
|
2233 |
}else if(sheet.cssText){ // IE
|
|
|
2234 |
var matches;
|
|
|
2235 |
var text = sheet.cssText.toString();
|
|
|
2236 |
// unfortunately, using RegExp.exec seems to be flakey
|
|
|
2237 |
// for looping across multiple lines on IE using the
|
|
|
2238 |
// global flag, so we have to simulate it
|
|
|
2239 |
var lines = text.split(/\f|\r|\n/);
|
|
|
2240 |
for(var i = 0; i < lines.length; i++){
|
|
|
2241 |
matches = lines[i].match(/url\(\s*([^\) ]*)\s*\)/i);
|
|
|
2242 |
if(matches && matches.length){
|
|
|
2243 |
handleUrl(matches[1]);
|
|
|
2244 |
}
|
|
|
2245 |
}
|
|
|
2246 |
}
|
|
|
2247 |
}catch(exp){
|
|
|
2248 |
//console.debug("dojox.off.files.slurp stylesheet parse error: "
|
|
|
2249 |
// + exp.message||exp);
|
|
|
2250 |
}
|
|
|
2251 |
});
|
|
|
2252 |
|
|
|
2253 |
//this.printURLs();
|
|
|
2254 |
},
|
|
|
2255 |
|
|
|
2256 |
_sameLocation: function(url){
|
|
|
2257 |
if(!url){ return false; }
|
|
|
2258 |
|
|
|
2259 |
// filter out anchors
|
|
|
2260 |
if(url.length && url.charAt(0) == "#"){
|
|
|
2261 |
return false;
|
|
|
2262 |
}
|
|
|
2263 |
|
|
|
2264 |
// FIXME: dojo._Url should be made public;
|
|
|
2265 |
// it's functionality is very useful for
|
|
|
2266 |
// parsing URLs correctly, which is hard to
|
|
|
2267 |
// do right
|
|
|
2268 |
url = new dojo._Url(url);
|
|
|
2269 |
|
|
|
2270 |
// totally relative -- ../../someFile.html
|
|
|
2271 |
if(!url.scheme && !url.port && !url.host){
|
|
|
2272 |
return true;
|
|
|
2273 |
}
|
|
|
2274 |
|
|
|
2275 |
// scheme relative with port specified -- brad.com:8080
|
|
|
2276 |
if(!url.scheme && url.host && url.port
|
|
|
2277 |
&& window.location.hostname == url.host
|
|
|
2278 |
&& window.location.port == url.port){
|
|
|
2279 |
return true;
|
|
|
2280 |
}
|
|
|
2281 |
|
|
|
2282 |
// scheme relative with no-port specified -- brad.com
|
|
|
2283 |
if(!url.scheme && url.host && !url.port
|
|
|
2284 |
&& window.location.hostname == url.host
|
|
|
2285 |
&& window.location.port == 80){
|
|
|
2286 |
return true;
|
|
|
2287 |
}
|
|
|
2288 |
|
|
|
2289 |
// else we have everything
|
|
|
2290 |
return window.location.protocol == (url.scheme + ":")
|
|
|
2291 |
&& window.location.hostname == url.host
|
|
|
2292 |
&& (window.location.port == url.port || !window.location.port && !url.port);
|
|
|
2293 |
},
|
|
|
2294 |
|
|
|
2295 |
_trimAnchor: function(url){
|
|
|
2296 |
return url.replace(/\#.*$/, "");
|
|
|
2297 |
},
|
|
|
2298 |
|
|
|
2299 |
_doRefresh: function(callback, newVersion){
|
|
|
2300 |
// get our local server
|
|
|
2301 |
var localServer;
|
|
|
2302 |
try{
|
|
|
2303 |
localServer = google.gears.factory.create("beta.localserver", "1.0");
|
|
|
2304 |
}catch(exp){
|
|
|
2305 |
dojo.setObject("google.gears.denied", true);
|
|
|
2306 |
dojox.off.onFrameworkEvent("coreOperationFailed");
|
|
|
2307 |
throw "Google Gears must be allowed to run";
|
|
|
2308 |
}
|
|
|
2309 |
|
|
|
2310 |
var storeName = "dot_store_"
|
|
|
2311 |
+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
|
|
|
2312 |
|
|
|
2313 |
// refresh everything by simply removing
|
|
|
2314 |
// any older stores
|
|
|
2315 |
localServer.removeStore(storeName);
|
|
|
2316 |
|
|
|
2317 |
// open/create the resource store
|
|
|
2318 |
localServer.openStore(storeName);
|
|
|
2319 |
var store = localServer.createStore(storeName);
|
|
|
2320 |
this._store = store;
|
|
|
2321 |
|
|
|
2322 |
// add our list of files to capture
|
|
|
2323 |
var self = this;
|
|
|
2324 |
this._currentFileIndex = 0;
|
|
|
2325 |
this._cancelID = store.capture(this.listOfURLs, function(url, success, captureId){
|
|
|
2326 |
//console.debug("store.capture, url="+url+", success="+success);
|
|
|
2327 |
if(!success && self.refreshing){
|
|
|
2328 |
self._cancelID = null;
|
|
|
2329 |
self.refreshing = false;
|
|
|
2330 |
var errorMsgs = [];
|
|
|
2331 |
errorMsgs.push("Unable to capture: " + url);
|
|
|
2332 |
callback(true, errorMsgs);
|
|
|
2333 |
return;
|
|
|
2334 |
}else if(success){
|
|
|
2335 |
self._currentFileIndex++;
|
|
|
2336 |
}
|
|
|
2337 |
|
|
|
2338 |
if(success && self._currentFileIndex >= self.listOfURLs.length){
|
|
|
2339 |
self._cancelID = null;
|
|
|
2340 |
self.refreshing = false;
|
|
|
2341 |
if(newVersion){
|
|
|
2342 |
dojox.storage.put("oldVersion", newVersion, null,
|
|
|
2343 |
dojox.off.STORAGE_NAMESPACE);
|
|
|
2344 |
}
|
|
|
2345 |
dojox.storage.put("justDebugged", djConfig.isDebug, null,
|
|
|
2346 |
dojox.off.STORAGE_NAMESPACE);
|
|
|
2347 |
callback(false, []);
|
|
|
2348 |
}
|
|
|
2349 |
});
|
|
|
2350 |
},
|
|
|
2351 |
|
|
|
2352 |
_getVersionInfo: function(callback){
|
|
|
2353 |
var justDebugged = dojox.storage.get("justDebugged",
|
|
|
2354 |
dojox.off.STORAGE_NAMESPACE);
|
|
|
2355 |
var oldVersion = dojox.storage.get("oldVersion",
|
|
|
2356 |
dojox.off.STORAGE_NAMESPACE);
|
|
|
2357 |
var newVersion = null;
|
|
|
2358 |
|
|
|
2359 |
callback = dojo.hitch(this, callback);
|
|
|
2360 |
|
|
|
2361 |
dojo.xhrGet({
|
|
|
2362 |
url: this.versionURL + "?browserbust=" + new Date().getTime(),
|
|
|
2363 |
timeout: 5 * 1000,
|
|
|
2364 |
handleAs: "javascript",
|
|
|
2365 |
error: function(err){
|
|
|
2366 |
//console.warn("dojox.off.files._getVersionInfo, err=",err);
|
|
|
2367 |
dojox.storage.remove("oldVersion", dojox.off.STORAGE_NAMESPACE);
|
|
|
2368 |
dojox.storage.remove("justDebugged", dojox.off.STORAGE_NAMESPACE);
|
|
|
2369 |
callback(oldVersion, newVersion, justDebugged);
|
|
|
2370 |
},
|
|
|
2371 |
load: function(data){
|
|
|
2372 |
//console.warn("dojox.off.files._getVersionInfo, load=",data);
|
|
|
2373 |
|
|
|
2374 |
// some servers incorrectly return 404's
|
|
|
2375 |
// as a real page
|
|
|
2376 |
if(data){
|
|
|
2377 |
newVersion = data;
|
|
|
2378 |
}
|
|
|
2379 |
|
|
|
2380 |
callback(oldVersion, newVersion, justDebugged);
|
|
|
2381 |
}
|
|
|
2382 |
});
|
|
|
2383 |
}
|
|
|
2384 |
}
|
|
|
2385 |
|
|
|
2386 |
}
|
|
|
2387 |
|
|
|
2388 |
if(!dojo._hasResource["dojox.off.sync"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
2389 |
dojo._hasResource["dojox.off.sync"] = true;
|
|
|
2390 |
dojo.provide("dojox.off.sync");
|
|
|
2391 |
|
|
|
2392 |
|
|
|
2393 |
|
|
|
2394 |
|
|
|
2395 |
|
|
|
2396 |
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
|
|
|
2397 |
|
|
|
2398 |
// summary:
|
|
|
2399 |
// Exposes syncing functionality to offline applications
|
|
|
2400 |
dojo.mixin(dojox.off.sync, {
|
|
|
2401 |
// isSyncing: boolean
|
|
|
2402 |
// Whether we are in the middle of a syncing session.
|
|
|
2403 |
isSyncing: false,
|
|
|
2404 |
|
|
|
2405 |
// cancelled: boolean
|
|
|
2406 |
// Whether we were cancelled during our last sync request or not. If
|
|
|
2407 |
// we are cancelled, then successful will be false.
|
|
|
2408 |
cancelled: false,
|
|
|
2409 |
|
|
|
2410 |
// successful: boolean
|
|
|
2411 |
// Whether the last sync was successful or not. If false, an error
|
|
|
2412 |
// occurred.
|
|
|
2413 |
successful: true,
|
|
|
2414 |
|
|
|
2415 |
// details: String[]
|
|
|
2416 |
// Details on the sync. If the sync was successful, this will carry
|
|
|
2417 |
// any conflict or merging messages that might be available; if the
|
|
|
2418 |
// sync was unsuccessful, this will have an error message. For both
|
|
|
2419 |
// of these, this should be an array of Strings, where each string
|
|
|
2420 |
// carries details on the sync.
|
|
|
2421 |
// Example:
|
|
|
2422 |
// dojox.off.sync.details = ["The document 'foobar' had conflicts - yours one",
|
|
|
2423 |
// "The document 'hello world' was automatically merged"];
|
|
|
2424 |
details: [],
|
|
|
2425 |
|
|
|
2426 |
// error: boolean
|
|
|
2427 |
// Whether an error occurred during the syncing process.
|
|
|
2428 |
error: false,
|
|
|
2429 |
|
|
|
2430 |
// actions: dojox.off.sync.ActionLog
|
|
|
2431 |
// Our ActionLog that we store offline actions into for later
|
|
|
2432 |
// replaying when we go online
|
|
|
2433 |
actions: null,
|
|
|
2434 |
|
|
|
2435 |
// autoSync: boolean
|
|
|
2436 |
// For advanced usage; most developers can ignore this.
|
|
|
2437 |
// Whether we do automatically sync on page load or when we go online.
|
|
|
2438 |
// If true we do, if false syncing must be manually initiated.
|
|
|
2439 |
// Defaults to true.
|
|
|
2440 |
autoSync: true,
|
|
|
2441 |
|
|
|
2442 |
// summary:
|
|
|
2443 |
// An event handler that is called during the syncing process with
|
|
|
2444 |
// the state of syncing. It is important that you connect to this
|
|
|
2445 |
// method and respond to certain sync events, especially the
|
|
|
2446 |
// "download" event.
|
|
|
2447 |
// description:
|
|
|
2448 |
// This event handler is called during the syncing process. You can
|
|
|
2449 |
// do a dojo.connect to receive sync feedback:
|
|
|
2450 |
//
|
|
|
2451 |
// dojo.connect(dojox.off.sync, "onSync", someFunc);
|
|
|
2452 |
//
|
|
|
2453 |
// You will receive one argument, which is the type of the event
|
|
|
2454 |
// and which can have the following values.
|
|
|
2455 |
//
|
|
|
2456 |
// The most common two types that you need to care about are "download"
|
|
|
2457 |
// and "finished", especially if you are using the default
|
|
|
2458 |
// Dojo Offline UI widget that does the hard work of informing
|
|
|
2459 |
// the user through the UI about what is occuring during syncing.
|
|
|
2460 |
//
|
|
|
2461 |
// If you receive the "download" event, you should make a network call
|
|
|
2462 |
// to retrieve and store your data somehow for offline access. The
|
|
|
2463 |
// "finished" event indicates that syncing is done. An example:
|
|
|
2464 |
//
|
|
|
2465 |
// dojo.connect(dojox.off.sync, "onSync", function(type){
|
|
|
2466 |
// if(type == "download"){
|
|
|
2467 |
// // make a network call to download some data
|
|
|
2468 |
// // for use offline
|
|
|
2469 |
// dojo.xhrGet({
|
|
|
2470 |
// url: "downloadData.php",
|
|
|
2471 |
// handleAs: "javascript",
|
|
|
2472 |
// error: function(err){
|
|
|
2473 |
// dojox.off.sync.finishedDownloading(false, "Can't download data");
|
|
|
2474 |
// },
|
|
|
2475 |
// load: function(data){
|
|
|
2476 |
// // store our data
|
|
|
2477 |
// dojox.storage.put("myData", data);
|
|
|
2478 |
//
|
|
|
2479 |
// // indicate we are finished downloading
|
|
|
2480 |
// dojox.off.sync.finishedDownloading(true);
|
|
|
2481 |
// }
|
|
|
2482 |
// });
|
|
|
2483 |
// }else if(type == "finished"){
|
|
|
2484 |
// // update UI somehow to indicate we are finished,
|
|
|
2485 |
// // such as using the download data to change the
|
|
|
2486 |
// // available data
|
|
|
2487 |
// }
|
|
|
2488 |
// })
|
|
|
2489 |
//
|
|
|
2490 |
// Here is the full list of event types if you want to do deep
|
|
|
2491 |
// customization, such as updating your UI to display the progress
|
|
|
2492 |
// of syncing (note that the default Dojo Offline UI widget does
|
|
|
2493 |
// this for you if you choose to pull that in). Most of these
|
|
|
2494 |
// are only appropriate for advanced usage and can be safely
|
|
|
2495 |
// ignored:
|
|
|
2496 |
//
|
|
|
2497 |
// * "start"
|
|
|
2498 |
// syncing has started
|
|
|
2499 |
// * "refreshFiles"
|
|
|
2500 |
// syncing will begin refreshing
|
|
|
2501 |
// our offline file cache
|
|
|
2502 |
// * "upload"
|
|
|
2503 |
// syncing will begin uploading
|
|
|
2504 |
// any local data changes we have on the client.
|
|
|
2505 |
// This event is fired before we fire
|
|
|
2506 |
// the dojox.off.sync.actions.onReplay event for
|
|
|
2507 |
// each action to replay; use it to completely
|
|
|
2508 |
// over-ride the replaying behavior and prevent
|
|
|
2509 |
// it entirely, perhaps rolling your own sync
|
|
|
2510 |
// protocol if needed.
|
|
|
2511 |
// * "download"
|
|
|
2512 |
// syncing will begin downloading any new data that is
|
|
|
2513 |
// needed into persistent storage. Applications are required to
|
|
|
2514 |
// implement this themselves, storing the required data into
|
|
|
2515 |
// persistent local storage using Dojo Storage.
|
|
|
2516 |
// * "finished"
|
|
|
2517 |
// syncing is finished; this
|
|
|
2518 |
// will be called whether an error ocurred or not; check
|
|
|
2519 |
// dojox.off.sync.successful and dojox.off.sync.error for sync details
|
|
|
2520 |
// * "cancel"
|
|
|
2521 |
// Fired when canceling has been initiated; canceling will be
|
|
|
2522 |
// attempted, followed by the sync event "finished".
|
|
|
2523 |
onSync: function(/* String */ type){},
|
|
|
2524 |
|
|
|
2525 |
synchronize: function(){ /* void */
|
|
|
2526 |
// summary: Starts synchronizing
|
|
|
2527 |
|
|
|
2528 |
//dojo.debug("synchronize");
|
|
|
2529 |
if(this.isSyncing || dojox.off.goingOnline || (!dojox.off.isOnline)){
|
|
|
2530 |
return;
|
|
|
2531 |
}
|
|
|
2532 |
|
|
|
2533 |
this.isSyncing = true;
|
|
|
2534 |
this.successful = false;
|
|
|
2535 |
this.details = [];
|
|
|
2536 |
this.cancelled = false;
|
|
|
2537 |
|
|
|
2538 |
this.start();
|
|
|
2539 |
},
|
|
|
2540 |
|
|
|
2541 |
cancel: function(){ /* void */
|
|
|
2542 |
// summary:
|
|
|
2543 |
// Attempts to cancel this sync session
|
|
|
2544 |
|
|
|
2545 |
if(!this.isSyncing){ return; }
|
|
|
2546 |
|
|
|
2547 |
this.cancelled = true;
|
|
|
2548 |
if(dojox.off.files.refreshing){
|
|
|
2549 |
dojox.off.files.abortRefresh();
|
|
|
2550 |
}
|
|
|
2551 |
|
|
|
2552 |
this.onSync("cancel");
|
|
|
2553 |
},
|
|
|
2554 |
|
|
|
2555 |
finishedDownloading: function(successful /* boolean? */,
|
|
|
2556 |
errorMessage /* String? */){
|
|
|
2557 |
// summary:
|
|
|
2558 |
// Applications call this method from their
|
|
|
2559 |
// after getting a "download" event in
|
|
|
2560 |
// dojox.off.sync.onSync to signal that
|
|
|
2561 |
// they are finished downloading any data
|
|
|
2562 |
// that should be available offline
|
|
|
2563 |
// successful: boolean?
|
|
|
2564 |
// Whether our downloading was successful or not.
|
|
|
2565 |
// If not present, defaults to true.
|
|
|
2566 |
// errorMessage: String?
|
|
|
2567 |
// If unsuccessful, a message explaining why
|
|
|
2568 |
if(typeof successful == "undefined"){
|
|
|
2569 |
successful = true;
|
|
|
2570 |
}
|
|
|
2571 |
|
|
|
2572 |
if(!successful){
|
|
|
2573 |
this.successful = false;
|
|
|
2574 |
this.details.push(errorMessage);
|
|
|
2575 |
this.error = true;
|
|
|
2576 |
}
|
|
|
2577 |
|
|
|
2578 |
this.finished();
|
|
|
2579 |
},
|
|
|
2580 |
|
|
|
2581 |
start: function(){ /* void */
|
|
|
2582 |
// summary:
|
|
|
2583 |
// For advanced usage; most developers can ignore this.
|
|
|
2584 |
// Called at the start of the syncing process. Advanced
|
|
|
2585 |
// developers can over-ride this method to use their
|
|
|
2586 |
// own sync mechanism to start syncing.
|
|
|
2587 |
|
|
|
2588 |
if(this.cancelled){
|
|
|
2589 |
this.finished();
|
|
|
2590 |
return;
|
|
|
2591 |
}
|
|
|
2592 |
this.onSync("start");
|
|
|
2593 |
this.refreshFiles();
|
|
|
2594 |
},
|
|
|
2595 |
|
|
|
2596 |
refreshFiles: function(){ /* void */
|
|
|
2597 |
// summary:
|
|
|
2598 |
// For advanced usage; most developers can ignore this.
|
|
|
2599 |
// Called when we are going to refresh our list
|
|
|
2600 |
// of offline files during syncing. Advanced developers
|
|
|
2601 |
// can over-ride this method to do some advanced magic related to
|
|
|
2602 |
// refreshing files.
|
|
|
2603 |
|
|
|
2604 |
//dojo.debug("refreshFiles");
|
|
|
2605 |
if(this.cancelled){
|
|
|
2606 |
this.finished();
|
|
|
2607 |
return;
|
|
|
2608 |
}
|
|
|
2609 |
|
|
|
2610 |
this.onSync("refreshFiles");
|
|
|
2611 |
|
|
|
2612 |
dojox.off.files.refresh(dojo.hitch(this, function(error, errorMessages){
|
|
|
2613 |
if(error){
|
|
|
2614 |
this.error = true;
|
|
|
2615 |
this.successful = false;
|
|
|
2616 |
for(var i = 0; i < errorMessages.length; i++){
|
|
|
2617 |
this.details.push(errorMessages[i]);
|
|
|
2618 |
}
|
|
|
2619 |
|
|
|
2620 |
// even if we get an error while syncing files,
|
|
|
2621 |
// keep syncing so we can upload and download
|
|
|
2622 |
// data
|
|
|
2623 |
}
|
|
|
2624 |
|
|
|
2625 |
this.upload();
|
|
|
2626 |
}));
|
|
|
2627 |
},
|
|
|
2628 |
|
|
|
2629 |
upload: function(){ /* void */
|
|
|
2630 |
// summary:
|
|
|
2631 |
// For advanced usage; most developers can ignore this.
|
|
|
2632 |
// Called when syncing wants to upload data. Advanced
|
|
|
2633 |
// developers can over-ride this method to completely
|
|
|
2634 |
// throw away the Action Log and replaying system
|
|
|
2635 |
// and roll their own advanced sync mechanism if needed.
|
|
|
2636 |
|
|
|
2637 |
if(this.cancelled){
|
|
|
2638 |
this.finished();
|
|
|
2639 |
return;
|
|
|
2640 |
}
|
|
|
2641 |
|
|
|
2642 |
this.onSync("upload");
|
|
|
2643 |
|
|
|
2644 |
// when we are done uploading start downloading
|
|
|
2645 |
dojo.connect(this.actions, "onReplayFinished", this, this.download);
|
|
|
2646 |
|
|
|
2647 |
// replay the actions log
|
|
|
2648 |
this.actions.replay();
|
|
|
2649 |
},
|
|
|
2650 |
|
|
|
2651 |
download: function(){ /* void */
|
|
|
2652 |
// summary:
|
|
|
2653 |
// For advanced usage; most developers can ignore this.
|
|
|
2654 |
// Called when syncing wants to download data. Advanced
|
|
|
2655 |
// developers can over-ride this method to use their
|
|
|
2656 |
// own sync mechanism.
|
|
|
2657 |
|
|
|
2658 |
if(this.cancelled){
|
|
|
2659 |
this.finished();
|
|
|
2660 |
return;
|
|
|
2661 |
}
|
|
|
2662 |
|
|
|
2663 |
// apps should respond to the "download"
|
|
|
2664 |
// event to download their data; when done
|
|
|
2665 |
// they must call dojox.off.sync.finishedDownloading()
|
|
|
2666 |
this.onSync("download");
|
|
|
2667 |
},
|
|
|
2668 |
|
|
|
2669 |
finished: function(){ /* void */
|
|
|
2670 |
// summary:
|
|
|
2671 |
// For advanced usage; most developers can ignore this.
|
|
|
2672 |
// Called when syncing is finished. Advanced
|
|
|
2673 |
// developers can over-ride this method to clean
|
|
|
2674 |
// up after finishing their own sync
|
|
|
2675 |
// mechanism they might have rolled.
|
|
|
2676 |
this.isSyncing = false;
|
|
|
2677 |
|
|
|
2678 |
this.successful = (!this.cancelled && !this.error);
|
|
|
2679 |
|
|
|
2680 |
this.onSync("finished");
|
|
|
2681 |
},
|
|
|
2682 |
|
|
|
2683 |
_save: function(callback){
|
|
|
2684 |
this.actions._save(function(){
|
|
|
2685 |
callback();
|
|
|
2686 |
});
|
|
|
2687 |
},
|
|
|
2688 |
|
|
|
2689 |
_load: function(callback){
|
|
|
2690 |
this.actions._load(function(){
|
|
|
2691 |
callback();
|
|
|
2692 |
});
|
|
|
2693 |
}
|
|
|
2694 |
});
|
|
|
2695 |
|
|
|
2696 |
|
|
|
2697 |
// summary:
|
|
|
2698 |
// A class that records actions taken by a user when they are offline,
|
|
|
2699 |
// suitable for replaying when the network reappears.
|
|
|
2700 |
// description:
|
|
|
2701 |
// The basic idea behind this method is to record user actions that would
|
|
|
2702 |
// normally have to contact a server into an action log when we are
|
|
|
2703 |
// offline, so that later when we are online we can simply replay this log
|
|
|
2704 |
// in the order user actions happened so that they can be executed against
|
|
|
2705 |
// the server, causing synchronization to happen.
|
|
|
2706 |
//
|
|
|
2707 |
// When we replay, for each of the actions that were added, we call a
|
|
|
2708 |
// method named onReplay that applications should connect to and
|
|
|
2709 |
// which will be called over and over for each of our actions --
|
|
|
2710 |
// applications should take the offline action
|
|
|
2711 |
// information and use it to talk to a server to have this action
|
|
|
2712 |
// actually happen online, 'syncing' themselves with the server.
|
|
|
2713 |
//
|
|
|
2714 |
// For example, if the action was "update" with the item that was updated, we
|
|
|
2715 |
// might call some RESTian server API that exists for updating an item in
|
|
|
2716 |
// our application. The server could either then do sophisticated merging
|
|
|
2717 |
// and conflict resolution on the server side, for example, allowing you
|
|
|
2718 |
// to pop up a custom merge UI, or could do automatic merging or nothing
|
|
|
2719 |
// of the sort. When you are finished with this particular action, your
|
|
|
2720 |
// application is then required to call continueReplay() on the actionLog object
|
|
|
2721 |
// passed to onReplay() to continue replaying the action log, or haltReplay()
|
|
|
2722 |
// with the reason for halting to completely stop the syncing/replaying
|
|
|
2723 |
// process.
|
|
|
2724 |
//
|
|
|
2725 |
// For example, imagine that we have a web application that allows us to add
|
|
|
2726 |
// contacts. If we are offline, and we update a contact, we would add an action;
|
|
|
2727 |
// imagine that the user has to click an Update button after changing the values
|
|
|
2728 |
// for a given contact:
|
|
|
2729 |
//
|
|
|
2730 |
// dojox.off.whenOffline(dojo.byId("updateButton"), "onclick", function(evt){
|
|
|
2731 |
// // get the updated customer values
|
|
|
2732 |
// var customer = getCustomerValues();
|
|
|
2733 |
//
|
|
|
2734 |
// // we are offline -- just record this action
|
|
|
2735 |
// var action = {name: "update", customer: customer};
|
|
|
2736 |
// dojox.off.sync.actions.add(action)
|
|
|
2737 |
//
|
|
|
2738 |
// // persist this customer data into local storage as well
|
|
|
2739 |
// dojox.storage.put(customer.name, customer);
|
|
|
2740 |
// })
|
|
|
2741 |
//
|
|
|
2742 |
// Then, when we go back online, the dojox.off.sync.actions.onReplay event
|
|
|
2743 |
// will fire over and over, once for each action that was recorded while offline:
|
|
|
2744 |
//
|
|
|
2745 |
// dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
|
|
|
2746 |
// // called once for each action we added while offline, in the order
|
|
|
2747 |
// // they were added
|
|
|
2748 |
// if(action.name == "update"){
|
|
|
2749 |
// var customer = action.customer;
|
|
|
2750 |
//
|
|
|
2751 |
// // call some network service to update this customer
|
|
|
2752 |
// dojo.xhrPost({
|
|
|
2753 |
// url: "updateCustomer.php",
|
|
|
2754 |
// content: {customer: dojo.toJson(customer)},
|
|
|
2755 |
// error: function(err){
|
|
|
2756 |
// actionLog.haltReplay(err);
|
|
|
2757 |
// },
|
|
|
2758 |
// load: function(data){
|
|
|
2759 |
// actionLog.continueReplay();
|
|
|
2760 |
// }
|
|
|
2761 |
// })
|
|
|
2762 |
// }
|
|
|
2763 |
// })
|
|
|
2764 |
//
|
|
|
2765 |
// Note that the actions log is always automatically persisted locally while using it, so
|
|
|
2766 |
// that if the user closes the browser or it crashes the actions will safely be stored
|
|
|
2767 |
// for later replaying.
|
|
|
2768 |
dojo.declare("dojox.off.sync.ActionLog", null, {
|
|
|
2769 |
// entries: Array
|
|
|
2770 |
// An array of our action entries, where each one is simply a custom
|
|
|
2771 |
// object literal that were passed to add() when this action entry
|
|
|
2772 |
// was added.
|
|
|
2773 |
entries: [],
|
|
|
2774 |
|
|
|
2775 |
// reasonHalted: String
|
|
|
2776 |
// If we halted, the reason why
|
|
|
2777 |
reasonHalted: null,
|
|
|
2778 |
|
|
|
2779 |
// isReplaying: boolean
|
|
|
2780 |
// If true, we are in the middle of replaying a command log; if false,
|
|
|
2781 |
// then we are not
|
|
|
2782 |
isReplaying: false,
|
|
|
2783 |
|
|
|
2784 |
// autoSave: boolean
|
|
|
2785 |
// Whether we automatically save the action log after each call to
|
|
|
2786 |
// add(); defaults to true. For applications that are rapidly adding
|
|
|
2787 |
// many action log entries in a short period of time, it can be
|
|
|
2788 |
// useful to set this to false and simply call save() yourself when
|
|
|
2789 |
// you are ready to persist your command log -- otherwise performance
|
|
|
2790 |
// could be slow as the default action is to attempt to persist the
|
|
|
2791 |
// actions log constantly with calls to add().
|
|
|
2792 |
autoSave: true,
|
|
|
2793 |
|
|
|
2794 |
add: function(action /* Object */){ /* void */
|
|
|
2795 |
// summary:
|
|
|
2796 |
// Adds an action to our action log
|
|
|
2797 |
// description:
|
|
|
2798 |
// This method will add an action to our
|
|
|
2799 |
// action log, later to be replayed when we
|
|
|
2800 |
// go from offline to online. 'action'
|
|
|
2801 |
// will be available when this action is
|
|
|
2802 |
// replayed and will be passed to onReplay.
|
|
|
2803 |
//
|
|
|
2804 |
// Example usage:
|
|
|
2805 |
//
|
|
|
2806 |
// dojox.off.sync.log.add({actionName: "create", itemType: "document",
|
|
|
2807 |
// {title: "Message", content: "Hello World"}});
|
|
|
2808 |
//
|
|
|
2809 |
// The object literal is simply a custom object appropriate
|
|
|
2810 |
// for our application -- it can be anything that preserves the state
|
|
|
2811 |
// of a user action that will be executed when we go back online
|
|
|
2812 |
// and replay this log. In the above example,
|
|
|
2813 |
// "create" is the name of this action; "documents" is the
|
|
|
2814 |
// type of item this command is operating on, such as documents, contacts,
|
|
|
2815 |
// tasks, etc.; and the final argument is the document that was created.
|
|
|
2816 |
|
|
|
2817 |
if(this.isReplaying){
|
|
|
2818 |
throw "Programming error: you can not call "
|
|
|
2819 |
+ "dojox.off.sync.actions.add() while "
|
|
|
2820 |
+ "we are replaying an action log";
|
|
|
2821 |
}
|
|
|
2822 |
|
|
|
2823 |
this.entries.push(action);
|
|
|
2824 |
|
|
|
2825 |
// save our updated state into persistent
|
|
|
2826 |
// storage
|
|
|
2827 |
if(this.autoSave){
|
|
|
2828 |
this._save();
|
|
|
2829 |
}
|
|
|
2830 |
},
|
|
|
2831 |
|
|
|
2832 |
onReplay: function(action /* Object */,
|
|
|
2833 |
actionLog /* dojox.off.sync.ActionLog */){ /* void */
|
|
|
2834 |
// summary:
|
|
|
2835 |
// Called when we replay our log, for each of our action
|
|
|
2836 |
// entries.
|
|
|
2837 |
// action: Object
|
|
|
2838 |
// A custom object literal representing an action for this
|
|
|
2839 |
// application, such as
|
|
|
2840 |
// {actionName: "create", item: {title: "message", content: "hello world"}}
|
|
|
2841 |
// actionLog: dojox.off.sync.ActionLog
|
|
|
2842 |
// A reference to the dojox.off.sync.actions log so that developers
|
|
|
2843 |
// can easily call actionLog.continueReplay() or actionLog.haltReplay().
|
|
|
2844 |
// description:
|
|
|
2845 |
// This callback should be connected to by applications so that
|
|
|
2846 |
// they can sync themselves when we go back online:
|
|
|
2847 |
//
|
|
|
2848 |
// dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
|
|
|
2849 |
// // do something
|
|
|
2850 |
// })
|
|
|
2851 |
//
|
|
|
2852 |
// When we replay our action log, this callback is called for each
|
|
|
2853 |
// of our action entries in the order they were added. The
|
|
|
2854 |
// 'action' entry that was passed to add() for this action will
|
|
|
2855 |
// also be passed in to onReplay, so that applications can use this information
|
|
|
2856 |
// to do their syncing, such as contacting a server web-service
|
|
|
2857 |
// to create a new item, for example.
|
|
|
2858 |
//
|
|
|
2859 |
// Inside the method you connected to onReplay, you should either call
|
|
|
2860 |
// actionLog.haltReplay(reason) if an error occurred and you would like to halt
|
|
|
2861 |
// action replaying or actionLog.continueReplay() to have the action log
|
|
|
2862 |
// continue replaying its log and proceed to the next action;
|
|
|
2863 |
// the reason you must call these is the action you execute inside of
|
|
|
2864 |
// onAction will probably be asynchronous, since it will be talking on
|
|
|
2865 |
// the network, and you should call one of these two methods based on
|
|
|
2866 |
// the result of your network call.
|
|
|
2867 |
},
|
|
|
2868 |
|
|
|
2869 |
length: function(){ /* Number */
|
|
|
2870 |
// summary:
|
|
|
2871 |
// Returns the length of this
|
|
|
2872 |
// action log
|
|
|
2873 |
return this.entries.length;
|
|
|
2874 |
},
|
|
|
2875 |
|
|
|
2876 |
haltReplay: function(reason /* String */){ /* void */
|
|
|
2877 |
// summary: Halts replaying this command log.
|
|
|
2878 |
// reason: String
|
|
|
2879 |
// The reason we halted.
|
|
|
2880 |
// description:
|
|
|
2881 |
// This method is called as we are replaying an action log; it
|
|
|
2882 |
// can be called from dojox.off.sync.actions.onReplay, for
|
|
|
2883 |
// example, for an application to indicate an error occurred
|
|
|
2884 |
// while replaying this action, halting further processing of
|
|
|
2885 |
// the action log. Note that any action log entries that
|
|
|
2886 |
// were processed before have their effects retained (i.e.
|
|
|
2887 |
// they are not rolled back), while the action entry that was
|
|
|
2888 |
// halted stays in our list of actions to later be replayed.
|
|
|
2889 |
if(!this.isReplaying){
|
|
|
2890 |
return;
|
|
|
2891 |
}
|
|
|
2892 |
|
|
|
2893 |
if(reason){
|
|
|
2894 |
this.reasonHalted = reason.toString();
|
|
|
2895 |
}
|
|
|
2896 |
|
|
|
2897 |
// save the state of our action log, then
|
|
|
2898 |
// tell anyone who is interested that we are
|
|
|
2899 |
// done when we are finished saving
|
|
|
2900 |
if(this.autoSave){
|
|
|
2901 |
var self = this;
|
|
|
2902 |
this._save(function(){
|
|
|
2903 |
self.isReplaying = false;
|
|
|
2904 |
self.onReplayFinished();
|
|
|
2905 |
});
|
|
|
2906 |
}else{
|
|
|
2907 |
this.isReplaying = false;
|
|
|
2908 |
this.onReplayFinished();
|
|
|
2909 |
}
|
|
|
2910 |
},
|
|
|
2911 |
|
|
|
2912 |
continueReplay: function(){ /* void */
|
|
|
2913 |
// summary:
|
|
|
2914 |
// Indicates that we should continue processing out list of
|
|
|
2915 |
// actions.
|
|
|
2916 |
// description:
|
|
|
2917 |
// This method is called by applications that have overridden
|
|
|
2918 |
// dojox.off.sync.actions.onReplay() to continue replaying our
|
|
|
2919 |
// action log after the application has finished handling the
|
|
|
2920 |
// current action.
|
|
|
2921 |
if(!this.isReplaying){
|
|
|
2922 |
return;
|
|
|
2923 |
}
|
|
|
2924 |
|
|
|
2925 |
// shift off the old action we just ran
|
|
|
2926 |
this.entries.shift();
|
|
|
2927 |
|
|
|
2928 |
// are we done?
|
|
|
2929 |
if(!this.entries.length){
|
|
|
2930 |
// save the state of our action log, then
|
|
|
2931 |
// tell anyone who is interested that we are
|
|
|
2932 |
// done when we are finished saving
|
|
|
2933 |
if(this.autoSave){
|
|
|
2934 |
var self = this;
|
|
|
2935 |
this._save(function(){
|
|
|
2936 |
self.isReplaying = false;
|
|
|
2937 |
self.onReplayFinished();
|
|
|
2938 |
});
|
|
|
2939 |
return;
|
|
|
2940 |
}else{
|
|
|
2941 |
this.isReplaying = false;
|
|
|
2942 |
this.onReplayFinished();
|
|
|
2943 |
return;
|
|
|
2944 |
}
|
|
|
2945 |
}
|
|
|
2946 |
|
|
|
2947 |
// get the next action
|
|
|
2948 |
var nextAction = this.entries[0];
|
|
|
2949 |
this.onReplay(nextAction, this);
|
|
|
2950 |
},
|
|
|
2951 |
|
|
|
2952 |
clear: function(){ /* void */
|
|
|
2953 |
// summary:
|
|
|
2954 |
// Completely clears this action log of its entries
|
|
|
2955 |
|
|
|
2956 |
if(this.isReplaying){
|
|
|
2957 |
return;
|
|
|
2958 |
}
|
|
|
2959 |
|
|
|
2960 |
this.entries = [];
|
|
|
2961 |
|
|
|
2962 |
// save our updated state into persistent
|
|
|
2963 |
// storage
|
|
|
2964 |
if(this.autoSave){
|
|
|
2965 |
this._save();
|
|
|
2966 |
}
|
|
|
2967 |
},
|
|
|
2968 |
|
|
|
2969 |
replay: function(){ /* void */
|
|
|
2970 |
// summary:
|
|
|
2971 |
// For advanced usage; most developers can ignore this.
|
|
|
2972 |
// Replays all of the commands that have been
|
|
|
2973 |
// cached in this command log when we go back online;
|
|
|
2974 |
// onCommand will be called for each command we have
|
|
|
2975 |
|
|
|
2976 |
if(this.isReplaying){
|
|
|
2977 |
return;
|
|
|
2978 |
}
|
|
|
2979 |
|
|
|
2980 |
this.reasonHalted = null;
|
|
|
2981 |
|
|
|
2982 |
if(!this.entries.length){
|
|
|
2983 |
this.onReplayFinished();
|
|
|
2984 |
return;
|
|
|
2985 |
}
|
|
|
2986 |
|
|
|
2987 |
this.isReplaying = true;
|
|
|
2988 |
|
|
|
2989 |
var nextAction = this.entries[0];
|
|
|
2990 |
this.onReplay(nextAction, this);
|
|
|
2991 |
},
|
|
|
2992 |
|
|
|
2993 |
// onReplayFinished: Function
|
|
|
2994 |
// For advanced usage; most developers can ignore this.
|
|
|
2995 |
// Called when we are finished replaying our commands;
|
|
|
2996 |
// called if we have successfully exhausted all of our
|
|
|
2997 |
// commands, or if an error occurred during replaying.
|
|
|
2998 |
// The default implementation simply continues the
|
|
|
2999 |
// synchronization process. Connect to this to register
|
|
|
3000 |
// for the event:
|
|
|
3001 |
//
|
|
|
3002 |
// dojo.connect(dojox.off.sync.actions, "onReplayFinished",
|
|
|
3003 |
// someFunc)
|
|
|
3004 |
onReplayFinished: function(){
|
|
|
3005 |
},
|
|
|
3006 |
|
|
|
3007 |
toString: function(){
|
|
|
3008 |
var results = "";
|
|
|
3009 |
results += "[";
|
|
|
3010 |
|
|
|
3011 |
for(var i = 0; i < this.entries.length; i++){
|
|
|
3012 |
results += "{";
|
|
|
3013 |
for(var j in this.entries[i]){
|
|
|
3014 |
results += j + ": \"" + this.entries[i][j] + "\"";
|
|
|
3015 |
results += ", ";
|
|
|
3016 |
}
|
|
|
3017 |
results += "}, ";
|
|
|
3018 |
}
|
|
|
3019 |
|
|
|
3020 |
results += "]";
|
|
|
3021 |
|
|
|
3022 |
return results;
|
|
|
3023 |
},
|
|
|
3024 |
|
|
|
3025 |
_save: function(callback){
|
|
|
3026 |
if(!callback){
|
|
|
3027 |
callback = function(){};
|
|
|
3028 |
}
|
|
|
3029 |
|
|
|
3030 |
try{
|
|
|
3031 |
var self = this;
|
|
|
3032 |
var resultsHandler = function(status, key, message){
|
|
|
3033 |
//console.debug("resultsHandler, status="+status+", key="+key+", message="+message);
|
|
|
3034 |
if(status == dojox.storage.FAILED){
|
|
|
3035 |
dojox.off.onFrameworkEvent("save",
|
|
|
3036 |
{status: dojox.storage.FAILED,
|
|
|
3037 |
isCoreSave: true,
|
|
|
3038 |
key: key,
|
|
|
3039 |
value: message,
|
|
|
3040 |
namespace: dojox.off.STORAGE_NAMESPACE});
|
|
|
3041 |
callback();
|
|
|
3042 |
}else if(status == dojox.storage.SUCCESS){
|
|
|
3043 |
callback();
|
|
|
3044 |
}
|
|
|
3045 |
};
|
|
|
3046 |
|
|
|
3047 |
dojox.storage.put("actionlog", this.entries, resultsHandler,
|
|
|
3048 |
dojox.off.STORAGE_NAMESPACE);
|
|
|
3049 |
}catch(exp){
|
|
|
3050 |
console.debug("dojox.off.sync._save: " + exp.message||exp);
|
|
|
3051 |
dojox.off.onFrameworkEvent("save",
|
|
|
3052 |
{status: dojox.storage.FAILED,
|
|
|
3053 |
isCoreSave: true,
|
|
|
3054 |
key: "actionlog",
|
|
|
3055 |
value: this.entries,
|
|
|
3056 |
namespace: dojox.off.STORAGE_NAMESPACE});
|
|
|
3057 |
callback();
|
|
|
3058 |
}
|
|
|
3059 |
},
|
|
|
3060 |
|
|
|
3061 |
_load: function(callback){
|
|
|
3062 |
var entries = dojox.storage.get("actionlog", dojox.off.STORAGE_NAMESPACE);
|
|
|
3063 |
|
|
|
3064 |
if(!entries){
|
|
|
3065 |
entries = [];
|
|
|
3066 |
}
|
|
|
3067 |
|
|
|
3068 |
this.entries = entries;
|
|
|
3069 |
|
|
|
3070 |
callback();
|
|
|
3071 |
}
|
|
|
3072 |
}
|
|
|
3073 |
);
|
|
|
3074 |
|
|
|
3075 |
dojox.off.sync.actions = new dojox.off.sync.ActionLog();
|
|
|
3076 |
|
|
|
3077 |
}
|
|
|
3078 |
|
|
|
3079 |
if(!dojo._hasResource["dojox.off._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
3080 |
dojo._hasResource["dojox.off._common"] = true;
|
|
|
3081 |
dojo.provide("dojox.off._common");
|
|
|
3082 |
|
|
|
3083 |
|
|
|
3084 |
|
|
|
3085 |
|
|
|
3086 |
|
|
|
3087 |
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
|
|
|
3088 |
|
|
|
3089 |
// summary:
|
|
|
3090 |
// dojox.off is the main object for offline applications.
|
|
|
3091 |
dojo.mixin(dojox.off, {
|
|
|
3092 |
// isOnline: boolean
|
|
|
3093 |
// true if we are online, false if not
|
|
|
3094 |
isOnline: false,
|
|
|
3095 |
|
|
|
3096 |
// NET_CHECK: int
|
|
|
3097 |
// For advanced usage; most developers can ignore this.
|
|
|
3098 |
// Time in seconds on how often we should check the status of the
|
|
|
3099 |
// network with an automatic background timer. The current default
|
|
|
3100 |
// is 5 seconds.
|
|
|
3101 |
NET_CHECK: 5,
|
|
|
3102 |
|
|
|
3103 |
// STORAGE_NAMESPACE: String
|
|
|
3104 |
// For advanced usage; most developers can ignore this.
|
|
|
3105 |
// The namespace we use to save core data into Dojo Storage.
|
|
|
3106 |
STORAGE_NAMESPACE: "_dot",
|
|
|
3107 |
|
|
|
3108 |
// enabled: boolean
|
|
|
3109 |
// For advanced usage; most developers can ignore this.
|
|
|
3110 |
// Whether offline ability is enabled or not. Defaults to true.
|
|
|
3111 |
enabled: true,
|
|
|
3112 |
|
|
|
3113 |
// availabilityURL: String
|
|
|
3114 |
// For advanced usage; most developers can ignore this.
|
|
|
3115 |
// The URL to check for site availability. We do a GET request on
|
|
|
3116 |
// this URL to check for site availability. By default we check for a
|
|
|
3117 |
// simple text file in src/off/network_check.txt that has one value
|
|
|
3118 |
// it, the value '1'.
|
|
|
3119 |
availabilityURL: dojo.moduleUrl("dojox", "off/network_check.txt"),
|
|
|
3120 |
|
|
|
3121 |
// goingOnline: boolean
|
|
|
3122 |
// For advanced usage; most developers can ignore this.
|
|
|
3123 |
// True if we are attempting to go online, false otherwise
|
|
|
3124 |
goingOnline: false,
|
|
|
3125 |
|
|
|
3126 |
// coreOpFailed: boolean
|
|
|
3127 |
// For advanced usage; most developers can ignore this.
|
|
|
3128 |
// A flag set by the Dojo Offline framework that indicates that the
|
|
|
3129 |
// user denied some operation that required the offline cache or an
|
|
|
3130 |
// operation failed in some critical way that was unrecoverable. For
|
|
|
3131 |
// example, if the offline cache is Google Gears and we try to get a
|
|
|
3132 |
// Gears database, a popup window appears asking the user whether they
|
|
|
3133 |
// will approve or deny this request. If the user denies the request,
|
|
|
3134 |
// and we are doing some operation that is core to Dojo Offline, then
|
|
|
3135 |
// we set this flag to 'true'. This flag causes a 'fail fast'
|
|
|
3136 |
// condition, turning off offline ability.
|
|
|
3137 |
coreOpFailed: false,
|
|
|
3138 |
|
|
|
3139 |
// doNetChecking: boolean
|
|
|
3140 |
// For advanced usage; most developers can ignore this.
|
|
|
3141 |
// Whether to have a timing interval in the background doing automatic
|
|
|
3142 |
// network checks at regular intervals; the length of time between
|
|
|
3143 |
// checks is controlled by dojox.off.NET_CHECK. Defaults to true.
|
|
|
3144 |
doNetChecking: true,
|
|
|
3145 |
|
|
|
3146 |
// hasOfflineCache: boolean
|
|
|
3147 |
// For advanced usage; most developers can ignore this.
|
|
|
3148 |
// Determines if an offline cache is available or installed; an
|
|
|
3149 |
// offline cache is a facility that can truely cache offline
|
|
|
3150 |
// resources, such as JavaScript, HTML, etc. in such a way that they
|
|
|
3151 |
// won't be removed from the cache inappropriately like a browser
|
|
|
3152 |
// cache would. If this is false then an offline cache will be
|
|
|
3153 |
// installed. Only Google Gears is currently supported as an offline
|
|
|
3154 |
// cache. Future possible offline caches include Firefox 3.
|
|
|
3155 |
hasOfflineCache: null,
|
|
|
3156 |
|
|
|
3157 |
// browserRestart: boolean
|
|
|
3158 |
// For advanced usage; most developers can ignore this.
|
|
|
3159 |
// If true, the browser must be restarted to register the existence of
|
|
|
3160 |
// a new host added offline (from a call to addHostOffline); if false,
|
|
|
3161 |
// then nothing is needed.
|
|
|
3162 |
browserRestart: false,
|
|
|
3163 |
|
|
|
3164 |
_STORAGE_APP_NAME: window.location.href.replace(/[^0-9A-Za-z_]/g, "_"),
|
|
|
3165 |
|
|
|
3166 |
_initializeCalled: false,
|
|
|
3167 |
_storageLoaded: false,
|
|
|
3168 |
_pageLoaded: false,
|
|
|
3169 |
|
|
|
3170 |
onLoad: function(){
|
|
|
3171 |
// summary:
|
|
|
3172 |
// Called when Dojo Offline can be used.
|
|
|
3173 |
// description:
|
|
|
3174 |
// Do a dojo.connect to this to know when you can
|
|
|
3175 |
// start using Dojo Offline:
|
|
|
3176 |
// dojo.connect(dojox.off, "onLoad", myFunc);
|
|
|
3177 |
},
|
|
|
3178 |
|
|
|
3179 |
onNetwork: function(type){
|
|
|
3180 |
// summary:
|
|
|
3181 |
// Called when our on- or offline- status changes.
|
|
|
3182 |
// description:
|
|
|
3183 |
// If we move online, then this method is called with the
|
|
|
3184 |
// value "online". If we move offline, then this method is
|
|
|
3185 |
// called with the value "offline". You can connect to this
|
|
|
3186 |
// method to do add your own behavior:
|
|
|
3187 |
//
|
|
|
3188 |
// dojo.connect(dojox.off, "onNetwork", someFunc)
|
|
|
3189 |
//
|
|
|
3190 |
// Note that if you are using the default Dojo Offline UI
|
|
|
3191 |
// widget that most of the on- and off-line notification
|
|
|
3192 |
// and syncing is automatically handled and provided to the
|
|
|
3193 |
// user.
|
|
|
3194 |
// type: String
|
|
|
3195 |
// Either "online" or "offline".
|
|
|
3196 |
},
|
|
|
3197 |
|
|
|
3198 |
initialize: function(){ /* void */
|
|
|
3199 |
// summary:
|
|
|
3200 |
// Called when a Dojo Offline-enabled application is finished
|
|
|
3201 |
// configuring Dojo Offline, and is ready for Dojo Offline to
|
|
|
3202 |
// initialize itself.
|
|
|
3203 |
// description:
|
|
|
3204 |
// When an application has finished filling out the variables Dojo
|
|
|
3205 |
// Offline needs to work, such as dojox.off.ui.appName, it must
|
|
|
3206 |
// this method to tell Dojo Offline to initialize itself.
|
|
|
3207 |
|
|
|
3208 |
// Note:
|
|
|
3209 |
// This method is needed for a rare edge case. In some conditions,
|
|
|
3210 |
// especially if we are dealing with a compressed Dojo build, the
|
|
|
3211 |
// entire Dojo Offline subsystem might initialize itself and be
|
|
|
3212 |
// running even before the JavaScript for an application has had a
|
|
|
3213 |
// chance to run and configure Dojo Offline, causing Dojo Offline
|
|
|
3214 |
// to have incorrect initialization parameters for a given app,
|
|
|
3215 |
// such as no value for dojox.off.ui.appName. This method is
|
|
|
3216 |
// provided to prevent this scenario, to slightly 'slow down' Dojo
|
|
|
3217 |
// Offline so it can be configured before running off and doing
|
|
|
3218 |
// its thing.
|
|
|
3219 |
|
|
|
3220 |
//console.debug("dojox.off.initialize");
|
|
|
3221 |
this._initializeCalled = true;
|
|
|
3222 |
|
|
|
3223 |
if(this._storageLoaded && this._pageLoaded){
|
|
|
3224 |
this._onLoad();
|
|
|
3225 |
}
|
|
|
3226 |
},
|
|
|
3227 |
|
|
|
3228 |
goOffline: function(){ /* void */
|
|
|
3229 |
// summary:
|
|
|
3230 |
// For advanced usage; most developers can ignore this.
|
|
|
3231 |
// Manually goes offline, away from the network.
|
|
|
3232 |
if((dojox.off.sync.isSyncing)||(this.goingOnline)){ return; }
|
|
|
3233 |
|
|
|
3234 |
this.goingOnline = false;
|
|
|
3235 |
this.isOnline = false;
|
|
|
3236 |
},
|
|
|
3237 |
|
|
|
3238 |
goOnline: function(callback){ /* void */
|
|
|
3239 |
// summary:
|
|
|
3240 |
// For advanced usage; most developers can ignore this.
|
|
|
3241 |
// Attempts to go online.
|
|
|
3242 |
// description:
|
|
|
3243 |
// Attempts to go online, making sure this web application's web
|
|
|
3244 |
// site is available. 'callback' is called asychronously with the
|
|
|
3245 |
// result of whether we were able to go online or not.
|
|
|
3246 |
// callback: Function
|
|
|
3247 |
// An optional callback function that will receive one argument:
|
|
|
3248 |
// whether the site is available or not and is boolean. If this
|
|
|
3249 |
// function is not present we call dojo.xoff.onOnline instead if
|
|
|
3250 |
// we are able to go online.
|
|
|
3251 |
|
|
|
3252 |
//console.debug("goOnline");
|
|
|
3253 |
|
|
|
3254 |
if(dojox.off.sync.isSyncing || dojox.off.goingOnline){
|
|
|
3255 |
return;
|
|
|
3256 |
}
|
|
|
3257 |
|
|
|
3258 |
this.goingOnline = true;
|
|
|
3259 |
this.isOnline = false;
|
|
|
3260 |
|
|
|
3261 |
// see if can reach our web application's web site
|
|
|
3262 |
this._isSiteAvailable(callback);
|
|
|
3263 |
},
|
|
|
3264 |
|
|
|
3265 |
onFrameworkEvent: function(type /* String */, saveData /* Object? */){
|
|
|
3266 |
// summary:
|
|
|
3267 |
// For advanced usage; most developers can ignore this.
|
|
|
3268 |
// A standard event handler that can be attached to to find out
|
|
|
3269 |
// about low-level framework events. Most developers will not need to
|
|
|
3270 |
// attach to this method; it is meant for low-level information
|
|
|
3271 |
// that can be useful for updating offline user-interfaces in
|
|
|
3272 |
// exceptional circumstances. The default Dojo Offline UI
|
|
|
3273 |
// widget takes care of most of these situations.
|
|
|
3274 |
// type: String
|
|
|
3275 |
// The type of the event:
|
|
|
3276 |
//
|
|
|
3277 |
// * "offlineCacheInstalled"
|
|
|
3278 |
// An event that is fired when a user
|
|
|
3279 |
// has installed an offline cache after the page has been loaded.
|
|
|
3280 |
// If a user didn't have an offline cache when the page loaded, a
|
|
|
3281 |
// UI of some kind might have prompted them to download one. This
|
|
|
3282 |
// method is called if they have downloaded and installed an
|
|
|
3283 |
// offline cache so a UI can reinitialize itself to begin using
|
|
|
3284 |
// this offline cache.
|
|
|
3285 |
// * "coreOperationFailed"
|
|
|
3286 |
// Fired when a core operation during interaction with the
|
|
|
3287 |
// offline cache is denied by the user. Some offline caches, such
|
|
|
3288 |
// as Google Gears, prompts the user to approve or deny caching
|
|
|
3289 |
// files, using the database, and more. If the user denies a
|
|
|
3290 |
// request that is core to Dojo Offline's operation, we set
|
|
|
3291 |
// dojox.off.coreOpFailed to true and call this method for
|
|
|
3292 |
// listeners that would like to respond some how to Dojo Offline
|
|
|
3293 |
// 'failing fast'.
|
|
|
3294 |
// * "save"
|
|
|
3295 |
// Called whenever the framework saves data into persistent
|
|
|
3296 |
// storage. This could be useful for providing save feedback
|
|
|
3297 |
// or providing appropriate error feedback if saving fails
|
|
|
3298 |
// due to a user not allowing the save to occur
|
|
|
3299 |
// saveData: Object?
|
|
|
3300 |
// If the type was 'save', then a saveData object is provided with
|
|
|
3301 |
// further save information. This object has the following properties:
|
|
|
3302 |
//
|
|
|
3303 |
// * status - dojox.storage.SUCCESS, dojox.storage.PENDING, dojox.storage.FAILED
|
|
|
3304 |
// Whether the save succeeded, whether it is pending based on a UI
|
|
|
3305 |
// dialog asking the user for permission, or whether it failed.
|
|
|
3306 |
//
|
|
|
3307 |
// * isCoreSave - boolean
|
|
|
3308 |
// If true, then this save was for a core piece of data necessary
|
|
|
3309 |
// for the functioning of Dojo Offline. If false, then it is a
|
|
|
3310 |
// piece of normal data being saved for offline access. Dojo
|
|
|
3311 |
// Offline will 'fail fast' if some core piece of data could not
|
|
|
3312 |
// be saved, automatically setting dojox.off.coreOpFailed to
|
|
|
3313 |
// 'true' and dojox.off.enabled to 'false'.
|
|
|
3314 |
//
|
|
|
3315 |
// * key - String
|
|
|
3316 |
// The key that we are attempting to persist
|
|
|
3317 |
//
|
|
|
3318 |
// * value - Object
|
|
|
3319 |
// The object we are trying to persist
|
|
|
3320 |
//
|
|
|
3321 |
// * namespace - String
|
|
|
3322 |
// The Dojo Storage namespace we are saving this key/value pair
|
|
|
3323 |
// into, such as "default", "Documents", "Contacts", etc.
|
|
|
3324 |
// Optional.
|
|
|
3325 |
if(type == "save"){
|
|
|
3326 |
if(saveData.isCoreSave && (saveData.status == dojox.storage.FAILED)){
|
|
|
3327 |
dojox.off.coreOpFailed = true;
|
|
|
3328 |
dojox.off.enabled = false;
|
|
|
3329 |
|
|
|
3330 |
// FIXME: Stop the background network thread
|
|
|
3331 |
dojox.off.onFrameworkEvent("coreOperationFailed");
|
|
|
3332 |
}
|
|
|
3333 |
}else if(type == "coreOperationFailed"){
|
|
|
3334 |
dojox.off.coreOpFailed = true;
|
|
|
3335 |
dojox.off.enabled = false;
|
|
|
3336 |
// FIXME: Stop the background network thread
|
|
|
3337 |
}
|
|
|
3338 |
},
|
|
|
3339 |
|
|
|
3340 |
_checkOfflineCacheAvailable: function(callback){
|
|
|
3341 |
// is a true, offline cache running on this machine?
|
|
|
3342 |
this.hasOfflineCache = dojo.isGears;
|
|
|
3343 |
|
|
|
3344 |
callback();
|
|
|
3345 |
},
|
|
|
3346 |
|
|
|
3347 |
_onLoad: function(){
|
|
|
3348 |
//console.debug("dojox.off._onLoad");
|
|
|
3349 |
|
|
|
3350 |
// both local storage and the page are finished loading
|
|
|
3351 |
|
|
|
3352 |
// cache the Dojo JavaScript -- just use the default dojo.js
|
|
|
3353 |
// name for the most common scenario
|
|
|
3354 |
// FIXME: TEST: Make sure syncing doesn't break if dojo.js
|
|
|
3355 |
// can't be found, or report an error to developer
|
|
|
3356 |
dojox.off.files.cache(dojo.moduleUrl("dojo", "dojo.js"));
|
|
|
3357 |
|
|
|
3358 |
// pull in the files needed by Dojo
|
|
|
3359 |
this._cacheDojoResources();
|
|
|
3360 |
|
|
|
3361 |
// FIXME: need to pull in the firebug lite files here!
|
|
|
3362 |
// workaround or else we will get an error on page load
|
|
|
3363 |
// from Dojo that it can't find 'console.debug' for optimized builds
|
|
|
3364 |
// dojox.off.files.cache(djConfig.baseRelativePath + "src/debug.js");
|
|
|
3365 |
|
|
|
3366 |
// make sure that resources needed by all of our underlying
|
|
|
3367 |
// Dojo Storage storage providers will be available
|
|
|
3368 |
// offline
|
|
|
3369 |
dojox.off.files.cache(dojox.storage.manager.getResourceList());
|
|
|
3370 |
|
|
|
3371 |
// slurp the page if the end-developer wants that
|
|
|
3372 |
dojox.off.files._slurp();
|
|
|
3373 |
|
|
|
3374 |
// see if we have an offline cache; when done, move
|
|
|
3375 |
// on to the rest of our startup tasks
|
|
|
3376 |
this._checkOfflineCacheAvailable(dojo.hitch(this, "_onOfflineCacheChecked"));
|
|
|
3377 |
},
|
|
|
3378 |
|
|
|
3379 |
_onOfflineCacheChecked: function(){
|
|
|
3380 |
// this method is part of our _onLoad series of startup tasks
|
|
|
3381 |
|
|
|
3382 |
// if we have an offline cache, see if we have been added to the
|
|
|
3383 |
// list of available offline web apps yet
|
|
|
3384 |
if(this.hasOfflineCache && this.enabled){
|
|
|
3385 |
// load framework data; when we are finished, continue
|
|
|
3386 |
// initializing ourselves
|
|
|
3387 |
this._load(dojo.hitch(this, "_finishStartingUp"));
|
|
|
3388 |
}else if(this.hasOfflineCache && !this.enabled){
|
|
|
3389 |
// we have an offline cache, but it is disabled for some reason
|
|
|
3390 |
// perhaps due to the user denying a core operation
|
|
|
3391 |
this._finishStartingUp();
|
|
|
3392 |
}else{
|
|
|
3393 |
this._keepCheckingUntilInstalled();
|
|
|
3394 |
}
|
|
|
3395 |
},
|
|
|
3396 |
|
|
|
3397 |
_keepCheckingUntilInstalled: function(){
|
|
|
3398 |
// this method is part of our _onLoad series of startup tasks
|
|
|
3399 |
|
|
|
3400 |
// kick off a background interval that keeps
|
|
|
3401 |
// checking to see if an offline cache has been
|
|
|
3402 |
// installed since this page loaded
|
|
|
3403 |
|
|
|
3404 |
// FIXME: Gears: See if we are installed somehow after the
|
|
|
3405 |
// page has been loaded
|
|
|
3406 |
|
|
|
3407 |
// now continue starting up
|
|
|
3408 |
this._finishStartingUp();
|
|
|
3409 |
},
|
|
|
3410 |
|
|
|
3411 |
_finishStartingUp: function(){
|
|
|
3412 |
//console.debug("dojox.off._finishStartingUp");
|
|
|
3413 |
|
|
|
3414 |
// this method is part of our _onLoad series of startup tasks
|
|
|
3415 |
|
|
|
3416 |
if(!this.hasOfflineCache){
|
|
|
3417 |
this.onLoad();
|
|
|
3418 |
}else if(this.enabled){
|
|
|
3419 |
// kick off a thread to check network status on
|
|
|
3420 |
// a regular basis
|
|
|
3421 |
this._startNetworkThread();
|
|
|
3422 |
|
|
|
3423 |
// try to go online
|
|
|
3424 |
this.goOnline(dojo.hitch(this, function(){
|
|
|
3425 |
//console.debug("Finished trying to go online");
|
|
|
3426 |
// indicate we are ready to be used
|
|
|
3427 |
dojox.off.onLoad();
|
|
|
3428 |
}));
|
|
|
3429 |
}else{ // we are disabled or a core operation failed
|
|
|
3430 |
if(this.coreOpFailed){
|
|
|
3431 |
this.onFrameworkEvent("coreOperationFailed");
|
|
|
3432 |
}else{
|
|
|
3433 |
this.onLoad();
|
|
|
3434 |
}
|
|
|
3435 |
}
|
|
|
3436 |
},
|
|
|
3437 |
|
|
|
3438 |
_onPageLoad: function(){
|
|
|
3439 |
//console.debug("dojox.off._onPageLoad");
|
|
|
3440 |
this._pageLoaded = true;
|
|
|
3441 |
|
|
|
3442 |
if(this._storageLoaded && this._initializeCalled){
|
|
|
3443 |
this._onLoad();
|
|
|
3444 |
}
|
|
|
3445 |
},
|
|
|
3446 |
|
|
|
3447 |
_onStorageLoad: function(){
|
|
|
3448 |
//console.debug("dojox.off._onStorageLoad");
|
|
|
3449 |
this._storageLoaded = true;
|
|
|
3450 |
|
|
|
3451 |
// were we able to initialize storage? if
|
|
|
3452 |
// not, then this is a core operation, and
|
|
|
3453 |
// let's indicate we will need to fail fast
|
|
|
3454 |
if(!dojox.storage.manager.isAvailable()
|
|
|
3455 |
&& dojox.storage.manager.isInitialized()){
|
|
|
3456 |
this.coreOpFailed = true;
|
|
|
3457 |
this.enabled = false;
|
|
|
3458 |
}
|
|
|
3459 |
|
|
|
3460 |
if(this._pageLoaded && this._initializeCalled){
|
|
|
3461 |
this._onLoad();
|
|
|
3462 |
}
|
|
|
3463 |
},
|
|
|
3464 |
|
|
|
3465 |
_isSiteAvailable: function(callback){
|
|
|
3466 |
// summary:
|
|
|
3467 |
// Determines if our web application's website is available.
|
|
|
3468 |
// description:
|
|
|
3469 |
// This method will asychronously determine if our web
|
|
|
3470 |
// application's web site is available, which is a good proxy for
|
|
|
3471 |
// network availability. The URL dojox.off.availabilityURL is
|
|
|
3472 |
// used, which defaults to this site's domain name (ex:
|
|
|
3473 |
// foobar.com). We check for dojox.off.AVAILABILITY_TIMEOUT (in
|
|
|
3474 |
// seconds) and abort after that
|
|
|
3475 |
// callback: Function
|
|
|
3476 |
// An optional callback function that will receive one argument:
|
|
|
3477 |
// whether the site is available or not and is boolean. If this
|
|
|
3478 |
// function is not present we call dojox.off.onNetwork instead if we
|
|
|
3479 |
// are able to go online.
|
|
|
3480 |
dojo.xhrGet({
|
|
|
3481 |
url: this._getAvailabilityURL(),
|
|
|
3482 |
handleAs: "text",
|
|
|
3483 |
timeout: this.NET_CHECK * 1000,
|
|
|
3484 |
error: dojo.hitch(this, function(err){
|
|
|
3485 |
//console.debug("dojox.off._isSiteAvailable.error: " + err);
|
|
|
3486 |
this.goingOnline = false;
|
|
|
3487 |
this.isOnline = false;
|
|
|
3488 |
if(callback){ callback(false); }
|
|
|
3489 |
}),
|
|
|
3490 |
load: dojo.hitch(this, function(data){
|
|
|
3491 |
//console.debug("dojox.off._isSiteAvailable.load, data="+data);
|
|
|
3492 |
this.goingOnline = false;
|
|
|
3493 |
this.isOnline = true;
|
|
|
3494 |
|
|
|
3495 |
if(callback){ callback(true);
|
|
|
3496 |
}else{ this.onNetwork("online"); }
|
|
|
3497 |
})
|
|
|
3498 |
});
|
|
|
3499 |
},
|
|
|
3500 |
|
|
|
3501 |
_startNetworkThread: function(){
|
|
|
3502 |
//console.debug("startNetworkThread");
|
|
|
3503 |
|
|
|
3504 |
// kick off a thread that does periodic
|
|
|
3505 |
// checks on the status of the network
|
|
|
3506 |
if(!this.doNetChecking){
|
|
|
3507 |
return;
|
|
|
3508 |
}
|
|
|
3509 |
|
|
|
3510 |
window.setInterval(dojo.hitch(this, function(){
|
|
|
3511 |
var d = dojo.xhrGet({
|
|
|
3512 |
url: this._getAvailabilityURL(),
|
|
|
3513 |
handleAs: "text",
|
|
|
3514 |
timeout: this.NET_CHECK * 1000,
|
|
|
3515 |
error: dojo.hitch(this,
|
|
|
3516 |
function(err){
|
|
|
3517 |
if(this.isOnline){
|
|
|
3518 |
this.isOnline = false;
|
|
|
3519 |
|
|
|
3520 |
// FIXME: xhrGet() is not
|
|
|
3521 |
// correctly calling abort
|
|
|
3522 |
// on the XHR object when
|
|
|
3523 |
// it times out; fix inside
|
|
|
3524 |
// there instead of externally
|
|
|
3525 |
// here
|
|
|
3526 |
try{
|
|
|
3527 |
if(typeof d.ioArgs.xhr.abort == "function"){
|
|
|
3528 |
d.ioArgs.xhr.abort();
|
|
|
3529 |
}
|
|
|
3530 |
}catch(e){}
|
|
|
3531 |
|
|
|
3532 |
// if things fell in the middle of syncing,
|
|
|
3533 |
// stop syncing
|
|
|
3534 |
dojox.off.sync.isSyncing = false;
|
|
|
3535 |
|
|
|
3536 |
this.onNetwork("offline");
|
|
|
3537 |
}
|
|
|
3538 |
}
|
|
|
3539 |
),
|
|
|
3540 |
load: dojo.hitch(this,
|
|
|
3541 |
function(data){
|
|
|
3542 |
if(!this.isOnline){
|
|
|
3543 |
this.isOnline = true;
|
|
|
3544 |
this.onNetwork("online");
|
|
|
3545 |
}
|
|
|
3546 |
}
|
|
|
3547 |
)
|
|
|
3548 |
});
|
|
|
3549 |
|
|
|
3550 |
}), this.NET_CHECK * 1000);
|
|
|
3551 |
},
|
|
|
3552 |
|
|
|
3553 |
_getAvailabilityURL: function(){
|
|
|
3554 |
var url = this.availabilityURL.toString();
|
|
|
3555 |
|
|
|
3556 |
// bust the browser's cache to make sure we are really talking to
|
|
|
3557 |
// the server
|
|
|
3558 |
if(url.indexOf("?") == -1){
|
|
|
3559 |
url += "?";
|
|
|
3560 |
}else{
|
|
|
3561 |
url += "&";
|
|
|
3562 |
}
|
|
|
3563 |
url += "browserbust=" + new Date().getTime();
|
|
|
3564 |
|
|
|
3565 |
return url;
|
|
|
3566 |
},
|
|
|
3567 |
|
|
|
3568 |
_onOfflineCacheInstalled: function(){
|
|
|
3569 |
this.onFrameworkEvent("offlineCacheInstalled");
|
|
|
3570 |
},
|
|
|
3571 |
|
|
|
3572 |
_cacheDojoResources: function(){
|
|
|
3573 |
// if we are a non-optimized build, then the core Dojo bootstrap
|
|
|
3574 |
// system was loaded as separate JavaScript files;
|
|
|
3575 |
// add these to our offline cache list. these are
|
|
|
3576 |
// loaded before the dojo.require() system exists
|
|
|
3577 |
|
|
|
3578 |
// FIXME: create a better mechanism in the Dojo core to
|
|
|
3579 |
// expose whether you are dealing with an optimized build;
|
|
|
3580 |
// right now we just scan the SCRIPT tags attached to this
|
|
|
3581 |
// page and see if there is one for _base/_loader/bootstrap.js
|
|
|
3582 |
var isOptimizedBuild = true;
|
|
|
3583 |
dojo.forEach(dojo.query("script"), function(i){
|
|
|
3584 |
var src = i.getAttribute("src");
|
|
|
3585 |
if(!src){ return; }
|
|
|
3586 |
|
|
|
3587 |
if(src.indexOf("_base/_loader/bootstrap.js") != -1){
|
|
|
3588 |
isOptimizedBuild = false;
|
|
|
3589 |
}
|
|
|
3590 |
});
|
|
|
3591 |
|
|
|
3592 |
if(!isOptimizedBuild){
|
|
|
3593 |
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base.js").uri);
|
|
|
3594 |
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/loader.js").uri);
|
|
|
3595 |
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/bootstrap.js").uri);
|
|
|
3596 |
|
|
|
3597 |
// FIXME: pull in the host environment file in a more generic way
|
|
|
3598 |
// for other host environments
|
|
|
3599 |
dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/hostenv_browser.js").uri);
|
|
|
3600 |
}
|
|
|
3601 |
|
|
|
3602 |
// add anything that was brought in with a
|
|
|
3603 |
// dojo.require() that resulted in a JavaScript
|
|
|
3604 |
// URL being fetched
|
|
|
3605 |
|
|
|
3606 |
// FIXME: modify dojo/_base/_loader/loader.js to
|
|
|
3607 |
// expose a public API to get this information
|
|
|
3608 |
|
|
|
3609 |
for(var i = 0; i < dojo._loadedUrls.length; i++){
|
|
|
3610 |
dojox.off.files.cache(dojo._loadedUrls[i]);
|
|
|
3611 |
}
|
|
|
3612 |
|
|
|
3613 |
// FIXME: add the standard Dojo CSS file
|
|
|
3614 |
},
|
|
|
3615 |
|
|
|
3616 |
_save: function(){
|
|
|
3617 |
// summary:
|
|
|
3618 |
// Causes the Dojo Offline framework to save its configuration
|
|
|
3619 |
// data into local storage.
|
|
|
3620 |
},
|
|
|
3621 |
|
|
|
3622 |
_load: function(callback){
|
|
|
3623 |
// summary:
|
|
|
3624 |
// Causes the Dojo Offline framework to load its configuration
|
|
|
3625 |
// data from local storage
|
|
|
3626 |
dojox.off.sync._load(callback);
|
|
|
3627 |
}
|
|
|
3628 |
});
|
|
|
3629 |
|
|
|
3630 |
|
|
|
3631 |
// wait until the storage system is finished loading
|
|
|
3632 |
dojox.storage.manager.addOnLoad(dojo.hitch(dojox.off, "_onStorageLoad"));
|
|
|
3633 |
|
|
|
3634 |
// wait until the page is finished loading
|
|
|
3635 |
dojo.addOnLoad(dojox.off, "_onPageLoad");
|
|
|
3636 |
|
|
|
3637 |
}
|
|
|
3638 |
|
|
|
3639 |
if(!dojo._hasResource["dojox.off"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
3640 |
dojo._hasResource["dojox.off"] = true;
|
|
|
3641 |
dojo.provide("dojox.off");
|
|
|
3642 |
|
|
|
3643 |
|
|
|
3644 |
}
|
|
|
3645 |
|
|
|
3646 |
if(!dojo._hasResource["dojox.off.ui"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
3647 |
dojo._hasResource["dojox.off.ui"] = true;
|
|
|
3648 |
dojo.provide("dojox.off.ui");
|
|
|
3649 |
|
|
|
3650 |
|
|
|
3651 |
|
|
|
3652 |
|
|
|
3653 |
|
|
|
3654 |
// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org
|
|
|
3655 |
|
|
|
3656 |
// summary:
|
|
|
3657 |
// dojox.off.ui provides a standard,
|
|
|
3658 |
// default user-interface for a
|
|
|
3659 |
// Dojo Offline Widget that can easily
|
|
|
3660 |
// be dropped into applications that would
|
|
|
3661 |
// like to work offline.
|
|
|
3662 |
dojo.mixin(dojox.off.ui, {
|
|
|
3663 |
// appName: String
|
|
|
3664 |
// This application's name, such as "Foobar". Note that
|
|
|
3665 |
// this is a string, not HTML, so embedded markup will
|
|
|
3666 |
// not work, including entities. Only the following
|
|
|
3667 |
// characters are allowed: numbers, letters, and spaces.
|
|
|
3668 |
// You must set this property.
|
|
|
3669 |
appName: "setme",
|
|
|
3670 |
|
|
|
3671 |
// autoEmbed: boolean
|
|
|
3672 |
// For advanced usage; most developers can ignore this.
|
|
|
3673 |
// Whether to automatically auto-embed the default Dojo Offline
|
|
|
3674 |
// widget into this page; default is true.
|
|
|
3675 |
autoEmbed: true,
|
|
|
3676 |
|
|
|
3677 |
// autoEmbedID: String
|
|
|
3678 |
// For advanced usage; most developers can ignore this.
|
|
|
3679 |
// The ID of the DOM element that will contain our
|
|
|
3680 |
// Dojo Offline widget; defaults to the ID 'dot-widget'.
|
|
|
3681 |
autoEmbedID: "dot-widget",
|
|
|
3682 |
|
|
|
3683 |
// runLink: String
|
|
|
3684 |
// For advanced usage; most developers can ignore this.
|
|
|
3685 |
// The URL that should be navigated to to run this
|
|
|
3686 |
// application offline; this will be placed inside of a
|
|
|
3687 |
// link that the user can drag to their desktop and double
|
|
|
3688 |
// click. Note that this URL must exactly match the URL
|
|
|
3689 |
// of the main page of our resource that is offline for
|
|
|
3690 |
// it to be retrieved from the offline cache correctly.
|
|
|
3691 |
// For example, if you have cached your main page as
|
|
|
3692 |
// http://foobar.com/index.html, and you set this to
|
|
|
3693 |
// http://www.foobar.com/index.html, the run link will
|
|
|
3694 |
// not work. By default this value is automatically set to
|
|
|
3695 |
// the URL of this page, so it does not need to be set
|
|
|
3696 |
// manually unless you have unusual needs.
|
|
|
3697 |
runLink: window.location.href,
|
|
|
3698 |
|
|
|
3699 |
// runLinkTitle: String
|
|
|
3700 |
// For advanced usage; most developers can ignore this.
|
|
|
3701 |
// The text that will be inside of the link that a user
|
|
|
3702 |
// can drag to their desktop to run this application offline.
|
|
|
3703 |
// By default this is automatically set to "Run " plus your
|
|
|
3704 |
// application's name.
|
|
|
3705 |
runLinkTitle: "Run Application",
|
|
|
3706 |
|
|
|
3707 |
// learnHowPath: String
|
|
|
3708 |
// For advanced usage; most developers can ignore this.
|
|
|
3709 |
// The path to a web page that has information on
|
|
|
3710 |
// how to use this web app offline; defaults to
|
|
|
3711 |
// src/off/ui-template/learnhow.html, relative to
|
|
|
3712 |
// your Dojo installation. Make sure to set
|
|
|
3713 |
// dojo.to.ui.customLearnHowPath to true if you want
|
|
|
3714 |
// a custom Learn How page.
|
|
|
3715 |
learnHowPath: dojo.moduleUrl("dojox", "off/resources/learnhow.html"),
|
|
|
3716 |
|
|
|
3717 |
// customLearnHowPath: boolean
|
|
|
3718 |
// For advanced usage; most developers can ignore this.
|
|
|
3719 |
// Whether the developer is using their own custom page
|
|
|
3720 |
// for the Learn How instructional page; defaults to false.
|
|
|
3721 |
// Use in conjunction with dojox.off.ui.learnHowPath.
|
|
|
3722 |
customLearnHowPath: false,
|
|
|
3723 |
|
|
|
3724 |
htmlTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.html").uri,
|
|
|
3725 |
cssTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.css").uri,
|
|
|
3726 |
onlineImagePath: dojo.moduleUrl("dojox", "off/resources/greenball.png").uri,
|
|
|
3727 |
offlineImagePath: dojo.moduleUrl("dojox", "off/resources/redball.png").uri,
|
|
|
3728 |
rollerImagePath: dojo.moduleUrl("dojox", "off/resources/roller.gif").uri,
|
|
|
3729 |
checkmarkImagePath: dojo.moduleUrl("dojox", "off/resources/checkmark.png").uri,
|
|
|
3730 |
learnHowJSPath: dojo.moduleUrl("dojox", "off/resources/learnhow.js").uri,
|
|
|
3731 |
|
|
|
3732 |
_initialized: false,
|
|
|
3733 |
|
|
|
3734 |
onLoad: function(){
|
|
|
3735 |
// summary:
|
|
|
3736 |
// A function that should be connected to allow your
|
|
|
3737 |
// application to know when Dojo Offline, the page, and
|
|
|
3738 |
// the Offline Widget are all initialized and ready to be
|
|
|
3739 |
// used:
|
|
|
3740 |
//
|
|
|
3741 |
// dojo.connect(dojox.off.ui, "onLoad", someFunc)
|
|
|
3742 |
},
|
|
|
3743 |
|
|
|
3744 |
_initialize: function(){
|
|
|
3745 |
//console.debug("dojox.off.ui._initialize");
|
|
|
3746 |
|
|
|
3747 |
// make sure our app name is correct
|
|
|
3748 |
if(this._validateAppName(this.appName) == false){
|
|
|
3749 |
alert("You must set dojox.off.ui.appName; it can only contain "
|
|
|
3750 |
+ "letters, numbers, and spaces; right now it "
|
|
|
3751 |
+ "is incorrectly set to '" + dojox.off.ui.appName + "'");
|
|
|
3752 |
dojox.off.enabled = false;
|
|
|
3753 |
return;
|
|
|
3754 |
}
|
|
|
3755 |
|
|
|
3756 |
// set our run link text to its default
|
|
|
3757 |
this.runLinkText = "Run " + this.appName;
|
|
|
3758 |
|
|
|
3759 |
// setup our event listeners for Dojo Offline events
|
|
|
3760 |
// to update our UI
|
|
|
3761 |
dojo.connect(dojox.off, "onNetwork", this, "_onNetwork");
|
|
|
3762 |
dojo.connect(dojox.off.sync, "onSync", this, "_onSync");
|
|
|
3763 |
|
|
|
3764 |
// cache our default UI resources
|
|
|
3765 |
dojox.off.files.cache([
|
|
|
3766 |
this.htmlTemplatePath,
|
|
|
3767 |
this.cssTemplatePath,
|
|
|
3768 |
this.onlineImagePath,
|
|
|
3769 |
this.offlineImagePath,
|
|
|
3770 |
this.rollerImagePath,
|
|
|
3771 |
this.checkmarkImagePath
|
|
|
3772 |
]);
|
|
|
3773 |
|
|
|
3774 |
// embed the offline widget UI
|
|
|
3775 |
if(this.autoEmbed){
|
|
|
3776 |
this._doAutoEmbed();
|
|
|
3777 |
}
|
|
|
3778 |
},
|
|
|
3779 |
|
|
|
3780 |
_doAutoEmbed: function(){
|
|
|
3781 |
// fetch our HTML for the offline widget
|
|
|
3782 |
|
|
|
3783 |
// dispatch the request
|
|
|
3784 |
dojo.xhrGet({
|
|
|
3785 |
url: this.htmlTemplatePath,
|
|
|
3786 |
handleAs: "text",
|
|
|
3787 |
error: function(err){
|
|
|
3788 |
dojox.off.enabled = false;
|
|
|
3789 |
err = err.message||err;
|
|
|
3790 |
alert("Error loading the Dojo Offline Widget from "
|
|
|
3791 |
+ this.htmlTemplatePath + ": " + err);
|
|
|
3792 |
},
|
|
|
3793 |
load: dojo.hitch(this, this._templateLoaded)
|
|
|
3794 |
});
|
|
|
3795 |
},
|
|
|
3796 |
|
|
|
3797 |
_templateLoaded: function(data){
|
|
|
3798 |
//console.debug("dojox.off.ui._templateLoaded");
|
|
|
3799 |
// inline our HTML
|
|
|
3800 |
var container = dojo.byId(this.autoEmbedID);
|
|
|
3801 |
if(container){ container.innerHTML = data; }
|
|
|
3802 |
|
|
|
3803 |
// fill out our image paths
|
|
|
3804 |
this._initImages();
|
|
|
3805 |
|
|
|
3806 |
// update our network indicator status ball
|
|
|
3807 |
this._updateNetIndicator();
|
|
|
3808 |
|
|
|
3809 |
// update our 'Learn How' text
|
|
|
3810 |
this._initLearnHow();
|
|
|
3811 |
|
|
|
3812 |
this._initialized = true;
|
|
|
3813 |
|
|
|
3814 |
// check offline cache settings
|
|
|
3815 |
if(!dojox.off.hasOfflineCache){
|
|
|
3816 |
this._showNeedsOfflineCache();
|
|
|
3817 |
return;
|
|
|
3818 |
}
|
|
|
3819 |
|
|
|
3820 |
// check to see if we need a browser restart
|
|
|
3821 |
// to be able to use this web app offline
|
|
|
3822 |
if(dojox.off.hasOfflineCache && dojox.off.browserRestart){
|
|
|
3823 |
this._needsBrowserRestart();
|
|
|
3824 |
return;
|
|
|
3825 |
}else{
|
|
|
3826 |
var browserRestart = dojo.byId("dot-widget-browser-restart");
|
|
|
3827 |
if(browserRestart){ browserRestart.style.display = "none"; }
|
|
|
3828 |
}
|
|
|
3829 |
|
|
|
3830 |
// update our sync UI
|
|
|
3831 |
this._updateSyncUI();
|
|
|
3832 |
|
|
|
3833 |
// register our event listeners for our main buttons
|
|
|
3834 |
this._initMainEvtHandlers();
|
|
|
3835 |
|
|
|
3836 |
// if offline functionality is disabled, disable everything
|
|
|
3837 |
this._setOfflineEnabled(dojox.off.enabled);
|
|
|
3838 |
|
|
|
3839 |
// update our UI based on the state of the network
|
|
|
3840 |
this._onNetwork(dojox.off.isOnline ? "online" : "offline");
|
|
|
3841 |
|
|
|
3842 |
// try to go online
|
|
|
3843 |
this._testNet();
|
|
|
3844 |
},
|
|
|
3845 |
|
|
|
3846 |
_testNet: function(){
|
|
|
3847 |
dojox.off.goOnline(dojo.hitch(this, function(isOnline){
|
|
|
3848 |
//console.debug("testNet callback, isOnline="+isOnline);
|
|
|
3849 |
|
|
|
3850 |
// display our online/offline results
|
|
|
3851 |
this._onNetwork(isOnline ? "online" : "offline");
|
|
|
3852 |
|
|
|
3853 |
// indicate that our default UI
|
|
|
3854 |
// and Dojo Offline are now ready to
|
|
|
3855 |
// be used
|
|
|
3856 |
this.onLoad();
|
|
|
3857 |
}));
|
|
|
3858 |
},
|
|
|
3859 |
|
|
|
3860 |
_updateNetIndicator: function(){
|
|
|
3861 |
var onlineImg = dojo.byId("dot-widget-network-indicator-online");
|
|
|
3862 |
var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
|
|
|
3863 |
var titleText = dojo.byId("dot-widget-title-text");
|
|
|
3864 |
|
|
|
3865 |
if(onlineImg && offlineImg){
|
|
|
3866 |
if(dojox.off.isOnline == true){
|
|
|
3867 |
onlineImg.style.display = "inline";
|
|
|
3868 |
offlineImg.style.display = "none";
|
|
|
3869 |
}else{
|
|
|
3870 |
onlineImg.style.display = "none";
|
|
|
3871 |
offlineImg.style.display = "inline";
|
|
|
3872 |
}
|
|
|
3873 |
}
|
|
|
3874 |
|
|
|
3875 |
if(titleText){
|
|
|
3876 |
if(dojox.off.isOnline){
|
|
|
3877 |
titleText.innerHTML = "Online";
|
|
|
3878 |
}else{
|
|
|
3879 |
titleText.innerHTML = "Offline";
|
|
|
3880 |
}
|
|
|
3881 |
}
|
|
|
3882 |
},
|
|
|
3883 |
|
|
|
3884 |
_initLearnHow: function(){
|
|
|
3885 |
var learnHow = dojo.byId("dot-widget-learn-how-link");
|
|
|
3886 |
|
|
|
3887 |
if(!learnHow){ return; }
|
|
|
3888 |
|
|
|
3889 |
if(!this.customLearnHowPath){
|
|
|
3890 |
// add parameters to URL so the Learn How page
|
|
|
3891 |
// can customize itself and display itself
|
|
|
3892 |
// correctly based on framework settings
|
|
|
3893 |
var dojoPath = djConfig.baseRelativePath;
|
|
|
3894 |
this.learnHowPath += "?appName=" + encodeURIComponent(this.appName)
|
|
|
3895 |
+ "&hasOfflineCache=" + dojox.off.hasOfflineCache
|
|
|
3896 |
+ "&runLink=" + encodeURIComponent(this.runLink)
|
|
|
3897 |
+ "&runLinkText=" + encodeURIComponent(this.runLinkText)
|
|
|
3898 |
+ "&baseRelativePath=" + encodeURIComponent(dojoPath);
|
|
|
3899 |
|
|
|
3900 |
// cache our Learn How JavaScript page and
|
|
|
3901 |
// the HTML version with full query parameters
|
|
|
3902 |
// so it is available offline without a cache miss
|
|
|
3903 |
dojox.off.files.cache(this.learnHowJSPath);
|
|
|
3904 |
dojox.off.files.cache(this.learnHowPath);
|
|
|
3905 |
}
|
|
|
3906 |
|
|
|
3907 |
learnHow.setAttribute("href", this.learnHowPath);
|
|
|
3908 |
|
|
|
3909 |
var appName = dojo.byId("dot-widget-learn-how-app-name");
|
|
|
3910 |
|
|
|
3911 |
if(!appName){ return; }
|
|
|
3912 |
|
|
|
3913 |
appName.innerHTML = "";
|
|
|
3914 |
appName.appendChild(document.createTextNode(this.appName));
|
|
|
3915 |
},
|
|
|
3916 |
|
|
|
3917 |
_validateAppName: function(appName){
|
|
|
3918 |
if(!appName){ return false; }
|
|
|
3919 |
|
|
|
3920 |
return (/^[a-z0-9 ]*$/i.test(appName));
|
|
|
3921 |
},
|
|
|
3922 |
|
|
|
3923 |
_updateSyncUI: function(){
|
|
|
3924 |
var roller = dojo.byId("dot-roller");
|
|
|
3925 |
var checkmark = dojo.byId("dot-success-checkmark");
|
|
|
3926 |
var syncMessages = dojo.byId("dot-sync-messages");
|
|
|
3927 |
var details = dojo.byId("dot-sync-details");
|
|
|
3928 |
var cancel = dojo.byId("dot-sync-cancel");
|
|
|
3929 |
|
|
|
3930 |
if(dojox.off.sync.isSyncing){
|
|
|
3931 |
this._clearSyncMessage();
|
|
|
3932 |
|
|
|
3933 |
if(roller){ roller.style.display = "inline"; }
|
|
|
3934 |
|
|
|
3935 |
if(checkmark){ checkmark.style.display = "none"; }
|
|
|
3936 |
|
|
|
3937 |
if(syncMessages){
|
|
|
3938 |
dojo.removeClass(syncMessages, "dot-sync-error");
|
|
|
3939 |
}
|
|
|
3940 |
|
|
|
3941 |
if(details){ details.style.display = "none"; }
|
|
|
3942 |
|
|
|
3943 |
if(cancel){ cancel.style.display = "inline"; }
|
|
|
3944 |
}else{
|
|
|
3945 |
if(roller){ roller.style.display = "none"; }
|
|
|
3946 |
|
|
|
3947 |
if(cancel){ cancel.style.display = "none"; }
|
|
|
3948 |
|
|
|
3949 |
if(syncMessages){
|
|
|
3950 |
dojo.removeClass(syncMessages, "dot-sync-error");
|
|
|
3951 |
}
|
|
|
3952 |
}
|
|
|
3953 |
},
|
|
|
3954 |
|
|
|
3955 |
_setSyncMessage: function(message){
|
|
|
3956 |
var syncMessage = dojo.byId("dot-sync-messages");
|
|
|
3957 |
if(syncMessage){
|
|
|
3958 |
// when used with Google Gears pre-release in Firefox/Mac OS X,
|
|
|
3959 |
// the browser would crash when testing in Moxie
|
|
|
3960 |
// if we set the message this way for some reason.
|
|
|
3961 |
// Brad Neuberg, bkn3@columbia.edu
|
|
|
3962 |
//syncMessage.innerHTML = message;
|
|
|
3963 |
|
|
|
3964 |
while(syncMessage.firstChild){
|
|
|
3965 |
syncMessage.removeChild(syncMessage.firstChild);
|
|
|
3966 |
}
|
|
|
3967 |
syncMessage.appendChild(document.createTextNode(message));
|
|
|
3968 |
}
|
|
|
3969 |
},
|
|
|
3970 |
|
|
|
3971 |
_clearSyncMessage: function(){
|
|
|
3972 |
this._setSyncMessage("");
|
|
|
3973 |
},
|
|
|
3974 |
|
|
|
3975 |
_initImages: function(){
|
|
|
3976 |
var onlineImg = dojo.byId("dot-widget-network-indicator-online");
|
|
|
3977 |
if(onlineImg){
|
|
|
3978 |
onlineImg.setAttribute("src", this.onlineImagePath);
|
|
|
3979 |
}
|
|
|
3980 |
|
|
|
3981 |
var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
|
|
|
3982 |
if(offlineImg){
|
|
|
3983 |
offlineImg.setAttribute("src", this.offlineImagePath);
|
|
|
3984 |
}
|
|
|
3985 |
|
|
|
3986 |
var roller = dojo.byId("dot-roller");
|
|
|
3987 |
if(roller){
|
|
|
3988 |
roller.setAttribute("src", this.rollerImagePath);
|
|
|
3989 |
}
|
|
|
3990 |
|
|
|
3991 |
var checkmark = dojo.byId("dot-success-checkmark");
|
|
|
3992 |
if(checkmark){
|
|
|
3993 |
checkmark.setAttribute("src", this.checkmarkImagePath);
|
|
|
3994 |
}
|
|
|
3995 |
},
|
|
|
3996 |
|
|
|
3997 |
_showDetails: function(evt){
|
|
|
3998 |
// cancel the button's default behavior
|
|
|
3999 |
evt.preventDefault();
|
|
|
4000 |
evt.stopPropagation();
|
|
|
4001 |
|
|
|
4002 |
if(!dojox.off.sync.details.length){
|
|
|
4003 |
return;
|
|
|
4004 |
}
|
|
|
4005 |
|
|
|
4006 |
// determine our HTML message to display
|
|
|
4007 |
var html = "";
|
|
|
4008 |
html += "<html><head><title>Sync Details</title><head><body>";
|
|
|
4009 |
html += "<h1>Sync Details</h1>\n";
|
|
|
4010 |
html += "<ul>\n";
|
|
|
4011 |
for(var i = 0; i < dojox.off.sync.details.length; i++){
|
|
|
4012 |
html += "<li>";
|
|
|
4013 |
html += dojox.off.sync.details[i];
|
|
|
4014 |
html += "</li>";
|
|
|
4015 |
}
|
|
|
4016 |
html += "</ul>\n";
|
|
|
4017 |
html += "<a href='javascript:window.close()' "
|
|
|
4018 |
+ "style='text-align: right; padding-right: 2em;'>"
|
|
|
4019 |
+ "Close Window"
|
|
|
4020 |
+ "</a>\n";
|
|
|
4021 |
html += "</body></html>";
|
|
|
4022 |
|
|
|
4023 |
// open a popup window with this message
|
|
|
4024 |
var windowParams = "height=400,width=600,resizable=true,"
|
|
|
4025 |
+ "scrollbars=true,toolbar=no,menubar=no,"
|
|
|
4026 |
+ "location=no,directories=no,dependent=yes";
|
|
|
4027 |
|
|
|
4028 |
var popup = window.open("", "SyncDetails", windowParams);
|
|
|
4029 |
|
|
|
4030 |
if(!popup){ // aggressive popup blocker
|
|
|
4031 |
alert("Please allow popup windows for this domain; can't display sync details window");
|
|
|
4032 |
return;
|
|
|
4033 |
}
|
|
|
4034 |
|
|
|
4035 |
popup.document.open();
|
|
|
4036 |
popup.document.write(html);
|
|
|
4037 |
popup.document.close();
|
|
|
4038 |
|
|
|
4039 |
// put the focus on the popup window
|
|
|
4040 |
if(popup.focus){
|
|
|
4041 |
popup.focus();
|
|
|
4042 |
}
|
|
|
4043 |
},
|
|
|
4044 |
|
|
|
4045 |
_cancel: function(evt){
|
|
|
4046 |
// cancel the button's default behavior
|
|
|
4047 |
evt.preventDefault();
|
|
|
4048 |
evt.stopPropagation();
|
|
|
4049 |
|
|
|
4050 |
dojox.off.sync.cancel();
|
|
|
4051 |
},
|
|
|
4052 |
|
|
|
4053 |
_needsBrowserRestart: function(){
|
|
|
4054 |
var browserRestart = dojo.byId("dot-widget-browser-restart");
|
|
|
4055 |
if(browserRestart){
|
|
|
4056 |
dojo.addClass(browserRestart, "dot-needs-browser-restart");
|
|
|
4057 |
}
|
|
|
4058 |
|
|
|
4059 |
var appName = dojo.byId("dot-widget-browser-restart-app-name");
|
|
|
4060 |
if(appName){
|
|
|
4061 |
appName.innerHTML = "";
|
|
|
4062 |
appName.appendChild(document.createTextNode(this.appName));
|
|
|
4063 |
}
|
|
|
4064 |
|
|
|
4065 |
var status = dojo.byId("dot-sync-status");
|
|
|
4066 |
if(status){
|
|
|
4067 |
status.style.display = "none";
|
|
|
4068 |
}
|
|
|
4069 |
},
|
|
|
4070 |
|
|
|
4071 |
_showNeedsOfflineCache: function(){
|
|
|
4072 |
var widgetContainer = dojo.byId("dot-widget-container");
|
|
|
4073 |
if(widgetContainer){
|
|
|
4074 |
dojo.addClass(widgetContainer, "dot-needs-offline-cache");
|
|
|
4075 |
}
|
|
|
4076 |
},
|
|
|
4077 |
|
|
|
4078 |
_hideNeedsOfflineCache: function(){
|
|
|
4079 |
var widgetContainer = dojo.byId("dot-widget-container");
|
|
|
4080 |
if(widgetContainer){
|
|
|
4081 |
dojo.removeClass(widgetContainer, "dot-needs-offline-cache");
|
|
|
4082 |
}
|
|
|
4083 |
},
|
|
|
4084 |
|
|
|
4085 |
_initMainEvtHandlers: function(){
|
|
|
4086 |
var detailsButton = dojo.byId("dot-sync-details-button");
|
|
|
4087 |
if(detailsButton){
|
|
|
4088 |
dojo.connect(detailsButton, "onclick", this, this._showDetails);
|
|
|
4089 |
}
|
|
|
4090 |
var cancelButton = dojo.byId("dot-sync-cancel-button");
|
|
|
4091 |
if(cancelButton){
|
|
|
4092 |
dojo.connect(cancelButton, "onclick", this, this._cancel);
|
|
|
4093 |
}
|
|
|
4094 |
},
|
|
|
4095 |
|
|
|
4096 |
_setOfflineEnabled: function(enabled){
|
|
|
4097 |
var elems = [];
|
|
|
4098 |
elems.push(dojo.byId("dot-sync-status"));
|
|
|
4099 |
|
|
|
4100 |
for(var i = 0; i < elems.length; i++){
|
|
|
4101 |
if(elems[i]){
|
|
|
4102 |
elems[i].style.visibility =
|
|
|
4103 |
(enabled ? "visible" : "hidden");
|
|
|
4104 |
}
|
|
|
4105 |
}
|
|
|
4106 |
},
|
|
|
4107 |
|
|
|
4108 |
_syncFinished: function(){
|
|
|
4109 |
this._updateSyncUI();
|
|
|
4110 |
|
|
|
4111 |
var checkmark = dojo.byId("dot-success-checkmark");
|
|
|
4112 |
var details = dojo.byId("dot-sync-details");
|
|
|
4113 |
|
|
|
4114 |
if(dojox.off.sync.successful == true){
|
|
|
4115 |
this._setSyncMessage("Sync Successful");
|
|
|
4116 |
if(checkmark){ checkmark.style.display = "inline"; }
|
|
|
4117 |
}else if(dojox.off.sync.cancelled == true){
|
|
|
4118 |
this._setSyncMessage("Sync Cancelled");
|
|
|
4119 |
|
|
|
4120 |
if(checkmark){ checkmark.style.display = "none"; }
|
|
|
4121 |
}else{
|
|
|
4122 |
this._setSyncMessage("Sync Error");
|
|
|
4123 |
|
|
|
4124 |
var messages = dojo.byId("dot-sync-messages");
|
|
|
4125 |
if(messages){
|
|
|
4126 |
dojo.addClass(messages, "dot-sync-error");
|
|
|
4127 |
}
|
|
|
4128 |
|
|
|
4129 |
if(checkmark){ checkmark.style.display = "none"; }
|
|
|
4130 |
}
|
|
|
4131 |
|
|
|
4132 |
if(dojox.off.sync.details.length && details){
|
|
|
4133 |
details.style.display = "inline";
|
|
|
4134 |
}
|
|
|
4135 |
},
|
|
|
4136 |
|
|
|
4137 |
_onFrameworkEvent: function(type, saveData){
|
|
|
4138 |
if(type == "save"){
|
|
|
4139 |
if(saveData.status == dojox.storage.FAILED && !saveData.isCoreSave){
|
|
|
4140 |
alert("Please increase the amount of local storage available "
|
|
|
4141 |
+ "to this application");
|
|
|
4142 |
if(dojox.storage.hasSettingsUI()){
|
|
|
4143 |
dojox.storage.showSettingsUI();
|
|
|
4144 |
}
|
|
|
4145 |
|
|
|
4146 |
// FIXME: Be able to know if storage size has changed
|
|
|
4147 |
// due to user configuration
|
|
|
4148 |
}
|
|
|
4149 |
}else if(type == "coreOperationFailed"){
|
|
|
4150 |
console.log("Application does not have permission to use Dojo Offline");
|
|
|
4151 |
|
|
|
4152 |
if(!this._userInformed){
|
|
|
4153 |
alert("This application will not work if Google Gears is not allowed to run");
|
|
|
4154 |
this._userInformed = true;
|
|
|
4155 |
}
|
|
|
4156 |
}else if(type == "offlineCacheInstalled"){
|
|
|
4157 |
// clear out the 'needs offline cache' info
|
|
|
4158 |
this._hideNeedsOfflineCache();
|
|
|
4159 |
|
|
|
4160 |
// check to see if we need a browser restart
|
|
|
4161 |
// to be able to use this web app offline
|
|
|
4162 |
if(dojox.off.hasOfflineCache == true
|
|
|
4163 |
&& dojox.off.browserRestart == true){
|
|
|
4164 |
this._needsBrowserRestart();
|
|
|
4165 |
return;
|
|
|
4166 |
}else{
|
|
|
4167 |
var browserRestart = dojo.byId("dot-widget-browser-restart");
|
|
|
4168 |
if(browserRestart){
|
|
|
4169 |
browserRestart.style.display = "none";
|
|
|
4170 |
}
|
|
|
4171 |
}
|
|
|
4172 |
|
|
|
4173 |
// update our sync UI
|
|
|
4174 |
this._updateSyncUI();
|
|
|
4175 |
|
|
|
4176 |
// register our event listeners for our main buttons
|
|
|
4177 |
this._initMainEvtHandlers();
|
|
|
4178 |
|
|
|
4179 |
// if offline is disabled, disable everything
|
|
|
4180 |
this._setOfflineEnabled(dojox.off.enabled);
|
|
|
4181 |
|
|
|
4182 |
// try to go online
|
|
|
4183 |
this._testNet();
|
|
|
4184 |
}
|
|
|
4185 |
},
|
|
|
4186 |
|
|
|
4187 |
_onSync: function(type){
|
|
|
4188 |
//console.debug("ui, onSync="+type);
|
|
|
4189 |
switch(type){
|
|
|
4190 |
case "start":
|
|
|
4191 |
this._updateSyncUI();
|
|
|
4192 |
break;
|
|
|
4193 |
|
|
|
4194 |
case "refreshFiles":
|
|
|
4195 |
this._setSyncMessage("Downloading UI...");
|
|
|
4196 |
break;
|
|
|
4197 |
|
|
|
4198 |
case "upload":
|
|
|
4199 |
this._setSyncMessage("Uploading new data...");
|
|
|
4200 |
break;
|
|
|
4201 |
|
|
|
4202 |
case "download":
|
|
|
4203 |
this._setSyncMessage("Downloading new data...");
|
|
|
4204 |
break;
|
|
|
4205 |
|
|
|
4206 |
case "finished":
|
|
|
4207 |
this._syncFinished();
|
|
|
4208 |
break;
|
|
|
4209 |
|
|
|
4210 |
case "cancel":
|
|
|
4211 |
this._setSyncMessage("Canceling Sync...");
|
|
|
4212 |
break;
|
|
|
4213 |
|
|
|
4214 |
default:
|
|
|
4215 |
dojo.warn("Programming error: "
|
|
|
4216 |
+ "Unknown sync type in dojox.off.ui: " + type);
|
|
|
4217 |
break;
|
|
|
4218 |
}
|
|
|
4219 |
},
|
|
|
4220 |
|
|
|
4221 |
_onNetwork: function(type){
|
|
|
4222 |
// summary:
|
|
|
4223 |
// Called when we go on- or off-line
|
|
|
4224 |
// description:
|
|
|
4225 |
// When we go online or offline, this method is called to update
|
|
|
4226 |
// our UI. Default behavior is to update the Offline
|
|
|
4227 |
// Widget UI and to attempt a synchronization.
|
|
|
4228 |
// type: String
|
|
|
4229 |
// "online" if we just moved online, and "offline" if we just
|
|
|
4230 |
// moved offline.
|
|
|
4231 |
|
|
|
4232 |
if(!this._initialized){ return; }
|
|
|
4233 |
|
|
|
4234 |
// update UI
|
|
|
4235 |
this._updateNetIndicator();
|
|
|
4236 |
|
|
|
4237 |
if(type == "offline"){
|
|
|
4238 |
this._setSyncMessage("You are working offline");
|
|
|
4239 |
|
|
|
4240 |
// clear old details
|
|
|
4241 |
var details = dojo.byId("dot-sync-details");
|
|
|
4242 |
if(details){ details.style.display = "none"; }
|
|
|
4243 |
|
|
|
4244 |
// if we fell offline during a sync, hide
|
|
|
4245 |
// the sync info
|
|
|
4246 |
this._updateSyncUI();
|
|
|
4247 |
}else{ // online
|
|
|
4248 |
// synchronize, but pause for a few seconds
|
|
|
4249 |
// so that the user can orient themselves
|
|
|
4250 |
if(dojox.off.sync.autoSync){
|
|
|
4251 |
window.setTimeout("dojox.off.sync.synchronize()", 1000);
|
|
|
4252 |
}
|
|
|
4253 |
}
|
|
|
4254 |
}
|
|
|
4255 |
});
|
|
|
4256 |
|
|
|
4257 |
// register ourselves for low-level framework events
|
|
|
4258 |
dojo.connect(dojox.off, "onFrameworkEvent", dojox.off.ui, "_onFrameworkEvent");
|
|
|
4259 |
|
|
|
4260 |
// start our magic when the Dojo Offline framework is ready to go
|
|
|
4261 |
dojo.connect(dojox.off, "onLoad", dojox.off.ui, dojox.off.ui._initialize);
|
|
|
4262 |
|
|
|
4263 |
}
|
|
|
4264 |
|
|
|
4265 |
if(!dojo._hasResource["dojox.off.offline"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
4266 |
dojo._hasResource["dojox.off.offline"] = true;
|
|
|
4267 |
dojo.provide("dojox.off.offline");
|
|
|
4268 |
|
|
|
4269 |
|
|
|
4270 |
|
|
|
4271 |
|
|
|
4272 |
|
|
|
4273 |
|
|
|
4274 |
|
|
|
4275 |
}
|
|
|
4276 |
|