Skip to content

Commit 194e17f

Browse files
committed
improve download performance
1 parent 9532cd5 commit 194e17f

File tree

5 files changed

+135
-77
lines changed

5 files changed

+135
-77
lines changed

assets/homepage.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
///<reference lib="es2021" />
2+
13
function humanFileSize(bytes: number, si = false, dp = 1) {
24
const thresh = si ? 1000 : 1024;
35
if (Math.abs(bytes) < thresh) {
@@ -35,14 +37,14 @@ async function generate_aes_ctr_keys() {
3537
return {
3638
key: key,
3739
key_base64: btoa(String.fromCharCode.apply(null, key_array))
38-
.replace("+", "-")
39-
.replace("/", "_")
40-
.replace("=", ""),
40+
.replaceAll("+", "-")
41+
.replaceAll("/", "_")
42+
.replaceAll("=", ""),
4143
nonce: nonce_array,
4244
nonce_base64: btoa(String.fromCharCode.apply(null, nonce_array))
43-
.replace("+", "-")
44-
.replace("/", "_")
45-
.replace("=", ""),
45+
.replaceAll("+", "-")
46+
.replaceAll("/", "_")
47+
.replaceAll("=", ""),
4648
};
4749
}
4850

@@ -77,9 +79,9 @@ async function encrypt_file_name(
7779
new Uint8Array(encrypted_filename_array)
7880
)
7981
)
80-
.replace("+", "-")
81-
.replace("/", "_")
82-
.replace("=", "");
82+
.replaceAll("+", "-")
83+
.replaceAll("/", "_")
84+
.replaceAll("=", "");
8385
return file_id + "." + encrypted_filename_base64;
8486
}
8587

assets/receive.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
///<reference lib="es2021" />
2+
13
function sleep(ms: number) {
24
return new Promise((resolve) => setTimeout(resolve, ms));
35
}
@@ -30,9 +32,9 @@ async function recover_aes_ctr_key(key_base64: string, nonce_base64: string) {
3032
throw new Error("nonce is broken");
3133
}
3234
let original_key_base64 =
33-
key_base64.replace("-", "+").replace("_", "/") + "=";
35+
key_base64.replaceAll("-", "+").replaceAll("_", "/") + "=";
3436
let original_nonce_base64 =
35-
nonce_base64.replace("-", "+").replace("_", "/") + "=";
37+
nonce_base64.replaceAll("-", "+").replaceAll("_", "/") + "=";
3638
let key_array = atob(original_key_base64)
3739
.split("")
3840
.map((c) => c.charCodeAt(0));
@@ -74,7 +76,7 @@ async function decrypt_file_name(
7476
padding_equals = 4 - padding_equals;
7577
}
7678
let name_encrypted_original_base64 =
77-
name_encrypted.replace("-", "+").replace("_", "/") +
79+
name_encrypted.replaceAll("-", "+").replaceAll("_", "/") +
7880
"=".repeat(padding_equals);
7981
let name_encrypted_array = atob(name_encrypted_original_base64)
8082
.split("")

minify.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ npx tsc ./assets.src/receive.ts --target es2017
2020
npx webpack ./assets.src/receive.js -o ./dist --mode production
2121
mv dist/main.js assets/receive.js
2222
npx tsc ./virtual-downloader.ts --target es2017
23+
sed -i '/export {};/d' ./virtual-downloader.js
2324
npx webpack ./virtual-downloader.js -o ./dist --mode production
2425
mv dist/main.js virtual-downloader.js

onesend.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ func entry() error {
219219
c.Data(200, "text/html", publicIndex)
220220
})
221221
r.GET("/auth.html", func(c *gin.Context) {
222+
if client != nil {
223+
c.Redirect(302, "/")
224+
return
225+
}
222226
c.Header("Cache-Control", "public, max-age=604800")
223227
c.Data(200, "text/html", publicAuth)
224228
})

virtual-downloader.ts

