PageRenderTime 62ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/static/js/models/thumbnails.js

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