source: Dev/branches/rest-dojo-ui/client/dojox/data/FlickrRestStore.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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