1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
import { spawnSync } from 'child_process'
import { DriveLsEntry, DriveLogEntry } from './drive_types'
/**
* Server-only drive functions that use Node.js APIs
*/
function executeFctDriveCommand(args: string[]): string {
const result = spawnSync('fctdrive', args, { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 })
if (result.error) {
throw new Error(`Failed to execute fctdrive: ${result.error.message}`)
}
if (result.status !== 0) {
throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`)
}
return result.stdout
}
/// lists the given path on the drive
export async function Drive_ls(path: string, recursive: boolean): Promise<DriveLsEntry[]> {
const args = ['ls']
if (recursive) {
args.push('-r')
}
if (path) {
args.push(path)
}
const stdout = executeFctDriveCommand(args)
const entries = []
for (const line of stdout.split('\n')) {
if (line.trim() == "")
continue;
const parts = line.split('\t');
const path = parts[0];
const type = parts[1];
const lastmod = parseInt(parts[2]);
const blobStr = parts[3];
const sizeStr = parts[4];
const author = parts[5];
let blob = null;
if (blobStr != "-")
blob = blobStr;
let size = null;
if (sizeStr != "-")
size = parseFloat(sizeStr);
entries.push({
path, type, lastmod, blob, size, author
} as DriveLsEntry);
}
return entries;
}
/// import the file at local_path by moving it to drive_path
export async function Drive_import(local_path: string, drive_path: string, email: string) {
executeFctDriveCommand(['import', local_path, "--mode", "move", "--destination", drive_path, "--email", email]);
}
/// returns the full filesystem path of the blob given its id
export async function Drive_blob_path(blob: string): Promise<string> {
const stdout = executeFctDriveCommand(['blob', blob])
return stdout.trim();
}
/// removes the file or directory at the given path
export async function Drive_remove(path: string, email: string) {
executeFctDriveCommand(['remove', '--email', email, path]);
}
/// creates a directory at the given path
export async function Drive_mkdir(path: string, email: string) {
executeFctDriveCommand(['mkdir', '--email', email, '--path', path]);
}
/// renames a file or directory from old path to new path
export async function Drive_rename(oldPath: string, newPath: string, email: string) {
executeFctDriveCommand(['rename', '--email', email, '--old', oldPath, '--new', newPath]);
}
/// gets the blob ID for a given path
export async function Drive_stat(path: string): Promise<string> {
const stdout = executeFctDriveCommand(['stat', path]);
return stdout.trim();
}
/// lists only directories (recursively) from the given path
export async function Drive_ls_directories(path: string = '/'): Promise<DriveLsEntry[]> {
// Get all entries recursively and filter for directories
const allEntries = await Drive_ls(path, true)
return allEntries.filter(entry => entry.type === 'dir')
}
/// returns the log entries from the drive
export async function Drive_log(): Promise<DriveLogEntry[]> {
const stdout = executeFctDriveCommand(['log'])
const entries: DriveLogEntry[] = []
for (const line of stdout.split('\n')) {
if (line.trim() === "")
continue;
const parts = line.split('\t');
const timestamp = parseInt(parts[0]);
const revision = parseInt(parts[1]);
const email = parts[2];
const action = parts[3];
// Parse based on action type to create the correct discriminated union
switch (action) {
case "create_file": {
const path = parts[4];
const blob_id = parts[5];
const size = parseInt(parts[6]);
entries.push({
timestamp,
revision,
email,
action: "create_file",
path,
blob_id,
size
});
break;
}
case "create_dir": {
const path = parts[4];
entries.push({
timestamp,
revision,
email,
action: "create_dir",
path
});
break;
}
case "remove": {
const path = parts[4];
entries.push({
timestamp,
revision,
email,
action: "remove",
path
});
break;
}
case "rename": {
const old_path = parts[4];
const new_path = parts[5];
entries.push({
timestamp,
revision,
email,
action: "rename",
old_path,
new_path
});
break;
}
default:
console.warn(`Unknown log action: ${action}`, parts);
continue;
}
}
return entries;
}
|