NodeFileSystemWritableFileStream.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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'
  69. ? chunk
  70. : chunk instanceof Blob
  71. ? new Uint8Array(await chunk.arrayBuffer())
  72. : chunk);
  73. const { bytesWritten } = await handle.write(buffer, 0, buffer.length, swap.offset);
  74. swap.offset += bytesWritten;
  75. },
  76. async close() {
  77. await swap.ready;
  78. const handle = swap.handle;
  79. if (!handle)
  80. return;
  81. await handle.close();
  82. await fs.promises.rename(swap.path, path);
  83. },
  84. async abort() {
  85. await swap.ready;
  86. const handle = swap.handle;
  87. if (!handle)
  88. return;
  89. await handle.close();
  90. await fs.promises.unlink(swap.path);
  91. },
  92. });
  93. this.fs = fs;
  94. this.path = path;
  95. this.swap = swap;
  96. }
  97. /**
  98. * @sse https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/seek
  99. * @param position An `unsigned long` describing the byte position from the top
  100. * (beginning) of the file.
  101. */
  102. async seek(position) {
  103. this.swap.offset = position;
  104. }
  105. /**
  106. * @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/truncate
  107. * @param size An `unsigned long` of the amount of bytes to resize the stream to.
  108. */
  109. async truncate(size) {
  110. await this.swap.ready;
  111. const handle = this.swap.handle;
  112. if (!handle)
  113. throw new Error('Invalid state');
  114. await handle.truncate(size);
  115. if (this.swap.offset > size)
  116. this.swap.offset = size;
  117. }
  118. async writeBase(chunk) {
  119. const writer = this.getWriter();
  120. try {
  121. await writer.write(chunk);
  122. }
  123. finally {
  124. writer.releaseLock();
  125. }
  126. }
  127. async write(params) {
  128. if (!params)
  129. throw new TypeError('Missing required argument: params');
  130. switch (typeof params) {
  131. case 'string': {
  132. return this.writeBase(params);
  133. }
  134. case 'object': {
  135. const constructor = params.constructor;
  136. switch (constructor) {
  137. case ArrayBuffer:
  138. case Blob:
  139. case DataView:
  140. return this.writeBase(params);
  141. default: {
  142. if (ArrayBuffer.isView(params)) {
  143. return this.writeBase(params);
  144. }
  145. else {
  146. const options = params;
  147. switch (options.type) {
  148. case 'write': {
  149. if (typeof options.position === 'number')
  150. await this.seek(options.position);
  151. return this.writeBase(params.data);
  152. }
  153. case 'truncate': {
  154. if (typeof params.size !== 'number')
  155. throw new TypeError('Missing required argument: size');
  156. if (this.swap.offset > params.size)
  157. this.swap.offset = params.size;
  158. return this.truncate(params.size);
  159. }
  160. case 'seek':
  161. if (typeof params.position !== 'number')
  162. throw new TypeError('Missing required argument: position');
  163. return this.seek(params.position);
  164. default:
  165. throw new TypeError('Invalid argument: params');
  166. }
  167. }
  168. }
  169. }
  170. }
  171. default:
  172. throw new TypeError('Invalid argument: params');
  173. }
  174. }
  175. }
  176. exports.NodeFileSystemWritableFileStream = NodeFileSystemWritableFileStream;
  177. //# sourceMappingURL=NodeFileSystemWritableFileStream.js.map