]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dojox/data/FlickrRestStore.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dojox / data / FlickrRestStore.js
1 if(!dojo._hasResource["dojox.data.FlickrRestStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojox.data.FlickrRestStore"] = true;
3 dojo.provide("dojox.data.FlickrRestStore");
4
5 dojo.require("dojox.data.FlickrStore");
6
7 dojo.declare("dojox.data.FlickrRestStore",
8         dojox.data.FlickrStore, {
9         constructor: function(/*Object*/args){ 
10                 // summary:
11                 //      Initializer for the FlickrRestStore store.  
12                 // description:
13                 //      The FlickrRestStore is a Datastore interface to one of the basic services
14                 //      of the Flickr service, the public photo feed.  This does not provide
15                 //      access to all the services of Flickr.
16                 //      This store cannot do * and ? filtering as the flickr service 
17                 //      provides no interface for wildcards.
18                 if(args && args.label){
19                         if(args.label) {
20                                 this.label = args.label;
21                         }
22                         if(args.apikey) {
23                                 this._apikey = args.apikey;
24                         }
25                 }
26                 this._cache = [];
27                 this._prevRequests = {};
28                 this._handlers = {};
29                 this._prevRequestRanges = [];
30                 this._maxPhotosPerUser = {};
31                 this._id = dojox.data.FlickrRestStore.prototype._id++;
32         },
33         
34         // _id: Integer
35         // A unique identifier for this store.
36         _id: 0,
37         
38         // _requestCount: Integer
39         // A counter for the number of requests made. This is used to define
40         // the callback function that Flickr will use.
41         _requestCount: 0,
42         
43         // _flickrRestUrl: String
44         //      The URL to the Flickr REST services.
45         _flickrRestUrl: "http://www.flickr.com/services/rest/",
46         
47         // _apikey: String
48         //      The users API key to be used when accessing Flickr REST services.
49         _apikey: null,
50         
51         // _storeRef: String
52         //      A key used to mark an data store item as belonging to this store.
53         _storeRef: "_S",
54         
55         // _cache: Array
56         //      An Array of all previously downloaded picture info.
57         _cache: null,
58         
59         // _prevRequests: Object
60         //      A HashMap used to record the signature of a request to prevent duplicate 
61         //      request being made.
62         _prevRequests: null,
63         
64         // _handlers: Object
65         //      A HashMap used to record the handlers registered for a single remote request.  Multiple 
66         //      requests may be made for the same information before the first request has finished. 
67         //      Each element of this Object is an array of handlers to call back when the request finishes.
68         //      This prevents multiple requests being made for the same information.  
69         _handlers: null,
70         
71         // _sortAttributes: Object
72         // A quick lookup of valid attribute names in a sort query.
73         _sortAttributes: {
74                 "date-posted": true,
75                 "date-taken": true,
76                 "interestingness": true
77         },
78                         
79         _fetchItems: function(request, fetchHandler, errorHandler){
80                 // summary: Fetch flickr items that match to a query
81                 // request:
82                 //      A request object
83                 // fetchHandler:
84                 //      A function to call for fetched items
85                 // errorHandler:
86                 //      A function to call on error
87                 var query = {};
88                 if(!request.query){
89                         request.query = query = {};
90                 } else {
91                         dojo.mixin(query, request.query);       
92                 }
93                 
94                 var primaryKey = [];
95                 var secondaryKey = [];
96                 
97                 //Generate a unique function to be called back
98                 var callbackFn = "FlickrRestStoreCallback_" + this._id + "_" + (++this._requestCount);
99                 //Build up the content to send the request for.
100                 var content = {
101                         format: "json",
102                         method: "flickr.photos.search",
103                         api_key: this._apikey,
104                         extras: "owner_name,date_upload,date_taken",
105                         jsoncallback: callbackFn
106                 };
107                 var isRest = false;
108                 if(query.userid){
109                         isRest = true;
110                         content.user_id = request.query.userid;
111                         primaryKey.push("userid"+request.query.userid);
112                 }
113                 if(query.apikey){
114                         isRest = true;
115                         content.api_key = request.query.apikey;
116                         secondaryKey.push("api"+request.query.apikey);
117                 } else{
118                         throw Error("dojox.data.FlickrRestStore: An API key must be specified.");
119                 }
120                 request._curCount = request.count;
121                 if(query.page){
122                         content.page = request.query.page;
123                         secondaryKey.push("page" + content.page);
124                 }else if(typeof(request.start) != "undefined" && request.start != null) {
125                         if(!request.count){
126                                 request.count = 20;
127                         }
128                         var diff = request.start % request.count;
129                         var start = request.start, count = request.count;
130                         //If the count does not divide cleanly into the start number,
131                         //more work has to be done to figure out the best page to request
132                         if(diff != 0) {
133                                 if(start < count / 2) {
134                                         //If the first record requested is less than half the amount requested,
135                                         //then request from 0 to the count record
136                                         count = start + count;
137                                         start = 0; 
138                                 } else {
139                                         var divLimit = 20, div = 2;
140                                         for(var i = divLimit; i > 0; i--) {
141                                                 if(start % i == 0 && (start/i) >= count){
142                                                         div = i;
143                                                         break;
144                                                 }
145                                         }
146                                         count = start/div;
147                                 }
148                                 request._realStart = request.start;
149                                 request._realCount = request.count;
150                                 request._curStart = start;
151                                 request._curCount = count;
152                         } else {
153                                 request._realStart = request._realCount = null;
154                                 request._curStart = request.start;
155                                 request._curCount = request.count;
156                         }
157                         
158                         content.page = (start / count) + 1;
159                         secondaryKey.push("page" + content.page);
160                 }
161                 if(request._curCount){
162                         content.per_page = request._curCount;
163                         secondaryKey.push("count" + request._curCount);
164                 }
165                 
166                 if(query.lang){
167                         content.lang = request.query.lang;
168                         primaryKey.push("lang" + request.lang);
169                 }
170                 var url = this._flickrRestUrl;
171                 
172                 if(query.setid){
173                         content.method = "flickr.photosets.getPhotos";
174                         content.photoset_id = request.query.set; 
175                         primaryKey.push("set" + request.query.set);
176                 }
177                 
178                 if(query.tags){
179                         if(query.tags instanceof Array){
180                                 content.tags = query.tags.join(",");
181                         } else {
182                                 content.tags=query.tags;                                
183                         }
184                         primaryKey.push("tags" + content.tags);
185                         
186                         if(query["tag_mode"] && (query.tag_mode.toLowerCase() == "any"
187                                 || query.tag_mode.toLowerCase() == "all")){
188                                 content.tag_mode = query.tag_mode;
189                         }
190                 }
191                 if(query.text){
192                         content.text=query.text;
193                         primaryKey.push("text:"+query.text);
194                 }
195                 
196                 //The store only supports a single sort attribute, even though the
197                 //Read API technically allows multiple sort attributes
198                 if(query.sort && query.sort.length > 0){                        
199                         //The default sort attribute is 'date-posted'
200                         if(!query.sort[0].attribute){
201                                 query.sort[0].attribute = "date-posted";
202                         }
203                         
204                         //If the sort attribute is valid, check if it is ascending or
205                         //descending.
206                         if(this._sortAttributes[query.sort[0].attribute]) {
207                                 if(query.sort[0].descending){
208                                         content.sort = query.sort[0].attribute + "-desc";
209                                 } else {
210                                         content.sort = query.sort[0].attribute + "-asc";
211                                 }
212                         }
213                 } else {
214                         //The default sort in the Dojo Data API is ascending.
215                         content.sort = "date-posted-asc";
216                 }
217                 primaryKey.push("sort:"+content.sort);
218         
219                 //Generate a unique key for this request, so the store can 
220                 //detect duplicate requests.
221                 primaryKey = primaryKey.join(".");
222                 secondaryKey = secondaryKey.length > 0 ? "." + secondaryKey.join(".") : "";
223                 var requestKey = primaryKey + secondaryKey;
224                 
225                 //Make a copy of the request, in case the source object is modified
226                 //before the request completes
227                 request = {
228                         query: query,
229                         count: request._curCount,
230                         start: request._curStart,
231                         _realCount: request._realCount,
232                         _realStart: request._realStart,
233                         onBegin: request.onBegin,
234                         onComplete: request.onComplete,
235                         onItem: request.onItem
236                 };
237
238                 var thisHandler = {
239                         request: request,
240                 fetchHandler: fetchHandler,
241                 errorHandler: errorHandler
242                 };
243
244                 //If the request has already been made, but not yet completed,
245                 //then add the callback handler to the list of handlers
246                 //for this request, and finish.
247                 if(this._handlers[requestKey]){
248                 this._handlers[requestKey].push(thisHandler);
249                 return;
250                 }
251
252                 this._handlers[requestKey] = [thisHandler];
253
254                 //Linking this up to Flickr is a PAIN!
255                 var self = this;
256                 var handle = null;
257                 var getArgs = {
258                         url: this._flickrRestUrl,
259                         preventCache: true,
260                         content: content
261                 };
262                 
263                 var doHandle = function(processedData, data, handler){
264                         var onBegin = handler.request.onBegin;
265                         handler.request.onBegin = null;
266                         var maxPhotos;
267                         var req = handler.request;
268                         
269                         if(typeof(req._realStart) != undefined && req._realStart != null) {
270                                 req.start = req._realStart;
271                                 req.count = req._realCount;
272                                 req._realStart = req._realCount = null;
273                         }
274
275                         //If the request contains an onBegin method, the total number
276                         //of photos must be calculated.
277                         if(onBegin){
278                                 if(data && typeof(data.photos.perpage) != "undefined" && typeof(data.photos.pages) != "undefined"){
279                                                 if(data.photos.perpage * data.photos.pages <= handler.request.start + handler.request.count){
280                                                         //If the final page of results has been received, it is possible to 
281                                                         //know exactly how many photos there are
282                                                         maxPhotos = handler.request.start + data.photos.photo.length;                
283                                                 }else{
284                                                         //If the final page of results has not yet been received,
285                                                         //it is not possible to tell exactly how many photos exist, so
286                                                         //return the number of pages multiplied by the number of photos per page.
287                                                         maxPhotos = data.photos.perpage * data.photos.pages;
288                                                 }
289                                                 self._maxPhotosPerUser[primaryKey] = maxPhotos;
290                                                 onBegin(maxPhotos, handler.request);
291                                 } else if(self._maxPhotosPerUser[primaryKey]) {
292                                         onBegin(self._maxPhotosPerUser[primaryKey], handler.request);
293                                 }
294                         }
295                         //Call whatever functions the caller has defined on the request object, except for onBegin
296                         handler.fetchHandler(processedData, handler.request);
297                         if(onBegin){
298                                 //Replace the onBegin function, if it existed.
299                                 handler.request.onBegin = onBegin;
300                         }
301                 };
302                 
303                 //Define a callback for the script that iterates through a list of 
304                 //handlers for this piece of data.  Multiple requests can come into
305                 //the store for the same data.
306                 var myHandler = function(data){
307                         //The handler should not be called more than once, so disconnect it.
308                         //if(handle !== null){ dojo.disconnect(handle); }
309                         if(data.stat != "ok"){
310                                 errorHandler(null, request);
311                         }else{ //Process the items...
312                                 var handlers = self._handlers[requestKey];
313                                 if(!handlers){
314                                         console.log("FlickrRestStore: no handlers for data", data);
315                                         return;
316                                 }
317
318                                 self._handlers[requestKey] = null;
319                                 self._prevRequests[requestKey] = data;
320
321                                 //Process the data once.
322                                 var processedData = self._processFlickrData(data, request, primaryKey);
323                                 if(!self._prevRequestRanges[primaryKey]) {
324                                         self._prevRequestRanges[primaryKey] = [];
325                                 }
326                                 self._prevRequestRanges[primaryKey].push({
327                                         start: request.start,
328                                         end: request.start + data.photos.photo.length
329                                 });
330
331                                 //Iterate through the array of handlers, calling each one.
332                                 for(var i = 0; i < handlers.length; i++ ){
333                                         doHandle(processedData, data, handlers[i]);
334                                 }
335                         }
336                 };
337
338                 var data = this._prevRequests[requestKey];
339                 
340                 //If the data was previously retrieved, there is no need to fetch it again.
341                 if(data){
342                         this._handlers[requestKey] = null;
343                         doHandle(this._cache[primaryKey], data, thisHandler);
344                         return;
345                 } else if(this._checkPrevRanges(primaryKey, request.start, request.count)) {
346                         //If this range of data has already been retrieved, reuse it.
347                         this._handlers[requestKey] = null;
348                         doHandle(this._cache[primaryKey], null, thisHandler);
349                         return;
350                 }
351                 
352                 dojo.global[callbackFn] = function(data){
353                         myHandler(data);
354                         //Clean up the function, it should never be called again
355                         dojo.global[callbackFn] = null;
356                 };
357                                 
358                 var deferred = dojo.io.script.get(getArgs);
359                 
360                 //We only set up the errback, because the callback isn't ever really used because we have
361                 //to link to the jsonFlickrFeed function....
362                 deferred.addErrback(function(error){
363                         dojo.disconnect(handle);
364                         errorHandler(error, request);
365                 });
366         },
367         
368         getAttributes: function(item){
369                 //      summary: 
370                 //      See dojo.data.api.Read.getAttributes()
371                 return ["title", "author", "imageUrl", "imageUrlSmall", 
372                                         "imageUrlMedium", "imageUrlThumb", "link",
373                                         "dateTaken", "datePublished"]; 
374         },
375         
376         getValues: function(item, attribute){
377                 //      summary:
378                 //      See dojo.data.api.Read.getValue()
379                 this._assertIsItem(item);
380                 this._assertIsAttribute(attribute);
381                 if(attribute === "title"){
382                         return [this._unescapeHtml(item.title)]; // String
383                 }else if(attribute === "author"){
384                         return [item.ownername]; // String
385                 }else if(attribute === "imageUrlSmall"){
386                         return [item.media.s]; // String
387                 }else if(attribute === "imageUrl"){
388                         return [item.media.l]; // String
389                 }else if(attribute === "imageUrlMedium"){
390                         return [item.media.m]; // String
391                 }else if(attribute === "imageUrlThumb"){
392                         return [item.media.t]; // String
393                 }else if(attribute === "link"){
394                         return ["http://www.flickr.com/photos/" + item.owner + "/" + item.id]; // String
395                 }else if(attribute === "dateTaken"){
396                         return item.datetaken;
397                 }else if(attribute === "datePublished"){
398                         return item.datepublished;
399                 }
400                 
401                 return undefined;
402         },
403
404         _processFlickrData: function(/* Object */data, /* Object */request, /* String */ cacheKey){
405                 // summary: Processes the raw data from Flickr and updates the internal cache.
406                 // data: 
407                 //              Data returned from Flickr
408                 // request: 
409                 //              The original dojo.data.Request object passed in by the user.
410                 
411                 //If the data contains an 'item' object, it has not come from the REST services,
412                 //so process it using the FlickrStore.
413                 if(data.items){
414                         return dojox.data.FlickrStore.prototype._processFlickrData.apply(this,arguments);
415                 }
416
417                 var template = ["http://farm", null, ".static.flickr.com/", null, "/", null, "_", null];
418                 
419                 var items = [];
420                 if(data.stat == "ok" && data.photos && data.photos.photo){
421                         items = data.photos.photo;
422                         
423                         //Add on the store ref so that isItem can work.
424                         for(var i = 0; i < items.length; i++){
425                                 var item = items[i];
426                                 item[this._storeRef] = this;
427                                 
428                                 template[1] = item.farm;
429                                 template[3] = item.server;
430                                 template[5] = item.id;
431                                 template[7] = item.secret;
432                                  
433                                 var base = template.join("");
434                                 item.media = {
435                                         s: base + "_s.jpg",
436                                         m: base + "_m.jpg",
437                                         l: base + ".jpg",
438                                         t: base + "_t.jpg"
439                                 };
440                         }
441                 }
442                 var start = request.start ? request.start : 0;
443                 var arr = this._cache[cacheKey];
444                 if(!arr) {
445                         this._cache[cacheKey] = arr = [];
446                 }
447                 for(var count = 0; count < items.length; count++){
448                         arr[count + start] = items[count];
449                 }
450
451                 return arr; // Array
452         },
453         
454         _checkPrevRanges: function(primaryKey, start, count) {
455                 var end = start + count;
456                 var arr = this._prevRequestRanges[primaryKey];
457                 if(!arr) {
458                         return false;
459                 }
460                 for(var i = 0; i< arr.length; i++) {
461                         if(start >= arr[i].start &&
462                            end <= arr[i].end) {
463                                 return true;
464                         }
465                 }
466                 return false;
467         }
468 });
469
470
471 }