Skip to content

Commit 0f38835

Browse files
committed
Drastically improve client
1 parent f3a07dd commit 0f38835

File tree

2 files changed

+85
-21
lines changed

2 files changed

+85
-21
lines changed

src/client.ts

+81-21
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ const { AWS_REGION, S3_BUCKET, WEBSOCKET_URL, LOCAL_DIR } =
2222
"LOCAL_DIR",
2323
);
2424

25+
const recentLocalDeletions = new Set<string>();
26+
const recentDownloads = new Set<string>();
27+
const recentDeletions = new Set<string>();
28+
const recentUploads = new Set<string>();
29+
// Time between remote operations have finished and the resulting S3 event that we will get - which is hopefully earlier than this timeout.
30+
const RECENT_REMOTE_TIMEOUT = 2000;
31+
// Time between writing the download has finished and chokidar hopefully getting triggered earlier than this.
32+
const RECENT_LOCAL_TIMEOUT = 500;
33+
2534
const s3Client = new S3Client({ region: AWS_REGION });
2635

2736
// Ensure the local sync directory exists
@@ -30,7 +39,7 @@ fs.mkdir(LOCAL_DIR, { recursive: true });
3039
const ws = new WebSocket(WEBSOCKET_URL);
3140

3241
ws.on("open", () => {
33-
console.log("Connected to WebSocket server");
42+
console.log(`Connected to ${WEBSOCKET_URL}`);
3443
});
3544

3645
ws.on("message", async (data) => {
@@ -60,10 +69,17 @@ ws.on("message", async (data) => {
6069

6170
ws.on("close", () => {
6271
console.log("Disconnected from WebSocket server");
72+
process.exit(1);
6373
});
6474

6575
async function downloadFile(key: string) {
6676
const localPath = path.join(LOCAL_DIR, key);
77+
if (recentUploads.has(localPath)) {
78+
console.log(
79+
`Skipping download for file recently uploaded to S3: ${localPath}`,
80+
);
81+
return;
82+
}
6783

6884
try {
6985
const { Body } = await s3Client.send(
@@ -74,9 +90,16 @@ async function downloadFile(key: string) {
7490
);
7591

7692
if (Body) {
93+
recentDownloads.add(localPath);
94+
7795
await fs.mkdir(path.dirname(localPath), { recursive: true });
7896
await fs.writeFile(localPath, await Body.transformToByteArray());
7997
console.log(`Downloaded: ${key}`);
98+
99+
// Start timeout only after writing the file has finished
100+
setTimeout(() => {
101+
recentDownloads.delete(localPath);
102+
}, RECENT_LOCAL_TIMEOUT);
80103
}
81104
} catch (error) {
82105
console.error(`Error downloading file ${key}:`, error);
@@ -85,10 +108,22 @@ async function downloadFile(key: string) {
85108

86109
async function removeLocalFile(key: string) {
87110
const localPath = path.join(LOCAL_DIR, key);
111+
if (recentDeletions.has(localPath)) {
112+
console.log(
113+
`Skipping local removal for file recently deleted on S3: ${localPath}`,
114+
);
115+
return;
116+
}
88117

89118
try {
119+
recentLocalDeletions.add(localPath);
120+
90121
await fs.unlink(localPath);
91122
console.log(`Removed local file: ${localPath}`);
123+
124+
setTimeout(() => {
125+
recentLocalDeletions.delete(localPath);
126+
}, RECENT_LOCAL_TIMEOUT);
92127
} catch (error) {
93128
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
94129
console.error(`Error removing local file ${localPath}:`, error);
@@ -98,11 +133,47 @@ async function removeLocalFile(key: string) {
98133
}
99134
}
100135

136+
async function removeFile(localPath: string) {
137+
if (recentLocalDeletions.has(localPath)) {
138+
console.log(
139+
`Skipping repeated S3 removal for recently deleted file: ${localPath}`,
140+
);
141+
return;
142+
}
143+
144+
const key = path.relative(LOCAL_DIR, localPath);
145+
146+
try {
147+
recentDeletions.add(localPath);
148+
149+
await s3Client.send(
150+
new DeleteObjectCommand({
151+
Bucket: S3_BUCKET,
152+
Key: key,
153+
}),
154+
);
155+
console.log(`Deleted from S3: ${key}`);
156+
157+
setTimeout(() => {
158+
recentDeletions.delete(localPath);
159+
}, RECENT_REMOTE_TIMEOUT);
160+
} catch (error) {
161+
console.error(`Error deleting file ${key} from S3:`, error);
162+
}
163+
}
164+
101165
async function syncFile(localPath: string) {
166+
if (recentDownloads.has(localPath)) {
167+
console.log(`Skipping upload for recently downloaded file: ${localPath}`);
168+
return;
169+
}
170+
102171
const key = path.relative(LOCAL_DIR, localPath);
103172

104173
async function uploadFile() {
105174
try {
175+
recentUploads.add(localPath);
176+
106177
const fileContent = await fs.readFile(localPath);
107178
await s3Client.send(
108179
new PutObjectCommand({
@@ -112,6 +183,10 @@ async function syncFile(localPath: string) {
112183
}),
113184
);
114185
console.log(`Uploaded: ${key}`);
186+
187+
setTimeout(() => {
188+
recentUploads.delete(localPath);
189+
}, RECENT_REMOTE_TIMEOUT);
115190
} catch (error) {
116191
console.error(`Error uploading file ${key}:`, error);
117192
}
@@ -140,25 +215,10 @@ async function syncFile(localPath: string) {
140215
}
141216
}
142217

143-
// Watch for local file changes
144-
const watcher = chokidar.watch(LOCAL_DIR);
145-
146-
watcher
147-
.on("add", syncFile)
148-
.on("change", syncFile)
149-
.on("unlink", async (localPath) => {
150-
const key = path.relative(LOCAL_DIR, localPath);
151-
try {
152-
await s3Client.send(
153-
new DeleteObjectCommand({
154-
Bucket: S3_BUCKET,
155-
Key: key,
156-
}),
157-
);
158-
console.log(`Deleted from S3: ${key}`);
159-
} catch (error) {
160-
console.error(`Error deleting file ${key} from S3:`, error);
161-
}
162-
});
218+
// Don't use for`awaitWriteFinish` because that would cause conflicts with the RECENT_TIMEOUT. Because that time only starts once writing has finished, potential `add` events during writing are ignored anyway.
219+
const watcher = chokidar.watch(LOCAL_DIR, {
220+
ignoreInitial: true,
221+
});
222+
watcher.on("add", syncFile).on("change", syncFile).on("unlink", removeFile);
163223

164224
console.log(`Watching for changes in ${LOCAL_DIR}`);

src/server.ts

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ app.use(
3838
}),
3939
);
4040

41+
app.get("/", (_, res) => {
42+
res.status(200).send("Running.");
43+
});
44+
4145
app.post("/sns", async (req, res) => {
4246
const message = req.body as SNSMessage;
4347

0 commit comments

Comments
 (0)