Lines changed: 114 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference lib="webworker" />
22
/// <reference no-default-lib="true"/>
33

4-
export type {}; // let typescript shut up
4+
export type {}; // make typescript shut up, this line should be deleted after transpiled
55
declare let self: ServiceWorkerGlobalScope;
66

77
const CACHE_KEY = "v1.0.1";
@@ -36,22 +36,56 @@ async function decrypt_file_part(
3636
return plain;
3737
}
3838

39-
self.addEventListener("install", function (event) {
40-
event.waitUntil(
41-
(async function () {
42-
let cache = await caches.open(CACHE_KEY);
43-
return cache.addAll([
44-
"/assets/favicon.ico",
45-
"/",
46-
"/assets/homepage.js",
47-
"/assets/homepage.css",
48-
"/s/*",
49-
"/assets/receive.js",
50-
"/assets/receive.css",
51-
]);
52-
})()
53-
);
54-
});
39+
class Chunker {
40+
done = false;
41+
private remaining: Uint8Array | undefined;
42+
remainingSize = 0;
43+
private reader: ReadableStreamDefaultReader<Uint8Array>;
44+
45+
constructor(stream: ReadableStream<Uint8Array>, private size = 16) {
46+
this.reader = stream.getReader();
47+
}
48+
49+
async read(): Promise<
50+
{ done: true; value: undefined } | { done: false; value: Uint8Array }
51+
> {
52+
if (this.done) {
53+
return { done: true, value: undefined };
54+
}
55+
const { done, value } = await this.reader.read();
56+
if (done || value === undefined) {
57+
this.done = true;
58+
if (this.remaining === undefined) {
59+
return { done: true, value: undefined };
60+
} else {
61+
return { done: false, value: this.remaining };
62+
}
63+
}
64+
const inSize = value.byteLength + this.remainingSize;
65+
const remainingSize = inSize % this.size;
66+
const outSize = inSize - remainingSize;
67+
let out: Uint8Array;
68+
if (this.remaining !== undefined) {
69+
out = new Uint8Array(outSize);
70+
out.set(this.remaining);
71+
out.set(
72+
value.slice(0, value.byteLength - remainingSize),
73+
this.remainingSize
74+
);
75+
} else {
76+
out = value.slice(0, value.byteLength - remainingSize);
77+
}
78+
79+
this.remainingSize = remainingSize;
80+
if (remainingSize > 0) {
81+
this.remaining = value.slice(value.byteLength - remainingSize);
82+
} else {
83+
this.remaining = undefined;
84+
}
85+
86+
return { done: false, value: out };
87+
}
88+
}
5589

