UNPKG

9.56 kBJavaScriptView Raw
1var crypto = require ('crypto'),
2 util = require ('util'),
3 urlUtil = require ('url'),
4 path = require ('path'),
5 os = require ('os'),
6 task = require ('./base'),
7 urlModel = require ('../model/from-url'),
8 io = require ('fsobject');
9
10
11var cacheTask = module.exports = function (config) {
12
13 try {
14 if (typeof project === "undefined") {
15 // var hash = crypto.createHash('md5').update(sketchFolder).digest('hex');
16 // console.log(hash); // 9b74c9897bac770ffc029102a200c5de
17
18 this.cachePath = new io (os.tmpdir()) // path.join (os.tmpdir(), hash.substr (0, 8));
19 } else {
20 if (!project.config.cachePath) {
21 console.log ('cachePath not defined in project config!');
22 }
23 this.cachePath = project.root.file_io (project.config.cachePath);
24 }
25
26 if (!cacheTask.caching) {
27 cacheTask.caching = {};
28 }
29 } catch (e) {
30 console.log (e);
31 }
32
33
34 this.url = config.url;
35
36 this.init (config);
37
38 if (!this.timeout)
39 this.timeout = 10000;
40
41};
42
43util.inherits (cacheTask, task);
44
45util.extend (cacheTask.prototype, {
46 /**
47 * Generates file name as a hash sum
48 * based on the cached file original URL.
49 */
50 generateCacheFileName: function () {
51
52 if (this.cacheFilePath)
53 return this.cacheFilePath;
54
55 var shasum = crypto.createHash('sha1');
56 shasum.update (this.url.href);
57 if (this.bodyData) shasum.update (JSON.stringify (this.bodyData));
58
59 // TODO: any request excluding GET must contain random number
60
61 this.cacheFile = this.cachePath.file_io (shasum.digest('hex'));
62 this.cacheFilePath = this.cacheFile.path;
63 this.cacheFileName = path.basename(this.cacheFile.path);
64
65 return this.cacheFilePath;
66 }
67});
68
69cacheTask.prototype.initModel = function () {
70 var self = this;
71
72 if (Object.is('String', this.url)) {
73 try {
74 this.url = urlUtil.parse(this.url, true);
75 } catch (e) {
76 this.emitError(e);
77 return;
78 }
79 }
80
81 if (!this.url.href) {
82 this.url.href = urlUtil.format (this.url);
83 }
84
85 this.url.headers = this.headers || {};
86
87 this.model = new urlModel (this.url, this);
88 this.url = this.model.url;
89
90 this.model.proxy = this.proxy;
91
92 // TODO: check for data amount periodically, say, in 1 second
93// this.model.on ('data', function (chunks) {
94// this.activityCheck ('model.fetch data');
95// }.bind (this));
96
97 this.model.on ('progress', function (current, total) {
98 this.activityCheck ('model.fetch data');
99 this.emit ('progress', current, total);
100 }.bind (this));
101
102 // self.model.on ('error', function (e, data) {
103 // // console.log("%%%%%%%%%%cache failed");
104 // self.emitError(e, data);
105 // });
106 this.model.on ('error', function (error) {
107// console.log (error);
108 this.clearOperationTimeout();
109 this.finishWith (error, 'failed');
110 }.bind (this));
111
112}
113
114cacheTask.prototype.isSameUrlLoading = function () {
115 var self = this;
116 // TODO: another task can download url contents to buffer/file and vice versa
117 // other task is caching requested url
118 var anotherTask = cacheTask.caching[self.cacheFilePath];
119
120 if (anotherTask && anotherTask != self) {
121
122 this.emit ('log', 'another process already downloading ' + this.url.href + ' to ' + this.cacheFilePath);
123 // we simply wait for another task
124 anotherTask.on ('complete', function (data) {
125 // TODO: add headers/contents
126 // TODO: check for file state. it is actually closed?
127 self.completed (data);
128 });
129 anotherTask.on ('error', function (e, data) {
130 self.emitError(e, data);
131 });
132 return true;
133 } else {
134 cacheTask.caching[self.cacheFilePath] = self;
135 }
136 return false;
137 };
138
139cacheTask.prototype.activityCheck = function (place) {
140 var self = this;
141
142 self.clearOperationTimeout();
143
144 self.timeoutId = setTimeout(function () {
145 self.state = 5;
146 self.emit (
147 'log', 'timeout is over for ' + place + ' operation'
148 );
149 self.model.stop ('timeout');
150// self._cancel();
151
152 }, self.timeout);
153
154}
155
156/**
157 * @method toBuffer
158 * Downloads a given URL into a memory buffer.
159 *
160 * @cfg {String} url (required) A URL to download from.
161 * @cfg {Number} [retries=0] The number of times to retry to run the task.
162 * @cfg {Number} [timeout=10000] Timeout for downloading of each file
163 * (in milliseconds)
164 * @cfg {String} [successCodes="200"] (HTTP only) Success status codes (example: "2xx,4xx")
165 * otherwise task will fail
166 * @cfg {String|Object} bodyData (HTTP only) POST body, can be string (raw POST data)
167 * or object (urlencoded query)
168 * @cfg {String} headers (HTTP only) HTTP headers for request
169 * @cfg {String} auth (HTTP only) basic auth
170 */
171cacheTask.prototype.toBuffer = function () {
172 var self = this;
173
174 self.download = {};
175
176 self.activityCheck ('task run');
177
178 // create model and listen
179 // model is a class for working with particular network protocol
180 // WHY? why model can be defined?
181 if (!self.model) {
182
183 // console.log("self.model.url -> ", self.url.fetch.uri);
184 self.initModel ();
185 self.model.on ('end', function () {
186 /*var srcName = self.model.dataSource.res.headers['content-disposition'].match(/filename=\"([^"]+)/)[1];
187 self.res = {};
188 self.res.srcName = srcName ? srcName : "";
189 console.log("self.res -> ", self.res);*/
190 self.clearOperationTimeout();
191 // self.res.cacheFilePath = self.cacheFilePath
192 // self.completed (self.res);
193 self.finishWith ({
194 data: self.download.data
195 });
196 });
197
198 // model error handling at @method initModel
199 }
200
201 self.emit ('log', 'start loading from ' + self.url.href + ' to memory buffer');
202
203 self.activityCheck ('model.fetch start');
204 self.model.fetch ({to: self.download});
205 };
206
207cacheTask.prototype.finishWith = function (result, method, metaJSON) {
208 var self = this;
209 var model = self.model,
210 ds;
211
212 var meta;
213 if (metaJSON) {
214 meta = JSON.parse (metaJSON);
215 }
216
217 if (model)
218 ds = self.model.dataSource;
219 if (ds && ds.addResultFields) {
220 ds.addResultFields (result, meta);
221 }
222
223// method = method || 'completed';
224 if (!method)
225 if (ds && ds.isSuccessResponse && ds.isSuccessResponse ()) {
226 method = 'completed';
227 } else {
228 method = 'failed';
229 }
230
231 self[method] (result);
232 };
233/**
234 * @method toFile
235 * Downloads a given URL into a uniquely named file.
236 *
237 * @cfg {String} url (required) A URL to download from.
238 * @cfg {Number} [retries=0] The number of times to retry to run the task.
239 * @cfg {Number} [timeout=10000] Timeout for downloading of each file
240 * (in milliseconds)
241 * @cfg {String} [successCodes="200"] Success HTTP status codes (example: "2xx,4xx")
242 * otherwise task will fail
243 * @cfg {String|Object} bodyData (HTTP only) POST body, can be string (raw POST data)
244 * or object (urlencoded query)
245 * @cfg {String} headers (HTTP only) HTTP headers for request
246 * @cfg {String} auth (HTTP only) basic auth
247 */
248cacheTask.prototype.toFile = function () {
249 var self = this;
250
251 self.activityCheck ('task run');
252
253 // create model and listen
254 // model is a class for working with particular network protocol
255 // WHY? why model can be defined?
256 if (!self.model) {
257
258 // console.log("self.model.url -> ", self.url.fetch.uri);
259 self.initModel ();
260 self.model.on ('end', function () {
261 /*var srcName = self.model.dataSource.res.headers['content-disposition'].match(/filename=\"([^"]+)/)[1];
262 self.res = {};
263 self.res.srcName = srcName ? srcName : "";
264 console.log("self.res -> ", self.res);*/
265 self.clearOperationTimeout();
266 self.cacheFile.chmod (0640, function (err) {
267 // TODO: check for exception (and what's next?)
268 delete cacheTask.caching[self.cacheFilePath];
269 // self.res.cacheFilePath = self.cacheFilePath
270 // self.completed (self.res);
271 self.finishWith ({
272 fileName: self.cacheFileName,
273 filePath: self.cacheFilePath
274 });
275 });
276 var metaFile = new io (self.cacheFilePath+'.meta');
277 metaFile.writeFile (JSON.stringify ({
278 code: self.model.dataSource.res.statusCode,
279 headers: self.model.dataSource.res.headers,
280 url: self.model.url.href,
281 urlFileName: path.basename (self.model.url.href)
282 }, null, "\t"));
283 });
284 }
285
286 this.generateCacheFileName ();
287
288 if (self.isSameUrlLoading ())
289 return;
290
291 self.cacheFile.stat (function (err, stats) {
292
293 if (err || ! (stats.mode & 0644 ^ 0600)) {
294 return self.cacheMiss ();
295 }
296
297 var metaFile = new io (self.cacheFilePath+'.meta');
298 metaFile.readFile (function (err, contents) {
299
300 if (err) {
301 return self.cacheMiss ();
302 }
303
304 try {
305 var js = JSON.parse (contents);
306 } catch (e) {
307 return self.cacheMiss ();
308 }
309
310 self.clearOperationTimeout();
311
312 self.emit ('log', 'file already downloaded from ' + self.url.href + ' to ' + self.cacheFilePath);
313 delete cacheTask.caching[self.cacheFilePath];
314
315 self.finishWith ({
316 fileName: self.cacheFileName,
317 filePath: self.cacheFilePath,
318 }, 'completed', contents);
319 });
320 });
321 };
322
323cacheTask.prototype.cacheMiss = function () {
324 try {
325 var writeStream = this.cacheFile.writeStream ({
326 flags: 'w', // constants.O_CREAT | constants.O_WRONLY
327 mode: 0600
328 });
329 writeStream.on ('error', function (err) {
330 this.emit ('log', 'write error: ' + err);
331 writeStream.end();
332 });
333 } catch (e) {
334 this.emit ('log', 'cannot open for write: ' + this.cacheFilePath);
335 this.emitError(e);
336 return;
337 }
338
339 this.emit ('log', 'start caching from ' + this.url.href + ' to ' + this.cacheFilePath);
340
341 this.activityCheck ('model.fetch start');
342 this.model.fetch ({to: writeStream});
343}
344
345cacheTask.prototype.run = cacheTask.prototype.toFile;
346
347cacheTask.prototype.emitError = function (e, data) {
348 if (e) {
349 this.finishWith (data || e, 'failed');
350 return true;
351 } else {
352 return false;
353 }
354 }