Skip to content

Commit 5c8a139

Browse files
author
Amir Tocker
committed
Add Responsive Breakpoints cache
1 parent 6838225 commit 5c8a139

18 files changed

+645
-41
lines changed

lib/cloudinary.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.
88

99
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
1010

11-
var _ = require('lodash'),
12-
cloudinary = module.exports;
11+
var _ = require('lodash');
1312

13+
var cloudinary = module.exports;
1414
exports.config = require("./config");
1515
exports.utils = require("./utils");
1616
exports.uploader = require("./uploader");
1717
exports.api = require("./api");
1818
exports.PreloadedFile = require("./preloaded_file");
19+
exports.Cache = require('./cache');
1920
var optionConsume = cloudinary.utils.option_consume;
2021

2122
var ensureOption = require('./utils/ensureOption').defaults(cloudinary.config());
2223

23-
exports.url = function (public_id, options) {
24+
exports.url = function url(public_id, options) {
2425
options = _.extend({}, options);
2526
return cloudinary.utils.url(public_id, options);
2627
};

lib/uploader.js

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ var extend = utils.extend,
5656
isObject = utils.isObject,
5757
build_upload_params = utils.build_upload_params;
5858

59+
var Cache = require('./cache');
60+
5961
exports.unsigned_upload_stream = function unsigned_upload_stream(upload_preset, callback) {
6062
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
6163
return exports.upload_stream(callback, utils.merge(options, {
@@ -415,6 +417,35 @@ function call_context_api(context, command) {
415417
return [params];
416418
});
417419
}
420+
/**
421+
* Cache (part of) the upload results.
422+
* @param result
423+
* @param {object} options
424+
* @param {string} options.type
425+
* @param {string} options.resource_type
426+
*/
427+
428+
429+
function cacheResults(result, _ref) {
430+
var type = _ref.type,
431+
resource_type = _ref.resource_type;
432+
433+
if (result.responsive_breakpoints) {
434+
result.responsive_breakpoints.forEach(function (_ref2) {
435+
var transformation = _ref2.transformation,
436+
url = _ref2.url,
437+
breakpoints = _ref2.breakpoints;
438+
return Cache.set(result.public_id, {
439+
type: type,
440+
resource_type: resource_type,
441+
raw_transformation: transformation,
442+
format: path.extname(breakpoints[0].url).slice(1)
443+
}, breakpoints.map(function (i) {
444+
return i.width;
445+
}));
446+
});
447+
}
448+
}
418449

419450
function parseResult(buffer, res) {
420451
var result = '';
@@ -480,6 +511,7 @@ function call_api(action, callback, options, get_params) {
480511
result["error"]["http_code"] = res.statusCode;
481512
deferred.reject(result.error);
482513
} else {
514+
cacheResults(result, options);
483515
deferred.resolve(result);
484516
}
485517

@@ -504,10 +536,10 @@ function call_api(action, callback, options, get_params) {
504536
}
505537
};
506538

507-
var post_data = Object.entries(params).reduce(function (entries, _ref) {
508-
var _ref2 = _slicedToArray(_ref, 2),
509-
key = _ref2[0],
510-
value = _ref2[1];
539+
var post_data = Object.entries(params).reduce(function (entries, _ref3) {
540+
var _ref4 = _slicedToArray(_ref3, 2),
541+
key = _ref4[0],
542+
value = _ref4[1];
511543

512544
if (isArray(value)) {
513545
key = key.endsWith('[]') ? key : key + '[]';
@@ -520,17 +552,17 @@ function call_api(action, callback, options, get_params) {
520552
}
521553

522554
return entries;
523-
}, []).filter(function (_ref3) {
524-
var _ref4 = _slicedToArray(_ref3, 2),
525-
key = _ref4[0],
526-
value = _ref4[1];
527-
528-
return value != null;
529-
}).map(function (_ref5) {
555+
}, []).filter(function (_ref5) {
530556
var _ref6 = _slicedToArray(_ref5, 2),
531557
key = _ref6[0],
532558
value = _ref6[1];
533559

560+
return value != null;
561+
}).map(function (_ref7) {
562+
var _ref8 = _slicedToArray(_ref7, 2),
563+
key = _ref8[0],
564+
value = _ref8[1];
565+
534566
return Buffer.from(encodeFieldPart(boundary, key, value), 'utf8');
535567
});
536568
var result = post(api_url, post_data, boundary, file, handle_response, options);

lib/utils/generateBreakpoints.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
1010

1111
/**
1212
* Helper function. Gets or populates srcset breakpoints using provided parameters
13-
* @private
1413
* Either the breakpoints or min_width, max_width, max_images must be provided.
1514
*
16-
* @param {object} srcset with either `breakpoints` or `min_width`, `max_width`, and `max_images`
17-
*
18-
* @param {number[]} srcset.breakpoints An array of breakpoints.
19-
* @param {int} srcset.min_width Minimal width of the srcset images.
20-
* @param {int} srcset.max_width Maximal width of the srcset images.
21-
* @param {int} srcset.max_images Number of srcset images to generate.
15+
* @module utils
16+
* @private
17+
* @param {srcset} srcset Options with either `breakpoints` or `min_width`, `max_width`, and `max_images`
2218
*
2319
* @return {number[]} Array of breakpoints
2420
*

lib/utils/srcsetUtils.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ var isEmpty = utils.isEmpty;
99
var generateBreakpoints = require('./generateBreakpoints');
1010

1111
var config = require('../../cloudinary').config;
12+
13+
var Cache = require('./../Cache');
1214
/**
1315
* Options used to generate the srcset attribute.
1416
* @typedef {object} srcset
@@ -54,6 +56,33 @@ function scaledUrl(public_id, width, transformation) {
5456
}]);
5557
return utils.url(public_id, configParams);
5658
}
59+
/**
60+
* If cache is enabled, get the breakpoints from the cache. If the values were not found in the cache,
61+
* or cache is not enabled, generate the values.
62+
* @param {srcset} srcset The srcset configuration parameters
63+
* @param {string} public_id
64+
* @param {object} options
65+
* @return {*|Array}
66+
*/
67+
68+
69+
function getOrGenerateBreakpoints(public_id) {
70+
var srcset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
71+
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
72+
var breakpoints = [];
73+
74+
if (srcset.useCache) {
75+
breakpoints = Cache.get(public_id, options);
76+
77+
if (!breakpoints) {
78+
breakpoints = [];
79+
}
80+
} else {
81+
breakpoints = generateBreakpoints(srcset);
82+
}
83+
84+
return breakpoints;
85+
}
5786
/**
5887
* Helper function. Generates srcset attribute value of the HTML img tag
5988
* @private
@@ -117,7 +146,7 @@ function generateImageResponsiveAttributes(publicId) {
117146
var generateSrcset = !attributes.srcset;
118147

119148
if (generateSrcset || generateSizes) {
120-
var breakpoints = generateBreakpoints(srcsetData);
149+
var breakpoints = getOrGenerateBreakpoints(publicId, srcsetData, options);
121150

122151
if (generateSrcset) {
123152
var transformation = srcsetData.transformation;

src/cache.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
2+
const CACHE = Symbol.for("com.cloudinary.cache");
3+
const CACHE_ADAPTER = Symbol.for("com.cloudinary.cacheAdapter");
4+
const utils = require('./utils');
5+
const {ensurePresenceOf} = utils;
6+
7+
/**
8+
* The adapter used to communicate with the underlying cache storage
9+
*/
10+
class CacheAdapter {
11+
constructor(storage) {}
12+
13+
/**
14+
* Get a value from the cache
15+
* @param {string} publicId
16+
* @param {string} type
17+
* @param {string} resourceType
18+
* @param {string} transformation
19+
* @return {*} the value associated with the provided arguments
20+
*/
21+
get(publicId, type, resourceType, transformation, format) {}
22+
23+
/**
24+
* Set a new value in the cache
25+
* @param {string} publicId
26+
* @param {string} type
27+
* @param {string} resourceType
28+
* @param {string} transformation
29+
* @param {*} value
30+
*/
31+
set(publicId, type, resourceType, transformation, format, value) {}
32+
33+
/**
34+
* Delete all values in the cache
35+
*/
36+
flushAll() {}
37+
38+
}
39+
/**
40+
* @class Cache
41+
* Stores and retrieves values identified by publicId / options pairs
42+
*/
43+
const Cache = {
44+
/**
45+
* The adapter interface. Extend this class to implement a specific adapter.
46+
* @type CacheAdapter
47+
*/
48+
CacheAdapter,
49+
/**
50+
* Set the cache adapter
51+
* @param {CacheAdapter} adapter The cache adapter
52+
*/
53+
setAdapter(adapter) {
54+
if(this.adapter){
55+
console.warn("Overriding existing cache adapter");
56+
}
57+
this.adapter = adapter;
58+
},
59+
/**
60+
* Get the adapter the Cache is using
61+
* @return {CacheAdapter} the current cache adapter
62+
*/
63+
getAdapter() {
64+
return this.adapter;
65+
},
66+
/**
67+
* Get an item from the cache
68+
* @param {string} publicId
69+
* @param {object} options
70+
* @return {*}
71+
*/
72+
get(publicId, options) {
73+
if(!this.adapter) {return undefined;}
74+
ensurePresenceOf({publicId});
75+
let transformation = utils.generate_transformation_string({...options});
76+
return this.adapter.get(
77+
publicId, options.type || 'upload',
78+
options.resource_type || 'image',
79+
transformation,
80+
options.format
81+
);
82+
},
83+
/**
84+
* Set a new value in the cache
85+
* @param {string} publicId
86+
* @param {object} options
87+
* @param {*} value
88+
* @return {*}
89+
*/
90+
set(publicId, options, value) {
91+
if(!this.adapter) {return undefined;}
92+
ensurePresenceOf({publicId, value});
93+
let transformation = utils.generate_transformation_string({...options});
94+
return this.adapter.set(
95+
publicId,
96+
options.type || 'upload',
97+
options.resource_type || 'image',
98+
transformation,
99+
options.format,
100+
value
101+
);
102+
},
103+
/**
104+
* Clear all items in the cache
105+
* @return {*} Returns the value from the adapter's flushAll() method
106+
*/
107+
flushAll() {
108+
if(!this.adapter) {return undefined;}
109+
return this.adapter.flushAll();
110+
}
111+
112+
};
113+
114+
// Define singleton property
115+
Object.defineProperty(Cache, "instance", {
116+
get() {
117+
return global[CACHE];
118+
}
119+
});
120+
Object.defineProperty(Cache, "adapter", {
121+
/**
122+
*
123+
* @return {CacheAdapter} The current cache adapter
124+
*/
125+
get() {
126+
return global[CACHE_ADAPTER];
127+
},
128+
/**
129+
* Set the cache adapter to be used by Cache
130+
* @param {CacheAdapter} adapter Cache adapter
131+
*/
132+
set(adapter) {
133+
global[CACHE_ADAPTER] = adapter;
134+
}
135+
});
136+
Object.freeze(Cache);
137+
138+
// Instantiate singleton
139+
let symbols = Object.getOwnPropertySymbols(global);
140+
if(!symbols.includes(CACHE)) {
141+
global[CACHE] = Cache;
142+
}
143+
144+
/**
145+
* Store key value pairs
146+
147+
*/
148+
module.exports = Cache;
149+

src/cache/FileKeyValueStorage.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const rimraf = require('../utils/rimraf');
4+
5+
class FileKeyValueStorage {
6+
constructor({baseFolder} = {}) {
7+
this.init(baseFolder);
8+
}
9+
10+
init(baseFolder) {
11+
if (baseFolder) {
12+
fs.access(baseFolder, (err, result) => {
13+
if (err) throw err;
14+
this.baseFolder = baseFolder;
15+
});
16+
} else {
17+
if(!fs.existsSync('test_cache')) {
18+
fs.mkdirSync('test_cache');
19+
}
20+
this.baseFolder = fs.mkdtempSync('test_cache/cloudinary_cache_');
21+
console.info("Created temporary cache folder at " + this.baseFolder);
22+
}
23+
}
24+
25+
get(key) {
26+
let value = fs.readFileSync(this.getFilename(key));
27+
try {
28+
return JSON.parse(value);
29+
} catch(e) {
30+
throw "Cannot parse cache value";
31+
}
32+
}
33+
34+
set(key, value) {
35+
fs.writeFileSync(this.getFilename(key), JSON.stringify(value));
36+
}
37+
38+
clear() {
39+
let files = fs.readdirSync(this.baseFolder);
40+
for(let file of files) {
41+
fs.unlinkSync(path.join(this.baseFolder, file));
42+
}
43+
}
44+
45+
deleteBaseFolder() {
46+
rimraf(this.baseFolder);
47+
}
48+
49+
getFilename(key) {
50+
return path.format({name: key, base: key, ext: '.json', dir: this.baseFolder});
51+
}
52+
53+
}
54+
55+
module.exports = FileKeyValueStorage;

0 commit comments

Comments
 (0)