NodeFileSystemWritableFileStream.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.NodeFileSystemWritableFileStream = exports.createSwapFile = void 0;
  4. const buffer_1 = require("../internal/buffer");
  5. /**
  6. * When Chrome writes to the file, it creates a copy of the file with extension
  7. * `.crswap` and then replaces the original file with the copy only when the
  8. * `close()` method is called. If the `abort()` method is called, the `.crswap`
  9. * file is deleted.
  10. *
  11. * If a file name with with extension `.crswap` is already taken, it
  12. * creates a new swap file with extension `.1.crswap` and so on.
  13. */
  14. const createSwapFile = async (fs, path, keepExistingData) => {
  15. let handle;
  16. let swapPath = path + '.crswap';
  17. try {
  18. handle = await fs.promises.open(swapPath, 'ax');
  19. }
  20. catch (error) {
  21. if (!error || typeof error !== 'object' || error.code !== 'EEXIST')
  22. throw error;
  23. }
  24. if (!handle) {
  25. for (let i = 1; i < 1000; i++) {
  26. try {
  27. swapPath = `${path}.${i}.crswap`;
  28. handle = await fs.promises.open(swapPath, 'ax');
  29. break;
  30. }
  31. catch (error) {
  32. if (!error || typeof error !== 'object' || error.code !== 'EEXIST')
  33. throw error;
  34. }
  35. }
  36. }
  37. if (!handle)
  38. throw new Error(`Could not create a swap file for "${path}".`);
  39. if (keepExistingData)
  40. await fs.promises.copyFile(path, swapPath, fs.constants.COPYFILE_FICLONE);
  41. return [handle, swapPath];
  42. };
  43. exports.createSwapFile = createSwapFile;
  44. const WS = (typeof WritableStream === 'undefined' ? require('stream/web').WritableStream : WritableStream);
  45. /**
  46. * Is a WritableStream object with additional convenience methods, which
  47. * operates on a single file on disk. The interface is accessed through the
  48. * `FileSystemFileHandle.createWritable()` method.
  49. *
  50. * @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream
  51. */
  52. class NodeFileSystemWritableFileStream extends WS {
  53. constructor(fs, path, keepExistingData) {
  54. const swap = { handle: undefined, path: '', offset: 0 };
  55. super({
  56. async start() {
  57. const promise = (0, exports.createSwapFile)(fs, path, keepExistingData);
  58. swap.ready = promise.then(() => undefined);
  59. const [handle, swapPath] = await promise;
  60. swap.handle = handle;
  61. swap.path = swapPath;
  62. },
  63. async write(chunk) {
  64. await swap.ready;
  65. const handle = swap.handle;
  66. if (!handle)
  67. throw new Error('Invalid state');
  68. const buffer = buffer_1.Buffer.from(typeof chunk === 'string' ? chunk : chunk instanceof Blob ? await chunk.arrayBuffer() : chunk);
  69. const { bytesWritten } = await handle.write(buffer, 0, buffer.length, swap.offset);
  70. swap.offset += bytesWritten;
  71. },
  72. async close() {
  73. await swap.ready;
  74. const handle = swap.handle;
  75. if (!handle)
  76. return;
  77. await handle.close();
  78. await fs.promises.rename(swap.path, path);
  79. },
  80. async abort() {
  81. await swap.ready;
  82. const handle = swap.handle;
  83. if (!handle)
  84. return;
  85. await handle.close();
  86. await fs.promises.unlink(swap.path);
  87. },
  88. });
  89. this.fs = fs;
  90. this.path = path;
  91. this.swap = swap;
  92. }
  93. /**
  94. * @sse https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/seek
  95. * @param position An `unsigned long` describing the byte position from the top
  96. * (beginning) of the file.
  97. */
  98. async seek(position) {
  99. this.swap.offset = position;
  100. }
  101. /**
  102. * @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/truncate
  103. * @param size An `unsigned long` of the amount of bytes to resize the stream to.
  104. */
  105. async truncate(size) {
  106. await this.swap.ready;
  107. const handle = this.swap.handle;
  108. if (!handle)
  109. throw new Error('Invalid state');
  110. await handle.truncate(size);
  111. if (this.swap.offset > size)
  112. this.swap.offset = size;
  113. }
  114. async writeBase(chunk) {
  115. const writer = this.getWriter();
  116. try {
  117. await writer.write(chunk);
  118. }
  119. finally {
  120. writer.releaseLock();
  121. }
  122. }
  123. async write(params) {
  124. if (!params)
  125. throw new TypeError('Missing required argument: params');
  126. switch (typeof params) {
  127. case 'string': {
  128. return this.writeBase(params);
  129. }
  130. case 'object': {
  131. const constructor = params.constructor;
  132. switch (constructor) {
  133. case ArrayBuffer:
  134. case Blob:
  135. case DataView:
  136. return this.writeBase(params);
  137. default: {
  138. if (ArrayBuffer.isView(params)) {
  139. return this.writeBase(params);
  140. }
  141. else {
  142. const options = params;
  143. switch (options.type) {
  144. case 'write': {
  145. if (typeof options.position === 'number')
  146. await this.seek(options.position);
  147. return this.writeBase(params.data);
  148. }
  149. case 'truncate': {
  150. if (typeof params.size !== 'number')
  151. throw new TypeError('Missing required argument: size');
  152. if (this.swap.offset > params.size)
  153. this.swap.offset = params.size;
  154. return this.truncate(params.size);
  155. }
  156. case 'seek':
  157. if (typeof params.position !== 'number')
  158. throw new TypeError('Missing required argument: position');
  159. return this.seek(params.position);
  160. default:
  161. throw new TypeError('Invalid argument: params');
  162. }
  163. }
  164. }
  165. }
  166. }
  167. default:
  168. throw new TypeError('Invalid argument: params');
  169. }
  170. }
  171. }
  172. exports.NodeFileSystemWritableFileStream = NodeFileSystemWritableFileStream;
  173. //# sourceMappingURL=NodeFileSystemWritableFileStream.js.map