UNPKG

39.6 kBJavaScriptView Raw
1var path = require('path');
2var fs = require('fs');
3var util = require("./util");
4var request = require("request");
5
6var http = require("http");
7var https = require("https");
8
9exports = module.exports = function()
10{
11 var toCacheFilePath = function(filePath)
12 {
13 var filename = path.basename(filePath);
14 var basedir = path.dirname(filePath);
15
16 return path.join(basedir, "_" + filename + ".cache");
17 };
18
19 var generateURL = function(datastoreTypeId, datastoreId, objectTypeId, objectId, previewId)
20 {
21 var uri = null;
22
23 if (datastoreTypeId == "domain")
24 {
25 if (objectTypeId == "principal")
26 {
27 uri = "/domains/" + datastoreId + "/principals/" + objectId;
28 }
29 }
30
31 if (uri)
32 {
33 if (previewId)
34 {
35 uri += "/preview/" + previewId;
36 }
37 }
38
39 return uri;
40 };
41
42 /**
43 * Ensures that the content deployment root directory for Cloud CMS assets.
44 *
45 * This directory looks like:
46 *
47 * /hosts
48 * /<host>
49 * /content
50 * /<repositoryId>
51 * /<branchId>
52 * /<nodeId>
53 * /<localeKey>
54 * /attachments
55 * <attachmentId>
56 * <attachmentId>.cache
57 *
58 * @param host
59 * @param repositoryId
60 * @param branchId
61 * @parma nodeId
62 * @param locale
63 * @param callback
64 *
65 * @return {*}
66 */
67 var generateContentDirectoryPath = function(contentStore, repositoryId, branchId, nodeId, locale, callback)
68 {
69 if (!repositoryId)
70 {
71 callback({
72 "message": "Missing repositoryId in ensureContentDirectory()"
73 });
74 return;
75 }
76
77 if (!branchId)
78 {
79 callback({
80 "message": "Missing branchId in ensureContentDirectory()"
81 });
82 return;
83 }
84
85 if (!locale)
86 {
87 callback({
88 "message": "Missing locale in ensureContentDirectory()"
89 });
90 return;
91 }
92
93 var contentDirectoryPath = path.join(repositoryId, branchId, nodeId, locale);
94
95 callback(null, contentDirectoryPath);
96 };
97
98 /**
99 * Ensures that the content deployment root directory for Cloud CMS assets.
100 *
101 * This directory looks like:
102 *
103 * <rootStore>
104 * /hosts
105 * /<host>
106 * /data
107 * /<datastoreType>
108 * /<datastoreId>
109 * /<objectTypeId>
110 * /<objectId>
111 * /<localeKey>
112 * /attachments
113 * [attachmentId]
114 * [attachmentId].cache
115 *
116 * @param contentStore
117 * @param datastoreTypeId
118 * @param datastoreId
119 * @param objectTypeId
120 * @param objectId
121 * @param locale
122 * @param callback
123 *
124 * @return {*}
125 */
126 var generateAttachableDirectoryPath = function(contentStore, datastoreTypeId, datastoreId, objectTypeId, objectId, locale, callback)
127 {
128 var attachableDirectoryPath = path.join(datastoreTypeId, datastoreId, objectTypeId, objectId, locale);
129
130 callback(null, attachableDirectoryPath);
131 };
132
133 /**
134 * Reads an existing asset and cacheInfo (if exists).
135 *
136 * @param contentStore
137 * @param filePath
138 * @param callback
139 */
140 var readFromDisk = function(contentStore, filePath, callback)
141 {
142 // the cache file must exist on disk
143 var cacheFilePath = toCacheFilePath(filePath);
144
145 // read the cache file (if it exists)
146 contentStore.readFile(cacheFilePath, function(err, cacheInfoString) {
147
148 if (err)
149 {
150 // nothing found
151 callback({
152 "message": "Nothing cached on disk"
153 });
154
155 return;
156 }
157
158 if (!cacheInfoString)
159 {
160 // nothing found
161 callback({
162 "message": "Nothing cached on disk"
163 });
164
165 return;
166 }
167
168 var invalidate = function () {
169 safeRemove(contentStore, filePath, function (err) {
170 safeRemove(contentStore, cacheFilePath, function (err) {
171 callback();
172 });
173 });
174 };
175
176 // safety check: does the actual physical asset exists?
177 contentStore.existsFile(filePath, function(exists) {
178
179 if (!exists)
180 {
181 // clean up
182 return invalidate();
183 }
184
185 // check the file size on disk
186 // if size 0, invalidate
187 contentStore.fileStats(filePath, function(err, stats) {
188
189 if (err || !stats || stats.size === 0)
190 {
191 // clean up
192 return invalidate();
193 }
194
195 // there is something on disk
196 // we should serve it back (if we can)
197
198 var cacheInfo = JSON.parse(cacheInfoString);
199 if (isCacheInfoValid(cacheInfo))
200 {
201 // all good!
202
203 // clean up here in case charset is part of mimetype
204 if (cacheInfo.mimetype)
205 {
206 var x = cacheInfo.mimetype.indexOf(";");
207 if (x > -1)
208 {
209 cacheInfo.mimetype = cacheInfo.mimetype.substring(0, x);
210 }
211 }
212
213 callback(null, cacheInfo);
214 }
215 else
216 {
217 // bad cache file
218 invalidate();
219 }
220 });
221 });
222 });
223 };
224
225 var isCacheInfoValid = function(cacheInfo)
226 {
227 if (!cacheInfo)
228 {
229 return false;
230 }
231
232 // length must be represented
233 if (typeof(cacheInfo.length) === "undefined")
234 {
235 return false;
236 }
237
238 return true;
239 };
240
241 var buildCacheInfo = function(response)
242 {
243 var cacheInfo = null;
244
245 if (response.headers)
246 {
247 cacheInfo = {};
248
249 for (var k in response.headers)
250 {
251 var headerName = k.toLowerCase();
252
253 // content-length
254 if (headerName == "content-length")
255 {
256 cacheInfo.length = response.headers[k];
257 }
258
259 // content-type
260 if (headerName == "content-type")
261 {
262 cacheInfo.mimetype = response.headers[k];
263
264 // clean up here in case charset is part of mimetype
265 if (cacheInfo.mimetype) {
266 var x = cacheInfo.mimetype.indexOf(";");
267 if (x > -1) {
268 cacheInfo.mimetype = cacheInfo.mimetype.substring(0, x);
269 }
270 }
271
272 }
273
274 // filename
275 if (headerName == "content-disposition")
276 {
277 // "filename"
278 var contentDispositionHeader = response.headers[k];
279 if (contentDispositionHeader)
280 {
281 var x = contentDispositionHeader.indexOf("filename=");
282 if (x > -1)
283 {
284 cacheInfo.filename = contentDispositionHeader.substring(x + 9);
285 }
286 }
287 }
288 }
289 }
290
291 return cacheInfo;
292 };
293
294 var safeRemove = function(contentStore, filePath, callback)
295 {
296 contentStore.deleteFile(filePath, function(err) {
297 callback(err);
298 });
299 };
300
301 /**
302 * Ensures that the write stream is closed.
303 *
304 * @param writeStream
305 */
306 var closeWriteStream = function(writeStream)
307 {
308 try { writeStream.end(); } catch(e) { }
309 };
310
311 /**
312 * Downloads the asset from host:port/path and stores it on disk at filePath.
313 *
314 * @param contentStore
315 * @param gitana
316 * @param uri
317 * @param filePath
318 * @param callback
319 */
320 var writeToDisk = function(contentStore, gitana, uri, filePath, callback)
321 {
322 var _refreshAccessTokenAndRetry = function(contentStore, gitana, uri, filePath, attemptCount, maxAttemptsAllowed, previousError, cb)
323 {
324 // tell gitana driver to refresh access token
325 gitana.getDriver().refreshAuthentication(function(err) {
326
327 if (err)
328 {
329 return cb({
330 "message": "Failed to refresh authentication token: " + JSON.stringify(err),
331 "err": previousError,
332 "invalidateGitanaDriver": true
333 });
334 }
335 else
336 {
337 // try again with attempt count + 1
338 setTimeout(function() {
339 _writeToDisk(contentStore, gitana, uri, filePath, attemptCount + 1, maxAttemptsAllowed, previousError, cb)
340 }, 250);
341 }
342 });
343 };
344
345 var _writeToDisk = function(contentStore, gitana, uri, filePath, attemptCount, maxAttemptsAllowed, previousError, cb)
346 {
347 if (attemptCount === maxAttemptsAllowed)
348 {
349 cb({
350 "message": "Maximum number of connection attempts exceeded(" + maxAttemptsAllowed + ")",
351 "err": previousError
352 });
353
354 return;
355 }
356
357 contentStore.writeStream(filePath, function(err, tempStream) {
358
359 if (err)
360 {
361 // ensure stream is closed
362 closeWriteStream(tempStream);
363
364 // ensure cleanup
365 safeRemove(contentStore, filePath, function () {
366 cb(err);
367 });
368 return;
369 }
370
371 var cacheFilePath = toCacheFilePath(filePath);
372
373 // headers
374 var headers = {};
375
376 // add "authorization" for OAuth2 bearer token
377 var headers2 = gitana.platform().getDriver().getHttpHeaders();
378 headers["Authorization"] = headers2["Authorization"];
379
380 var agent = http.globalAgent;
381 if (process.env.GITANA_PROXY_SCHEME === "https")
382 {
383 agent = https.globalAgent;
384 }
385
386 var URL = util.asURL(process.env.GITANA_PROXY_SCHEME, process.env.GITANA_PROXY_HOST, process.env.GITANA_PROXY_PORT) + uri;
387 request({
388 "method": "GET",
389 "url": URL,
390 "qs": {},
391 "headers": headers,
392 "timeout": process.defaultHttpTimeoutMs,
393 "agent": agent
394 }).on('response', function (response) {
395
396 //console.log("Status Code: " + response.statusCode);
397
398 if (response.statusCode >= 200 && response.statusCode <= 204)
399 {
400 response.pipe(tempStream).on("close", function (err) {
401
402 // TODO: not needed here?
403 // ensure stream is closed
404 // closeWriteStream(tempStream);
405
406 if (err)
407 {
408 // some went wrong at disk io level?
409 return safeRemove(contentStore, filePath, function () {
410 cb({
411 "message": "Failed to download: " + JSON.stringify(err)
412 });
413 });
414 }
415
416 contentStore.existsFile(filePath, function (exists) {
417
418 if (exists) {
419
420 // write cache file
421 var cacheInfo = buildCacheInfo(response);
422 if (cacheInfo)
423 {
424 contentStore.writeFile(cacheFilePath, JSON.stringify(cacheInfo, null, " "), function (err) {
425
426 if (err)
427 {
428 // failed to write cache file, thus the whole thing is invalid
429 safeRemove(contentStore, cacheFilePath, function () {
430 safeRemove(contentStore, filePath, function () {
431 cb({
432 "message": "Failed to write cache file: " + cacheFilePath
433 });
434 });
435 });
436 }
437 else
438 {
439 cb(null, filePath, cacheInfo);
440 }
441
442 });
443 }
444 else
445 {
446 cb(null, filePath, cacheInfo);
447 }
448 }
449 else
450 {
451 console.log("WARN: exists false, " + filePath);
452
453 // for some reason, file wasn't found
454 // roll back the whole thing
455 safeRemove(contentStore, cacheFilePath, function () {
456 safeRemove(contentStore, filePath, function () {
457 cb({
458 "message": "Failed to verify written cached file: " + filePath
459 });
460 });
461 });
462 }
463 });
464
465 }).on("error", function (err) {
466
467 // ensure stream is closed
468 closeWriteStream(tempStream);
469
470 console.log("Pipe error: " + err);
471 });
472 }
473 else
474 {
475 // some kind of http error (usually permission denied or invalid_token)
476
477 var body = "";
478
479 response.on('data', function (chunk) {
480 body += chunk;
481 });
482
483 response.on('end', function () {
484
485 // ensure stream is closed
486 closeWriteStream(tempStream);
487
488 var afterCleanup = function () {
489
490 // see if it is "invalid_token"
491 // if so, we can automatically retry
492 var isInvalidToken = false;
493 try
494 {
495 var json = JSON.parse(body);
496 if (json && json.error === "invalid_token")
497 {
498 isInvalidToken = true;
499 }
500 }
501 catch (e)
502 {
503 // swallow
504 }
505
506 if (isInvalidToken)
507 {
508 // fire for retry
509 return _refreshAccessTokenAndRetry(contentStore, gitana, uri, filePath, attemptCount, maxAttemptsAllowed, {
510 "message": "Unable to load asset from remote store",
511 "code": response.statusCode,
512 "body": body
513 }, cb);
514 }
515
516 // otherwise, it's not worth retrying at this time
517 cb({
518 "message": "Unable to load asset from remote store",
519 "code": response.statusCode,
520 "body": body
521 });
522
523 };
524
525 // clean things up
526 safeRemove(contentStore, cacheFilePath, function () {
527 safeRemove(contentStore, filePath, function () {
528 afterCleanup();
529 });
530 });
531 });
532
533 }
534
535 }).on('error', function (e) {
536
537 // ensure stream is closed
538 closeWriteStream(tempStream);
539
540 console.log("_writeToDisk request timed out");
541 console.log(e)
542 }).on('end', function (e) {
543
544 // ensure stream is closed
545 closeWriteStream(tempStream);
546
547 }).end();
548
549 tempStream.on("error", function (e) {
550 console.log("Temp stream errored out");
551 console.log(e);
552
553 // ensure stream is closed
554 closeWriteStream(tempStream);
555
556 });
557 });
558
559 };
560
561 _writeToDisk(contentStore, gitana, uri, filePath, 0, 2, null, function(err, filePath, cacheInfo) {
562 callback(err, filePath, cacheInfo);
563 });
564
565 };
566
567 /**
568 * Downloads node metadata or an attachment and saves it to disk.
569 *
570 * @param contentStore
571 * @param gitana driver instance
572 * @param repositoryId
573 * @param branchId
574 * @param nodeId
575 * @param attachmentId
576 * @param nodePath
577 * @param locale
578 * @param forceReload
579 * @param callback
580 */
581 var downloadNode = function(contentStore, gitana, repositoryId, branchId, nodeId, attachmentId, nodePath, locale, forceReload, callback)
582 {
583 // ensure path starts with "/"
584 if (nodePath && nodePath.substring(0, 1) !== "/") {
585 nodePath = "/" + nodePath;
586 }
587
588 // base storage directory
589 generateContentDirectoryPath(contentStore, repositoryId, branchId, nodeId, locale, function(err, contentDirectoryPath) {
590
591 if (err) {
592 return callback(err);
593 }
594
595 var filePath = contentDirectoryPath;
596 if (nodePath) {
597 filePath = path.join(contentDirectoryPath, "paths", nodePath);
598 }
599
600 if (attachmentId) {
601 filePath = path.join(filePath, "attachments", attachmentId);
602 } else {
603 filePath = path.join(contentDirectoryPath, "metadata.json");
604 }
605
606 var doWork = function() {
607
608 // if the cached asset is on disk, we serve it back
609 readFromDisk(contentStore, filePath, function (err, cacheInfo) {
610
611 if (!err && cacheInfo) {
612 callback(err, filePath, cacheInfo);
613 return;
614 }
615
616 // either there was an error (in which case things were cleaned up)
617 // or there was nothing on disk
618
619 // load asset from server, begin constructing the URI
620 var uri = "/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/" + nodeId;
621 if (attachmentId) {
622 uri += "/attachments/" + attachmentId;
623 }
624 // force content disposition information to come back
625 uri += "?a=true";
626 if (nodePath) {
627 uri += "&path=" + nodePath;
628 }
629
630 // grab from Cloud CMS and write to disk
631 writeToDisk(contentStore, gitana, uri, filePath, function (err, filePath, cacheInfo) {
632
633 if (err) {
634 console.log("ERR: " + err.message + ", BODY: " + err.body);
635 callback(err);
636 }
637 else {
638 //console.log("Fetched: " + assetPath);
639 //console.log("Retrieved from server: " + filePath);
640 callback(null, filePath, cacheInfo);
641 }
642 });
643 });
644 };
645
646 // if force reload, delete from disk if exist
647 if (forceReload)
648 {
649 contentStore.existsFile(filePath, function (exists) {
650
651 if (exists)
652 {
653 contentStore.removeFile(filePath, function (err) {
654 contentStore.removeFile(toCacheFilePath(filePath), function (err) {
655 doWork();
656 });
657 });
658 }
659 else
660 {
661 doWork();
662 }
663 });
664 }
665 else
666 {
667 doWork();
668 }
669 });
670 };
671
672 /**
673 * Downloads a preview image for a node.
674 *
675 * @param contentStore
676 * @param gitana driver instance
677 * @param repositoryId
678 * @param branchId
679 * @param nodeId
680 * @param nodePath
681 * @param attachmentId
682 * @param locale
683 * @param previewId
684 * @param size
685 * @param mimetype
686 * @param forceReload
687 * @param callback
688 */
689 var previewNode = function(contentStore, gitana, repositoryId, branchId, nodeId, nodePath, attachmentId, locale, previewId, size, mimetype, forceReload, callback)
690 {
691 if (!previewId)
692 {
693 previewId = attachmentId;
694 }
695
696 // ensure path starts with "/"
697 if (nodePath && nodePath.substring(0, 1) !== "/") {
698 nodePath = "/" + nodePath;
699 }
700
701 // base storage directory
702 generateContentDirectoryPath(contentStore, repositoryId, branchId, nodeId, locale, function(err, contentDirectoryPath) {
703
704 if (err) {
705 return callback(err);
706 }
707
708 var filePath = contentDirectoryPath;
709 if (nodePath) {
710 filePath = path.join(contentDirectoryPath, "paths", nodePath);
711 }
712
713 filePath = path.join(filePath, "previews", previewId);
714
715 var doWork = function() {
716
717 // if the cached asset is on disk, we serve it back
718 readFromDisk(contentStore, filePath, function (err, cacheInfo) {
719
720 if (!err && cacheInfo) {
721
722 // if no mimetype or mimetype matches, then hand back
723 if (!mimetype || (cacheInfo.mimetype === mimetype)) {
724 return callback(null, filePath, cacheInfo);
725 }
726 }
727
728 // either there was an error (in which case things were cleaned up)
729 // or there was nothing on disk
730
731 var uri = "/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/" + nodeId + "/preview/" + previewId;
732 // force content disposition information to come back
733 uri += "?a=true";
734 if (forceReload) {
735 uri += "&force=" + forceReload;
736 }
737 if (nodePath) {
738 uri += "&path=" + nodePath;
739 }
740 if (attachmentId) {
741 uri += "&attachment=" + attachmentId;
742 }
743 if (size > -1) {
744 uri += "&size=" + size;
745 }
746 if (mimetype) {
747 uri += "&mimetype=" + mimetype;
748 }
749
750 writeToDisk(contentStore, gitana, uri, filePath, function (err, filePath, responseHeaders) {
751
752 if (err) {
753 if (err.code === 404) {
754 return callback();
755 }
756
757 console.log("ERR: " + err.message + " for URI: " + uri);
758 return callback(err);
759 }
760
761 //console.log("Fetched: " + assetPath);
762 //console.log("Retrieved from server: " + filePath);
763 callback(null, filePath, responseHeaders);
764 });
765 });
766 };
767
768 /////////////////////////////////
769
770 // if force reload, delete from disk if exist
771 if (forceReload)
772 {
773 contentStore.existsFile(filePath, function (exists) {
774
775 if (exists)
776 {
777 contentStore.removeFile(filePath, function (err) {
778 contentStore.removeFile(toCacheFilePath(filePath), function (err) {
779 doWork();
780 });
781 });
782 }
783 else
784 {
785 doWork();
786 }
787 });
788 }
789 else
790 {
791 doWork();
792 }
793 });
794 };
795
796 var invalidateNode = function(contentStore, repositoryId, branchId, nodeId, callback)
797 {
798 // base storage directory
799 var contentDirectoryPath = path.join(repositoryId, branchId, nodeId);
800
801 //console.log("Considering: " + contentDirectoryPath);
802 contentStore.existsDirectory(contentDirectoryPath, function(exists) {
803
804 //console.log("Exists -> " + exists);
805
806 if (!exists)
807 {
808 return callback();
809 }
810
811 contentStore.removeDirectory(contentDirectoryPath, function(err) {
812
813 console.log(" > Invalidated Node [repository: " + repositoryId + ", branch: " + branchId + ", node: " + nodeId + "]");
814
815 callback(err, true);
816 });
817
818 });
819 };
820
821 var invalidateNodePaths = function(contentStore, repositoryId, branchId, paths, callback)
822 {
823 if (!paths)
824 {
825 return callback();
826 }
827
828 var rootPath = paths["root"];
829 if (!rootPath)
830 {
831 return callback();
832 }
833
834 // base storage directory
835 // TODO: support non-root and non-default locale?
836 var rootCachePath = path.join(repositoryId, branchId, "root", "default", "paths", rootPath);
837
838 contentStore.existsDirectory(rootCachePath, function(exists) {
839
840 if (!exists)
841 {
842 return callback();
843 }
844
845 contentStore.removeDirectory(rootCachePath, function(err) {
846
847 console.log(" > Invalidated Path [repository: " + repositoryId + ", branch: " + branchId + ", path: " + rootPath + "]");
848
849 callback(err, true);
850 });
851
852 });
853 };
854
855 /**
856 * Downloads attachable metadata or an attachment and saves it to disk.
857 *
858 * @param contentStore
859 * @param gitana driver instance
860 * @param datastoreTypeId
861 * @param datastoreId
862 * @param objectTypeId
863 * @param objectId
864 * @param attachmentId
865 * @param locale
866 * @param forceReload
867 * @param callback
868 */
869 var downloadAttachable = function(contentStore, gitana, datastoreTypeId, datastoreId, objectTypeId, objectId, attachmentId, locale, forceReload, callback)
870 {
871 // base storage directory
872 generateAttachableDirectoryPath(contentStore, datastoreTypeId, datastoreId, objectTypeId, objectId, locale, function(err, dataDirectoryPath) {
873
874 if (err) {
875 callback(err);
876 return;
877 }
878
879 var filePath = dataDirectoryPath;
880 if (attachmentId) {
881 filePath = path.join(filePath, "attachments", attachmentId);
882 } else {
883 filePath = path.join(filePath, "metadata.json");
884 }
885
886 var doWork = function() {
887
888 // if the cached asset is on disk, we serve it back
889 readFromDisk(contentStore, filePath, function (err, cacheInfo) {
890
891 if (!err && cacheInfo) {
892 callback(null, filePath, cacheInfo);
893 return;
894 }
895
896 // either there was an error (in which case things were cleaned up)
897 // or there was nothing on disk
898
899 // begin constructing a URI
900 var uri = generateURL(datastoreTypeId, datastoreId, objectTypeId, objectId);
901 if (attachmentId) {
902 uri += "/attachments/" + attachmentId;
903 }
904 // force content disposition information to come back
905 uri += "?a=true";
906
907 // grab from Cloud CMS and write to disk
908 writeToDisk(contentStore, gitana, uri, filePath, function (err, filePath, cacheInfo) {
909
910 if (err) {
911 callback(err);
912 }
913 else {
914 callback(null, filePath, cacheInfo);
915 }
916 });
917 });
918 };
919
920 // if force reload, delete from disk if exist
921 if (forceReload)
922 {
923 contentStore.existsFile(filePath, function(exists) {
924
925 if (exists)
926 {
927 contentStore.removeFile(filePath, function(err) {
928 doWork();
929 });
930 }
931 else
932 {
933 doWork();
934 }
935 })
936 }
937 else
938 {
939 doWork();
940 }
941
942 });
943 };
944
945 /**
946 * Downloads a preview image for an attachable.
947 *
948 * @param contentStore
949 * @param gitana driver instance
950 * @param datastoreTypeId
951 * @param datastoreId
952 * @param objectTypeId
953 * @param objectId
954 * @param attachmentId
955 * @param locale
956 * @param previewId
957 * @param size
958 * @param mimetype
959 * @param forceReload
960 * @param callback
961 */
962 var previewAttachable = function(contentStore, gitana, datastoreTypeId, datastoreId, objectTypeId, objectId, attachmentId, locale, previewId, size, mimetype, forceReload, callback)
963 {
964 // base storage directory
965 generateAttachableDirectoryPath(contentStore, datastoreTypeId, datastoreId, objectTypeId, objectId, locale, function(err, dataDirectoryPath) {
966
967 if (err) {
968 callback(err);
969 return;
970 }
971
972 if (!previewId)
973 {
974 previewId = attachmentId;
975 //previewId = "_preview";
976 //forceReload = true;
977 }
978
979 var filePath = path.join(dataDirectoryPath, "previews", previewId);
980
981 var doWork = function() {
982
983 // if the cached asset is on disk, we serve it back
984 readFromDisk(contentStore, filePath, function (err, cacheInfo) {
985
986 if (!err && cacheInfo) {
987 callback(null, filePath, cacheInfo);
988 return;
989 }
990
991 // either there was an error (in which case things were cleaned up)
992 // or there was nothing on disk
993
994 // begin constructing a URI
995 var uri = generateURL(datastoreTypeId, datastoreId, objectTypeId, objectId, previewId);
996 uri += "?a=true";
997 if (forceReload) {
998 uri += "&force=" + forceReload;
999 }
1000 if (attachmentId) {
1001 uri += "&attachment=" + attachmentId;
1002 }
1003 if (size > -1) {
1004 uri += "&size=" + size;
1005 }
1006 if (mimetype) {
1007 uri += "&mimetype=" + mimetype;
1008 }
1009
1010 writeToDisk(contentStore, gitana, uri, filePath, function (err, filePath, responseHeaders) {
1011
1012 if (err) {
1013 callback(err);
1014 }
1015 else {
1016 callback(null, filePath, responseHeaders);
1017 }
1018 });
1019 });
1020 };
1021
1022 // if force reload, delete from disk if exist
1023 if (forceReload)
1024 {
1025 contentStore.existsFile(filePath, function(exists) {
1026
1027 if (exists)
1028 {
1029 contentStore.removeFile(filePath, function(err) {
1030 doWork();
1031 });
1032 }
1033 else
1034 {
1035 doWork();
1036 }
1037
1038 });
1039 }
1040 else
1041 {
1042 doWork();
1043 }
1044
1045 });
1046 };
1047
1048 // lock helpers
1049
1050 var _lock_identifier = function()
1051 {
1052 var args = Array.prototype.slice.call(arguments);
1053
1054 return args.join("_");
1055 };
1056
1057 var _LOCK = function(store, lockIdentifier, workFunction)
1058 {
1059 process.locks.lock(store.id + "_" + lockIdentifier, workFunction);
1060 };
1061
1062
1063 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
1064 //
1065 // RESULTING OBJECT
1066 //
1067 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
1068
1069 var r = {};
1070
1071 r.toCacheFilePath = toCacheFilePath;
1072 r.buildCacheInfo = buildCacheInfo;
1073 r.safeRemove = safeRemove;
1074
1075 r.download = function(contentStore, gitana, repositoryId, branchId, nodeId, attachmentId, nodePath, locale, forceReload, callback)
1076 {
1077 // claim a lock around this node for this server
1078 _LOCK(contentStore, _lock_identifier(repositoryId, branchId, nodeId), function(releaseLockFn) {
1079
1080 // workhorse - pass releaseLockFn back to callback
1081 downloadNode(contentStore, gitana, repositoryId, branchId, nodeId, attachmentId, nodePath, locale, forceReload, function (err, filePath, cacheInfo) {
1082 callback(err, filePath, cacheInfo, releaseLockFn);
1083 });
1084
1085 });
1086 };
1087
1088 r.preview = function(contentStore, gitana, repositoryId, branchId, nodeId, nodePath, attachmentId, locale, previewId, size, mimetype, forceReload, callback)
1089 {
1090 // claim a lock around this node for this server
1091 _LOCK(contentStore, _lock_identifier(repositoryId, branchId, nodeId), function(releaseLockFn) {
1092
1093 // workhorse - pass releaseLockFn back to callback
1094 previewNode(contentStore, gitana, repositoryId, branchId, nodeId, nodePath, attachmentId, locale, previewId, size, mimetype, forceReload, function(err, filePath, cacheInfo) {
1095 callback(err, filePath, cacheInfo, releaseLockFn);
1096 });
1097
1098 });
1099 };
1100
1101 r.invalidate = function(contentStore, repositoryId, branchId, nodeId, paths, callback)
1102 {
1103 // claim a lock around this node for this server
1104 _LOCK(contentStore, _lock_identifier(repositoryId, branchId, nodeId), function(releaseLockFn) {
1105
1106 invalidateNode(contentStore, repositoryId, branchId, nodeId, function () {
1107
1108 invalidateNodePaths(contentStore, repositoryId, branchId, paths, function() {
1109
1110 // release lock
1111 releaseLockFn();
1112
1113 // all done
1114 callback();
1115
1116 });
1117 });
1118 });
1119 };
1120
1121 r.downloadAttachable = function(contentStore, gitana, datastoreTypeId, datastoreId, objectTypeId, objectId, attachmentId, locale, forceReload, callback)
1122 {
1123 // claim a lock around this node for this server
1124 _LOCK(contentStore, _lock_identifier(datastoreId, objectId), function(releaseLockFn) {
1125
1126 // workhorse - pass releaseLockFn back to callback
1127 downloadAttachable(contentStore, gitana, datastoreTypeId, datastoreId, objectTypeId, objectId, attachmentId, locale, forceReload, function(err, filePath, cacheInfo) {
1128 callback(err, filePath, cacheInfo, releaseLockFn);
1129 });
1130
1131 });
1132 };
1133
1134 r.previewAttachable = function(contentStore, gitana, datastoreTypeId, datastoreId, objectTypeId, objectId, attachmentId, locale, previewId, size, mimetype, forceReload, callback)
1135 {
1136 // claim a lock around this node for this server
1137 _LOCK(contentStore, _lock_identifier(datastoreId, objectId), function(releaseLockFn) {
1138
1139 // workhorse - pass releaseLockFn back to callback
1140 previewAttachable(contentStore, gitana, datastoreTypeId, datastoreId, objectTypeId, objectId, attachmentId, locale, previewId, size, mimetype, forceReload, function (err, filePath, cacheInfo) {
1141 callback(err, filePath, cacheInfo, releaseLockFn);
1142 });
1143
1144 });
1145 };
1146
1147 r.invalidateAttachable = function(contentStore, datastoreTypeId, datastoreId, objectTypeId, objectId, callback)
1148 {
1149 // claim a lock around this node for this server
1150 _LOCK(contentStore, _lock_identifier(datastoreId, objectId), function(releaseLockFn) {
1151
1152 // TODO: not implemented
1153 callback();
1154
1155 releaseLockFn();
1156 });
1157 };
1158
1159 return r;
1160}();