1 | var 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 |
|
11 | var cacheTask = module.exports = function (config) {
|
12 |
|
13 | try {
|
14 | if (typeof project === "undefined") {
|
15 |
|
16 |
|
17 |
|
18 | this.cachePath = new io (os.tmpdir())
|
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 |
|
43 | util.inherits (cacheTask, task);
|
44 |
|
45 | util.extend (cacheTask.prototype, {
|
46 | |
47 |
|
48 |
|
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 |
|
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 |
|
69 | cacheTask.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 |
|
93 |
|
94 |
|
95 |
|
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 |
|
103 |
|
104 |
|
105 |
|
106 | this.model.on ('error', function (error) {
|
107 |
|
108 | this.clearOperationTimeout();
|
109 | this.finishWith (error, 'failed');
|
110 | }.bind (this));
|
111 |
|
112 | }
|
113 |
|
114 | cacheTask.prototype.isSameUrlLoading = function () {
|
115 | var self = this;
|
116 |
|
117 |
|
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 |
|
124 | anotherTask.on ('complete', function (data) {
|
125 |
|
126 |
|
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 |
|
139 | cacheTask.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 |
|
151 |
|
152 | }, self.timeout);
|
153 |
|
154 | }
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 | cacheTask.prototype.toBuffer = function () {
|
172 | var self = this;
|
173 |
|
174 | self.download = {};
|
175 |
|
176 | self.activityCheck ('task run');
|
177 |
|
178 |
|
179 |
|
180 |
|
181 | if (!self.model) {
|
182 |
|
183 |
|
184 | self.initModel ();
|
185 | self.model.on ('end', function () {
|
186 | |
187 |
|
188 |
|
189 |
|
190 | self.clearOperationTimeout();
|
191 |
|
192 |
|
193 | self.finishWith ({
|
194 | data: self.download.data
|
195 | });
|
196 | });
|
197 |
|
198 |
|
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 |
|
207 | cacheTask.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 |
|
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 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 | cacheTask.prototype.toFile = function () {
|
249 | var self = this;
|
250 |
|
251 | self.activityCheck ('task run');
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | if (!self.model) {
|
257 |
|
258 |
|
259 | self.initModel ();
|
260 | self.model.on ('end', function () {
|
261 | |
262 |
|
263 |
|
264 |
|
265 | self.clearOperationTimeout();
|
266 | self.cacheFile.chmod (0640, function (err) {
|
267 |
|
268 | delete cacheTask.caching[self.cacheFilePath];
|
269 |
|
270 |
|
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 |
|
323 | cacheTask.prototype.cacheMiss = function () {
|
324 | try {
|
325 | var writeStream = this.cacheFile.writeStream ({
|
326 | flags: 'w',
|
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 |
|
345 | cacheTask.prototype.run = cacheTask.prototype.toFile;
|
346 |
|
347 | cacheTask.prototype.emitError = function (e, data) {
|
348 | if (e) {
|
349 | this.finishWith (data || e, 'failed');
|
350 | return true;
|
351 | } else {
|
352 | return false;
|
353 | }
|
354 | }
|