5690
self.addEventListener("activate", function (event) {
5791
event.waitUntil(self.clients.claim());
@@ -63,17 +97,6 @@ self.addEventListener("message", function (event) {
6397
}
6498
});
6599

66-
async function try_fetch(input, init, tries = 3) {
67-
try {
68-
return await fetch(input, init);
69-
} catch (e) {
70-
if (tries > 0) {
71-
return try_fetch(input, init, tries - 1);
72-
}
73-
throw e;
74-
}
75-
}
76-
77100
self.addEventListener("fetch", function (event) {
78101
let request = event.request;
79102
let url = new URL(request.url);
@@ -82,7 +105,7 @@ self.addEventListener("fetch", function (event) {
82105
}
83106
let path = url.pathname;
84107
if (path.startsWith("/s/download")) {
85-
event.respondWith(virtual_downloading_response(path));
108+
event.respondWith(virtual_downloading_response(request));
86109
return;
87110
}
88111
if (path.startsWith("/s/")) {
@@ -91,7 +114,22 @@ self.addEventListener("fetch", function (event) {
91114
event.respondWith(cached_response(request));
92115
});
93116

94-
async function virtual_downloading_response(path: string) {
117+
function rangeOf(request: Request) {
118+
let range = request.headers.get("Range");
119+
if (range === null) {
120+
return null;
121+
}
122+
let range_match = range.match(/^bytes=(\d+)-(\d+)$/);
123+
if (range_match === null) {
124+
return null;
125+
}
126+
let start = parseInt(range_match[1]);
127+
let end = parseInt(range_match[2]);
128+
return [start, end];
129+
}
130+
131+
async function virtual_downloading_response(request: Request) {
132+
const path = new URL(request.url).pathname;
95133
let path_list = path.split("/");
96134
let file_path = path_list[path_list.length - 1];
97135
let file_info = FilesData[file_path];
@@ -101,53 +139,64 @@ async function virtual_downloading_response(path: string) {
101139
statusText: "Not Found",
102140
});
103141
}
142+
let headers = new Headers();
143+
// let range = rangeOf(request);
144+
// let start: number;
145+
// if (range !== null) {
146+
// start = range[0];
147+
// } else {
148+
// start = 0;
149+
// }
150+
// if (range !== null) {
151+
// headers.set("Range", `bytes=${range[0]}-${range[1]}`);
152+
// }
153+
//// TODO: handle cases when range does not start from multiple of 16
154+
let { abort, signal } = new AbortController();
155+
let response = await fetch(file_info.download_url, { headers, signal });
156+
let body = response.body;
157+
if (body === null) {
158+
return response;
159+
}
160+
let reader = new Chunker(body, 16); // chunk stream to size of multiple of 16 bytes
104161
let decrypted_readable_stream = new ReadableStream({
105162
async start(controller) {
106-
const chunk_size = 1310720;
107-
let chunk_number = Math.ceil(file_info.file_size / chunk_size);
108-
let fetched = 0;
109-
let fetch_queue: Promise<Uint8Array | null>[] = [];
110-
async function next_fetch() {
111-
if (fetched >= chunk_number) {
112-
return null;
163+
let offset = 0;
164+
while (true) {
165+
let readResult = await reader.read();
166+
if (readResult.done) {
167+
break;
113168
}
114-
let i = fetched;
115-
fetched += 1;
116-
let start = i * chunk_size;
117-
let end;
118-
if (i === chunk_number - 1) {
119-
end = file_info.file_size - 1;
120-
} else {
121-
end = start + chunk_size - 1;
122-
}
123-
let response = await try_fetch(file_info.download_url, {
124-
headers: { Range: `bytes=${start}-${end}` },
125-
});
126-
let data = await response.arrayBuffer();
127169
let plain = await decrypt_file_part(
128170
file_info.key,
129-
data,
171+
readResult.value,
130172
file_info.nonce,
131173
file_info.file_id,
132-
start / 16
174+
offset / 16
133175
);
134-
return new Uint8Array(plain);
135-
}
136-
fetch_queue.push(next_fetch());
137-
setTimeout(function () {
138-
// 4 concurrent download
139-
fetch_queue.push(next_fetch());
140-
fetch_queue.push(next_fetch());
141-
fetch_queue.push(next_fetch());
142-
}, 1000);
143-
for (let j = 0; j < chunk_number; j++) {
144-
let chunk = await fetch_queue.shift();
145-
controller.enqueue(chunk);
146-
fetch_queue.push(next_fetch());
176+
offset += readResult.value.byteLength;
177+
controller.enqueue(new Uint8Array(plain));
147178
}
148179
controller.close();
149180
},
181+
cancel() {
182+
abort();
183+
},
150184
});
185+
// let decrypted_readable_stream = body.pipeThrough(
186+
// new TransformStream({
187+
// async transform(chunk, controller) {
188+
// let plain = await decrypt_file_part(
189+
// file_info.key,
190+
// chunk,
191+
// file_info.nonce,
192+
// file_info.file_id,
193+
// start / 16
194+
// );
195+
// start += chunk.byteLength;
196+
// controller.enqueue(new Uint8Array(plain));
197+
// },
198+
// })
199+
// );
151200
return new Response(decrypted_readable_stream, {
152201
headers: {
153202
"Content-Length": file_info.file_size,

0 commit comments

Comments
 (0)