import fs from 'fs'; import { Uint64BE } from 'int64-buffer'; import file from '../io/file'; import packet from '../networking/packet'; import { RW_BUFFER_SIZE } from '../utils/constants'; const _snapshot_directory = async (conn, remote_path) => { try { const request = new packet(); request.encode_utf8(remote_path); const response = await conn.send( '::json_create_directory_snapshot', request ); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result === 0) { const data = JSON.parse(response.decode_utf8()); let released = false; const release = async () => { if (!released) { released = true; const request = new packet(); request.encode_ui64(data.handle); await conn.send('::json_release_directory_snapshot', request); } }; try { const get_page = async (page) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui64(data.handle); request.encode_ui32(page); const response = await conn.send( '::json_read_directory_snapshot', request ); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result === 0 || result === -120) { const data = JSON.parse(response.decode_utf8()); return data.directory_list; } } catch (err) { await release(); return Promise.reject(new Error(`'get_page' failed: ${err}`)); } return []; }; return { get_page, page_count: data.page_count, release, remote_path, }; } catch (err) { await release(); return Promise.reject(new Error(`'snapshot_directory' failed: ${err}`)); } } } catch (err) { return Promise.reject(new Error(`'snapshot_directory' failed: ${err}`)); } }; export const close_file = async ( conn, remote_path, handle, optional_thread_id ) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui64(handle); const response = await conn.send( '::fuse_release', request, optional_thread_id ); response.decode_ui32(); // Service flags return response.decode_i32(); } catch (err) { return Promise.reject(new Error(`'close_file' failed: ${err}`)); } }; export const create_directory = async (conn, remote_path) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui16((7 << 6) | (5 << 3)); const response = await conn.send('::fuse_mkdir', request); response.decode_ui32(); // Service flags return response.decode_i32(); } catch (err) { return Promise.reject(new Error(`'create_directory' failed: ${err}`)); } }; export const create_or_open_file = async ( conn, remote_path, optional_thread_id ) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui16((7 << 6) | (5 << 3)); request.encode_ui32(2 | 4); // Read-Write, Create const response = await conn.send( '::fuse_create', request, optional_thread_id ); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result === 0) { return response.decode_ui64(); } return Promise.reject(new Error(`'create_or_open_file' error: ${result}`)); } catch (err) { return Promise.reject(new Error(`'create_or_open_file' failed: ${err}`)); } }; export const remove_file = async (conn, remote_path) => { try { const request = new packet(); request.encode_utf8(remote_path); const response = await conn.send('::fuse_unlink', request); response.decode_ui32(); // Service flags return response.decode_i32(); } catch (err) { return Promise.reject(new Error(`'remove_file' failed: ${err}`)); } }; export const download_file = async ( conn, remote_path, local_path, progress_cb, overwrite, resume ) => { try { const src = new file(conn, await open_file(conn, remote_path), remote_path); const cleanup = async (fd) => { try { await src.close(); } catch (err) { console.error(err); } try { if (fd !== undefined) { fs.closeSync(fd); } } catch (err) { console.error(err); } }; try { const src_size = await src.get_size(); let dst_fd; try { let offset = 0; if (overwrite) { dst_fd = fs.openSync(local_path, 'w+'); } else if (resume) { dst_fd = fs.openSync(local_path, 'r+'); const dst_size = fs.fstatSync(dst_fd).size; if (dst_size === src_size) { await cleanup(dst_fd); return true; } if (dst_size > src_size) { await cleanup(dst_fd); return Promise.reject( new Error( `'download_file' failed: destination is larger than source` ) ); } offset = dst_size; } else { if (fs.existsSync(local_path)) { await cleanup(dst_fd); return Promise.reject( new Error(`'download_file' failed: file exists`) ); } dst_fd = fs.openSync(local_path, 'wx+'); } let remain = src_size - offset; while (remain > 0) { const to_write = remain >= RW_BUFFER_SIZE ? RW_BUFFER_SIZE : remain; const buffer = await src.read(offset, to_write); const written = fs.writeSync(dst_fd, buffer, 0, to_write, offset); if (written > 0) { remain -= written; offset += written; if (progress_cb) { progress_cb( local_path, remote_path, ((src_size - remain) / src_size) * 100.0, false ); } } } if (progress_cb) { progress_cb(local_path, remote_path, 100, true); } await cleanup(dst_fd); return true; } catch (err) { await cleanup(dst_fd); return Promise.reject(new Error(`'download_file' failed: ${err}`)); } } catch (err) { await cleanup(); return Promise.reject(new Error(`'download_file' failed: ${err}`)); } } catch (err) { return Promise.reject(new Error(`'download_file' failed: ${err}`)); } }; export const get_drive_information = async (conn) => { try { const response = await conn.send('::winfsp_get_volume_info', new packet()); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result === 0) { const total = response.decode_ui64(); const free = response.decode_ui64(); return { free, total, used: (new Uint64BE(total) - new Uint64BE(free)).toString(10), }; } return Promise.reject( new Error(`'get_drive_information' failed: ${result}`) ); } catch (err) { return Promise.reject(new Error(`'get_drive_information' failed: ${err}`)); } }; export const get_file_attributes = async ( conn, handle, remote_path, optional_thread_id ) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui64(handle); request.encode_ui32(0); request.encode_ui32(0); const response = await conn.send( '::fuse_fgetattr', request, optional_thread_id ); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result === 0) { return response.decode_stat(); } return Promise.reject(new Error(`'get_file_attributes' failed: ${result}`)); } catch (err) { return Promise.reject(new Error(`'get_file_attributes' failed: ${err}`)); } }; export const get_file_attributes2 = async ( conn, remote_path, optional_thread_id ) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui32(0); request.encode_ui32(0); const response = await conn.send( '::fuse_getattr', request, optional_thread_id ); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result === 0) { return response.decode_stat(); } return Promise.reject( new Error(`'get_file_attributes2' failed: ${result}`) ); } catch (err) { return Promise.reject(new Error(`'get_file_attributes2' failed: ${err}`)); } }; export const list_directory = async (conn, remote_path, page_reader_cb) => { const dir_snapshot = await _snapshot_directory(conn, remote_path); try { await page_reader_cb( dir_snapshot.remote_path, dir_snapshot.page_count, dir_snapshot.get_page ); await dir_snapshot.release(); } catch (err) { await dir_snapshot.release(); return Promise.reject(`'list_directory' failed: ${err}`); } }; export const open_file = async (conn, remote_path, optional_thread_id) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui32(2); // Read-Write const response = await conn.send( '::fuse_open', request, optional_thread_id ); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result === 0) { return response.decode_ui64(); } return Promise.reject(new Error(`'open_file' error: ${result}`)); } catch (err) { return Promise.reject(new Error(`'open_file' failed: ${err}`)); } }; export const read_file = async ( conn, handle, remote_path, offset, length, optional_thread_id ) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui64(length); request.encode_ui64(offset); request.encode_ui64(handle); const response = await conn.send( '::fuse_read', request, optional_thread_id ); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result === length) { return response.decode_buffer(result); } return Promise.reject(new Error(`'read_file' error: ${result}`)); } catch (err) { return Promise.reject(new Error(`'read_file' failed: ${err}`)); } }; export const remove_directory = async (conn, remote_path) => { try { const request = new packet(); request.encode_utf8(remote_path); const response = await conn.send('::fuse_rmdir', request); response.decode_ui32(); // Service flags return response.decode_i32(); } catch (err) { return Promise.reject(new Error(`'remove_directory' failed: ${err}`)); } }; export const snapshot_directory = _snapshot_directory; export const truncate_file = async ( conn, handle, remote_path, length, optional_thread_id ) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui64(length); request.encode_ui64(handle); const response = await conn.send( '::fuse_ftruncate', request, optional_thread_id ); response.decode_ui32(); // Service flags return response.decode_i32(); } catch (err) { return Promise.reject(new Error(`'truncate_file' failed: ${err}`)); } }; export const upload_file = async ( conn, local_path, remote_path, progress_cb, overwrite, resume ) => { try { const src_fd = fs.openSync(local_path, 'r'); const cleanup = async (f) => { try { fs.closeSync(src_fd); } catch (err) { console.error(err); } try { if (f) { await f.close(); } } catch (err) { console.error(err); } }; try { const src_st = fs.fstatSync(src_fd); let dst; const create_dest = async () => { dst = new file( conn, await create_or_open_file(conn, remote_path), remote_path ); }; try { let offset = 0; if (overwrite) { await create_dest(); const result = await dst.truncate(0); if (result !== 0) { await cleanup(dst); return Promise.reject(new Error(`'upload_file' failed: ${result}`)); } } else if (resume) { await create_dest(); const dst_size = new Uint64BE(await dst.get_size()).toNumber(); if (dst_size === src_st.size) { await cleanup(dst); return true; } if (dst_size > src_st.size) { await cleanup(dst); return Promise.reject( new Error( `'upload_file' failed: destination is larger than source` ) ); } offset = dst_size; } else { try { const f = new file( conn, await open_file(conn, remote_path), remote_path ); await cleanup(f); return Promise.reject( new Error("'upload_file' failed: file exists") ); } catch (err) { await create_dest(); } } let remain = src_st.size - offset; const default_buffer = Buffer.alloc(RW_BUFFER_SIZE); while (remain > 0) { const to_write = remain >= default_buffer.length ? default_buffer.length : remain; const buffer = to_write === default_buffer.length ? default_buffer : Buffer.alloc(to_write); fs.readSync(src_fd, buffer, 0, to_write, offset); const written = await dst.write(offset, buffer); if (written > 0) { remain -= written; offset += written; if (progress_cb) { progress_cb( local_path, remote_path, ((src_st.size - remain) / src_st.size) * 100.0, false ); } } } if (progress_cb) { progress_cb(local_path, remote_path, 100, true); } await cleanup(dst); return true; } catch (err) { await cleanup(dst); return Promise.reject(new Error(`'upload_file' failed: ${err}`)); } } catch (err) { await cleanup(); return Promise.reject(new Error(`'upload_file' failed: ${err}`)); } } catch (err) { return Promise.reject(new Error(`'upload_file' failed: ${err}`)); } }; export const write_base64_file = async ( conn, handle, remote_path, offset, base64_string, optional_thread_id ) => { try { const buffer = new Uint8Array(new TextEncoder().encode(base64_string)); const request = new packet(); request.encode_utf8(remote_path); request.encode_ui64(buffer.length); request.encode_buffer(buffer); request.encode_ui64(offset); request.encode_ui64(handle); const response = await conn.send( '::fuse_write_base64', request, optional_thread_id ); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result > 0) { return result; } return Promise.reject(new Error(`'write_base64_file' error: ${result}`)); } catch (err) { return Promise.reject(new Error(`'write_base64_file' failed: ${err}`)); } }; export const write_file = async ( conn, handle, remote_path, offset, buffer, optional_thread_id ) => { try { const request = new packet(); request.encode_utf8(remote_path); request.encode_ui64(buffer.length); request.encode_buffer(buffer); request.encode_ui64(offset); request.encode_ui64(handle); const response = await conn.send( '::fuse_write', request, optional_thread_id ); response.decode_ui32(); // Service flags const result = response.decode_i32(); if (result === buffer.length) { return result; } return Promise.reject(new Error(`'write_file' error: ${result}`)); } catch (err) { return Promise.reject(new Error(`'write_file' failed: ${err}`)); } };