FlickrRestStore.js 15 KB

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