MegaStorage.js 5.9 KB

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