Blame | Last modification | View Log | RSS feed
if(!dojo._hasResource["dojox.data.FlickrRestStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.dojo._hasResource["dojox.data.FlickrRestStore"] = true;dojo.provide("dojox.data.FlickrRestStore");dojo.require("dojox.data.FlickrStore");dojo.declare("dojox.data.FlickrRestStore",dojox.data.FlickrStore, {constructor: function(/*Object*/args){// summary:// Initializer for the FlickrRestStore store.// description:// The FlickrRestStore is a Datastore interface to one of the basic services// of the Flickr service, the public photo feed. This does not provide// access to all the services of Flickr.// This store cannot do * and ? filtering as the flickr service// provides no interface for wildcards.if(args && args.label){if(args.label) {this.label = args.label;}if(args.apikey) {this._apikey = args.apikey;}}this._cache = [];this._prevRequests = {};this._handlers = {};this._prevRequestRanges = [];this._maxPhotosPerUser = {};this._id = dojox.data.FlickrRestStore.prototype._id++;},// _id: Integer// A unique identifier for this store._id: 0,// _requestCount: Integer// A counter for the number of requests made. This is used to define// the callback function that Flickr will use._requestCount: 0,// _flickrRestUrl: String// The URL to the Flickr REST services._flickrRestUrl: "http://www.flickr.com/services/rest/",// _apikey: String// The users API key to be used when accessing Flickr REST services._apikey: null,// _storeRef: String// A key used to mark an data store item as belonging to this store._storeRef: "_S",// _cache: Array// An Array of all previously downloaded picture info._cache: null,// _prevRequests: Object// A HashMap used to record the signature of a request to prevent duplicate// request being made._prevRequests: null,// _handlers: Object// A HashMap used to record the handlers registered for a single remote request. Multiple// requests may be made for the same information before the first request has finished.// Each element of this Object is an array of handlers to call back when the request finishes.// This prevents multiple requests being made for the same information._handlers: null,// _sortAttributes: Object// A quick lookup of valid attribute names in a sort query._sortAttributes: {"date-posted": true,"date-taken": true,"interestingness": true},_fetchItems: function(request, fetchHandler, errorHandler){// summary: Fetch flickr items that match to a query// request:// A request object// fetchHandler:// A function to call for fetched items// errorHandler:// A function to call on errorvar query = {};if(!request.query){request.query = query = {};} else {dojo.mixin(query, request.query);}var primaryKey = [];var secondaryKey = [];//Generate a unique function to be called backvar callbackFn = "FlickrRestStoreCallback_" + this._id + "_" + (++this._requestCount);//Build up the content to send the request for.var content = {format: "json",method: "flickr.photos.search",api_key: this._apikey,extras: "owner_name,date_upload,date_taken",jsoncallback: callbackFn};var isRest = false;if(query.userid){isRest = true;content.user_id = request.query.userid;primaryKey.push("userid"+request.query.userid);}if(query.apikey){isRest = true;content.api_key = request.query.apikey;secondaryKey.push("api"+request.query.apikey);} else{throw Error("dojox.data.FlickrRestStore: An API key must be specified.");}request._curCount = request.count;if(query.page){content.page = request.query.page;secondaryKey.push("page" + content.page);}else if(typeof(request.start) != "undefined" && request.start != null) {if(!request.count){request.count = 20;}var diff = request.start % request.count;var start = request.start, count = request.count;//If the count does not divide cleanly into the start number,//more work has to be done to figure out the best page to requestif(diff != 0) {if(start < count / 2) {//If the first record requested is less than half the amount requested,//then request from 0 to the count recordcount = start + count;start = 0;} else {var divLimit = 20, div = 2;for(var i = divLimit; i > 0; i--) {if(start % i == 0 && (start/i) >= count){div = i;break;}}count = start/div;}request._realStart = request.start;request._realCount = request.count;request._curStart = start;request._curCount = count;} else {request._realStart = request._realCount = null;request._curStart = request.start;request._curCount = request.count;}content.page = (start / count) + 1;secondaryKey.push("page" + content.page);}if(request._curCount){content.per_page = request._curCount;secondaryKey.push("count" + request._curCount);}if(query.lang){content.lang = request.query.lang;primaryKey.push("lang" + request.lang);}var url = this._flickrRestUrl;if(query.setid){content.method = "flickr.photosets.getPhotos";content.photoset_id = request.query.set;primaryKey.push("set" + request.query.set);}if(query.tags){if(query.tags instanceof Array){content.tags = query.tags.join(",");} else {content.tags=query.tags;}primaryKey.push("tags" + content.tags);if(query["tag_mode"] && (query.tag_mode.toLowerCase() == "any"|| query.tag_mode.toLowerCase() == "all")){content.tag_mode = query.tag_mode;}}if(query.text){content.text=query.text;primaryKey.push("text:"+query.text);}//The store only supports a single sort attribute, even though the//Read API technically allows multiple sort attributesif(query.sort && query.sort.length > 0){//The default sort attribute is 'date-posted'if(!query.sort[0].attribute){query.sort[0].attribute = "date-posted";}//If the sort attribute is valid, check if it is ascending or//descending.if(this._sortAttributes[query.sort[0].attribute]) {if(query.sort[0].descending){content.sort = query.sort[0].attribute + "-desc";} else {content.sort = query.sort[0].attribute + "-asc";}}} else {//The default sort in the Dojo Data API is ascending.content.sort = "date-posted-asc";}primaryKey.push("sort:"+content.sort);//Generate a unique key for this request, so the store can//detect duplicate requests.primaryKey = primaryKey.join(".");secondaryKey = secondaryKey.length > 0 ? "." + secondaryKey.join(".") : "";var requestKey = primaryKey + secondaryKey;//Make a copy of the request, in case the source object is modified//before the request completesrequest = {query: query,count: request._curCount,start: request._curStart,_realCount: request._realCount,_realStart: request._realStart,onBegin: request.onBegin,onComplete: request.onComplete,onItem: request.onItem};var thisHandler = {request: request,fetchHandler: fetchHandler,errorHandler: errorHandler};//If the request has already been made, but not yet completed,//then add the callback handler to the list of handlers//for this request, and finish.if(this._handlers[requestKey]){this._handlers[requestKey].push(thisHandler);return;}this._handlers[requestKey] = [thisHandler];//Linking this up to Flickr is a PAIN!var self = this;var handle = null;var getArgs = {url: this._flickrRestUrl,preventCache: true,content: content};var doHandle = function(processedData, data, handler){var onBegin = handler.request.onBegin;handler.request.onBegin = null;var maxPhotos;var req = handler.request;if(typeof(req._realStart) != undefined && req._realStart != null) {req.start = req._realStart;req.count = req._realCount;req._realStart = req._realCount = null;}//If the request contains an onBegin method, the total number//of photos must be calculated.if(onBegin){if(data && typeof(data.photos.perpage) != "undefined" && typeof(data.photos.pages) != "undefined"){if(data.photos.perpage * data.photos.pages <= handler.request.start + handler.request.count){//If the final page of results has been received, it is possible to//know exactly how many photos there aremaxPhotos = handler.request.start + data.photos.photo.length;}else{//If the final page of results has not yet been received,//it is not possible to tell exactly how many photos exist, so//return the number of pages multiplied by the number of photos per page.maxPhotos = data.photos.perpage * data.photos.pages;}self._maxPhotosPerUser[primaryKey] = maxPhotos;onBegin(maxPhotos, handler.request);} else if(self._maxPhotosPerUser[primaryKey]) {onBegin(self._maxPhotosPerUser[primaryKey], handler.request);}}//Call whatever functions the caller has defined on the request object, except for onBeginhandler.fetchHandler(processedData, handler.request);if(onBegin){//Replace the onBegin function, if it existed.handler.request.onBegin = onBegin;}};//Define a callback for the script that iterates through a list of//handlers for this piece of data. Multiple requests can come into//the store for the same data.var myHandler = function(data){//The handler should not be called more than once, so disconnect it.//if(handle !== null){ dojo.disconnect(handle); }if(data.stat != "ok"){errorHandler(null, request);}else{ //Process the items...var handlers = self._handlers[requestKey];if(!handlers){console.log("FlickrRestStore: no handlers for data", data);return;}self._handlers[requestKey] = null;self._prevRequests[requestKey] = data;//Process the data once.var processedData = self._processFlickrData(data, request, primaryKey);if(!self._prevRequestRanges[primaryKey]) {self._prevRequestRanges[primaryKey] = [];}self._prevRequestRanges[primaryKey].push({start: request.start,end: request.start + data.photos.photo.length});//Iterate through the array of handlers, calling each one.for(var i = 0; i < handlers.length; i++ ){doHandle(processedData, data, handlers[i]);}}};var data = this._prevRequests[requestKey];//If the data was previously retrieved, there is no need to fetch it again.if(data){this._handlers[requestKey] = null;doHandle(this._cache[primaryKey], data, thisHandler);return;} else if(this._checkPrevRanges(primaryKey, request.start, request.count)) {//If this range of data has already been retrieved, reuse it.this._handlers[requestKey] = null;doHandle(this._cache[primaryKey], null, thisHandler);return;}dojo.global[callbackFn] = function(data){myHandler(data);//Clean up the function, it should never be called againdojo.global[callbackFn] = null;};var deferred = dojo.io.script.get(getArgs);//We only set up the errback, because the callback isn't ever really used because we have//to link to the jsonFlickrFeed function....deferred.addErrback(function(error){dojo.disconnect(handle);errorHandler(error, request);});},getAttributes: function(item){// summary:// See dojo.data.api.Read.getAttributes()return ["title", "author", "imageUrl", "imageUrlSmall","imageUrlMedium", "imageUrlThumb", "link","dateTaken", "datePublished"];},getValues: function(item, attribute){// summary:// See dojo.data.api.Read.getValue()this._assertIsItem(item);this._assertIsAttribute(attribute);if(attribute === "title"){return [this._unescapeHtml(item.title)]; // String}else if(attribute === "author"){return [item.ownername]; // String}else if(attribute === "imageUrlSmall"){return [item.media.s]; // String}else if(attribute === "imageUrl"){return [item.media.l]; // String}else if(attribute === "imageUrlMedium"){return [item.media.m]; // String}else if(attribute === "imageUrlThumb"){return [item.media.t]; // String}else if(attribute === "link"){return ["http://www.flickr.com/photos/" + item.owner + "/" + item.id]; // String}else if(attribute === "dateTaken"){return item.datetaken;}else if(attribute === "datePublished"){return item.datepublished;}return undefined;},_processFlickrData: function(/* Object */data, /* Object */request, /* String */ cacheKey){// summary: Processes the raw data from Flickr and updates the internal cache.// data:// Data returned from Flickr// request:// The original dojo.data.Request object passed in by the user.//If the data contains an 'item' object, it has not come from the REST services,//so process it using the FlickrStore.if(data.items){return dojox.data.FlickrStore.prototype._processFlickrData.apply(this,arguments);}var template = ["http://farm", null, ".static.flickr.com/", null, "/", null, "_", null];var items = [];if(data.stat == "ok" && data.photos && data.photos.photo){items = data.photos.photo;//Add on the store ref so that isItem can work.for(var i = 0; i < items.length; i++){var item = items[i];item[this._storeRef] = this;template[1] = item.farm;template[3] = item.server;template[5] = item.id;template[7] = item.secret;var base = template.join("");item.media = {s: base + "_s.jpg",m: base + "_m.jpg",l: base + ".jpg",t: base + "_t.jpg"};}}var start = request.start ? request.start : 0;var arr = this._cache[cacheKey];if(!arr) {this._cache[cacheKey] = arr = [];}for(var count = 0; count < items.length; count++){arr[count + start] = items[count];}return arr; // Array},_checkPrevRanges: function(primaryKey, start, count) {var end = start + count;var arr = this._prevRequestRanges[primaryKey];if(!arr) {return false;}for(var i = 0; i< arr.length; i++) {if(start >= arr[i].start &&end <= arr[i].end) {return true;}}return false;}});}