PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/pancake-web/pancake/web/static/js/models/thumbnails.js

https://bitbucket.org/mozillapancake/pancake
JavaScript | 243 lines | 123 code | 36 blank | 84 comment | 6 complexity | dd2bd0d832f2c957ac813728304371de MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, MIT, Apache-2.0
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. define([
  5. 'config',
  6. 'underscore',
  7. 'backbone',
  8. 'models/livecollection',
  9. 'lib/template',
  10. 'lib/objecttools',
  11. 'lib/errors',
  12. 'lib/lazily',
  13. 'logger'
  14. ], function (
  15. config,
  16. util,
  17. Backbone,
  18. LiveCollection,
  19. template,
  20. objectTools,
  21. errors,
  22. lazily,
  23. logger
  24. ) {
  25. // Cache underscore templates for URL endpoints.
  26. var makeStatusUrl = template(config.thumbnailerStatus);
  27. var ThumbnailModel = Backbone.Model.extend({
  28. idAttribute: 'thumbnail_key'
  29. });
  30. // A simple reference object containing width/height objects for
  31. // thumbnails at various keyword sizes.
  32. var thumbnailSizes = {
  33. '1': {
  34. width: 150,
  35. height: 100
  36. },
  37. '2': {
  38. width: 300,
  39. height: 200
  40. }
  41. };
  42. // Get the default thumbnail size based on device pixel ratio.
  43. var thumbnailSize = thumbnailSizes[config.devicePixelRatio];
  44. var translateResponseKeys = util.bind(objectTools.translate, null, {
  45. // Convert key to id, so we can use all of the nice
  46. // collection/model getters.
  47. 'key': 'thumbnail_key',
  48. 'status': 'thumbnail_status'
  49. });
  50. var ThumbnailJobCollection = LiveCollection.extend({
  51. model: ThumbnailModel,
  52. initialize: function (attributes, options) {
  53. options = options || {};
  54. this.jobId = options.jobId;
  55. logger.log('New Thumbnail Job Collection', this.jobId);
  56. this.bind('success', this.onSuccess, this);
  57. },
  58. url: lazily(function () {
  59. if (!this.jobId) throw new errors.IdMissingError( "required attribute jobId missing from ThumbnailJobCollection instance" );
  60. return makeStatusUrl({ jobId: this.jobId });
  61. }),
  62. pollUntilFinishedProcessing: function () {
  63. this.stream({
  64. interval: 3000,
  65. tries: 10,
  66. diff: true
  67. });
  68. },
  69. onSuccess: function (resp, options) {
  70. var thumbnails = util.map(resp.sites, translateResponseKeys);
  71. // Get all of the statuses from thumbnails
  72. var statuses = util.pluck(thumbnails, 'thumbnail_status');
  73. var ready = util.map(statuses, function (status) {
  74. return status === 'ready';
  75. });
  76. var collection = this;
  77. logger.log(
  78. 'Polled Thumbnail Job API',
  79. '(' + ready.length + ' of ' + thumbnails.length + ' ready)',
  80. collection.url()
  81. );
  82. // When the job id is generated by the caller (social code), there is
  83. // the possibility that the returned thumbnail list is empty the first
  84. // few polls.
  85. if (thumbnails.length === 0) {
  86. return;
  87. }
  88. // There are 3 thumbnail statuses:
  89. //
  90. // 1. processing
  91. // 2. error
  92. // 3. ready
  93. //
  94. // Filter out everything that is not processing, and see if there
  95. // is anything left over. If all thumbnails are processed,
  96. // stop polling.
  97. if (util.indexOf(statuses, 'processing') === -1) {
  98. collection.unstream();
  99. }
  100. },
  101. // Defining a custom parse implementation that massages data
  102. // from our JSON API into a Backbone.Model-compatible format.
  103. //
  104. // Sample return data:
  105. //
  106. // {
  107. // "job": "...",
  108. // "sites": [
  109. // {
  110. // "key": "...",
  111. // "status": "processing",
  112. // "url": "http://instapaper.com/"
  113. // }
  114. // ...
  115. // ],
  116. // "success": true
  117. // }
  118. //
  119. // ...which is turned into:
  120. //
  121. // [
  122. // {
  123. // "thumbnail_key": "...",
  124. // "thumbnail_status": "processing",
  125. // // Created from template + ID during parse:
  126. // "thumbnail_url": "http://example.com/example.png"
  127. // }
  128. // ...
  129. // ]
  130. parse: function (resp) {
  131. // Normalize response keys
  132. var thumbnails = util.map(resp.sites, translateResponseKeys);
  133. // Make sure JobID is correct.
  134. // If no job is found in response, log the response for reference.
  135. //
  136. // This case is known to happen when a stack is requested, and it comes
  137. // back with no results. The stack API will hand us a thumbnail job ID
  138. // for that stack, but when the ID is handed to the thumbnailer, it
  139. // will return:
  140. //
  141. // {
  142. // "reason": "no such job",
  143. // "success": false
  144. // }
  145. if (!thumbnails) {
  146. logger.log('No thumbnails were found in job.', resp);
  147. }
  148. // Filter out thumbnails that aren't ready.
  149. thumbnails = util.filter(thumbnails, function (thumbnail) {
  150. return thumbnail.thumbnail_status === 'ready';
  151. });
  152. // Create a template function for the `image_url` description.
  153. var makeThumbnailUrl = template(resp.image_url);
  154. // Add `thumbnail_url` property.
  155. thumbnails = util.map(thumbnails, function (thumbnail) {
  156. thumbnail.thumbnail_url = makeThumbnailUrl(util.extend({
  157. key: thumbnail.thumbnail_key
  158. }, thumbnailSize));
  159. return thumbnail;
  160. });
  161. // Return the full list of ready thumbnails. Since this method is a
  162. // child of LiveCollection, `add` will by default de-dupe thumbnails
  163. // with the same ID.
  164. return thumbnails;
  165. }
  166. });
  167. // A parse implementation that gives you everything you need to spawn
  168. // ThumbnailJobCollections. Call from within other parse methods:
  169. //
  170. // resp = thumbnails.parse.call(this, resp);
  171. var parse = function (resp) {
  172. this.trigger('parse', this, resp);
  173. this.jobId = resp.thumbnails_job;
  174. return resp;
  175. };
  176. // Manages thumbnail jobs by ID. Makes sure there is never more than one
  177. // job per ID.
  178. var jobManager = {
  179. jobs: [],
  180. // A factory for job collections. If your job doesn't exist yet, it
  181. // will create one. Bind to the collection's `reset` event to get updates.
  182. // TODO: we should be calling `add` event on fetch.
  183. job: function (key) {
  184. var job = this.jobs[key];
  185. // Hit the callback if we already have a stored job.
  186. if (!job) {
  187. // Create a new thumbnail job collection. Set the key of the
  188. // thumbnail job (which ThumbnailJobCollection turns into a URL).
  189. var collection = new ThumbnailJobCollection([], {
  190. jobId: key
  191. });
  192. collection.pollUntilFinishedProcessing();
  193. // Memoize. Make sure this job is not duplicated.
  194. job = this.jobs[key] = collection;
  195. }
  196. return job;
  197. }
  198. };
  199. // Create a bound version of the job manager.
  200. var getJobFromManager = util.bind(jobManager.job, jobManager);
  201. return {
  202. ThumbnailModel: ThumbnailModel,
  203. JobCollection: ThumbnailJobCollection,
  204. getJobFromManager: getJobFromManager,
  205. parse: parse
  206. };
  207. });