Files
repertory-js/src/ops/index.js

645 lines
17 KiB
JavaScript

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}`));
}
};