source: Dev/trunk/src/client/dojox/data/FlickrRestStore.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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