PageRenderTime 65ms CodeModel.GetById 37ms RepoModel.GetById 0ms app.codeStats 0ms

/public/dojox/data/FlickrRestStore.js

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