MegaStorage.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. const _ = require('lodash');
  2. const fs = require('fs-extra');
  3. const path = require('path');
  4. const log = new (require('../AppLogger'))().log;//singleton
  5. const ZipStreamer = require('../Zip/ZipStreamer');
  6. const utils = require('../utils');
  7. const zeroStats = {
  8. zipFilesCount: 0,
  9. descFilesCount: 0,
  10. zipFilesSize: 0,
  11. descFilesSize: 0,
  12. };
  13. let instance = null;
  14. //singleton
  15. class MegaStorage {
  16. constructor() {
  17. if (!instance) {
  18. this.inited = false;
  19. this.debouncedSaveStats = _.debounce(() => {
  20. this.saveStats().catch((e) => {
  21. log(LM_ERR, `MegaStorage::saveStats ${e.message}`);
  22. //process.exit(1);
  23. });
  24. }, 5000, {'maxWait':6000});
  25. process.on('exit', () => {
  26. this.saveStatsSync();
  27. });
  28. instance = this;
  29. }
  30. return instance;
  31. }
  32. async init(config) {
  33. this.config = config;
  34. this.megaStorageDir = config.megaStorageDir;
  35. this.statsPath = `${this.megaStorageDir}/stats.json`;
  36. this.compressLevel = (config.compressLevel ? config.compressLevel : 4);
  37. await fs.ensureDir(this.megaStorageDir);
  38. this.readingFiles = false;
  39. this.stats = _.cloneDeep(zeroStats);
  40. if (await fs.pathExists(this.statsPath)) {
  41. this.stats = Object.assign({},
  42. this.stats,
  43. JSON.parse(await fs.readFile(this.statsPath, 'utf8'))
  44. );
  45. }
  46. this.inited = true;
  47. }
  48. async nameHash(filename) {
  49. if (!this.inited)
  50. throw new Error('not inited');
  51. const hash = utils.toBase36(await utils.getFileHash(filename, 'sha1'));
  52. const hashPath = `${hash.substr(0, 2)}/${hash.substr(2, 2)}/${hash}`;
  53. const fullHashPath = `${this.megaStorageDir}/${hashPath}`;
  54. return {
  55. filename,
  56. hash,
  57. hashPath,
  58. fullHashPath,
  59. zipPath: `${fullHashPath}.zip`,
  60. descPath: `${fullHashPath}.desc`,
  61. };
  62. }
  63. async checkFileExists(nameHash) {
  64. return await fs.pathExists(nameHash.zipPath);
  65. }
  66. async addFile(nameHash, desc = null, force = false) {
  67. if (!this.inited)
  68. throw new Error('not inited');
  69. if (await this.checkFileExists(nameHash) && !force)
  70. return false;
  71. await fs.ensureDir(path.dirname(nameHash.zipPath));
  72. let oldZipSize = 0;
  73. let newZipCount = 1;
  74. if (await fs.pathExists(nameHash.zipPath)) {
  75. oldZipSize = (await fs.stat(nameHash.zipPath)).size;
  76. newZipCount = 0;
  77. }
  78. const zip = new ZipStreamer();
  79. let entry = {};
  80. let resultFile = await zip.pack(nameHash.zipPath, [nameHash.filename], {zlib: {level: this.compressLevel}}, (ent) => {
  81. entry = ent;
  82. });
  83. if (desc) {
  84. desc = Object.assign({}, desc, {fileSize: entry.size, zipFileSize: resultFile.size});
  85. await this.updateDesc(nameHash, desc);
  86. }
  87. this.stats.zipFilesSize += -oldZipSize + resultFile.size;
  88. this.stats.zipFilesCount += newZipCount;
  89. this.needSaveStats = true;
  90. this.debouncedSaveStats();
  91. return desc;
  92. }
  93. async updateDesc(nameHash, desc) {
  94. let oldDescSize = 0;
  95. let newDescCount = 1;
  96. if (await fs.pathExists(nameHash.descPath)) {
  97. oldDescSize = (await fs.stat(nameHash.descPath)).size;
  98. newDescCount = 0;
  99. }
  100. const data = JSON.stringify(desc, null, 2);
  101. await fs.writeFile(nameHash.descPath, data);
  102. this.stats.descFilesSize += -oldDescSize + data.length;
  103. this.stats.descFilesCount += newDescCount;
  104. this.needSaveStats = true;
  105. this.debouncedSaveStats();
  106. }
  107. async _findFiles(callback, dir) {
  108. if (!callback || !this.readingFiles)
  109. return;
  110. let result = true;
  111. const files = await fs.readdir(dir, { withFileTypes: true });
  112. for (const file of files) {
  113. if (!this.readingFiles)
  114. return;
  115. const found = path.resolve(dir, file.name);
  116. if (file.isDirectory())
  117. result = await this._findFiles(callback, found);
  118. else
  119. await callback(found);
  120. }
  121. return result;
  122. }
  123. async startFindFiles(callback) {
  124. if (!this.inited)
  125. throw new Error('not inited');
  126. this.readingFiles = true;
  127. try {
  128. return await this._findFiles(callback, this.megaStorageDir);
  129. } finally {
  130. this.readingFiles = false;
  131. }
  132. }
  133. async stopFindFiles() {
  134. this.readingFiles = false;
  135. }
  136. async saveStats() {
  137. if (this.needSaveStats) {
  138. await fs.writeFile(this.statsPath, JSON.stringify(this.stats, null, 2));
  139. this.needSaveStats = false;
  140. }
  141. }
  142. saveStatsSync() {
  143. if (this.needSaveStats) {
  144. fs.writeFileSync(this.statsPath, JSON.stringify(this.stats, null, 2));
  145. this.needSaveStats = false;
  146. }
  147. }
  148. async getStats(gather = false) {
  149. if (!this.inited)
  150. throw new Error('MegaStorage::not inited');
  151. if (!gather || this.readingFiles)
  152. return this.stats;
  153. let stats = _.cloneDeep(zeroStats);
  154. const result = await this.startFindFiles(async(entry) => {
  155. if (path.extname(entry) == '.zip') {
  156. stats.zipFilesSize += (await fs.stat(entry)).size;
  157. stats.zipFilesCount++;
  158. }
  159. if (path.extname(entry) == '.desc') {
  160. stats.descFilesSize += (await fs.stat(entry)).size;
  161. stats.descFilesCount++;
  162. }
  163. });
  164. if (result) {
  165. this.stats = stats;
  166. this.needSaveStats = true;
  167. this.debouncedSaveStats();
  168. }
  169. return this.stats;
  170. }
  171. }
  172. module.exports = MegaStorage;