rezo 1.0.66 → 1.0.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/entries/curl.d.ts +5 -0
- package/dist/adapters/entries/fetch.d.ts +5 -0
- package/dist/adapters/entries/http.d.ts +5 -0
- package/dist/adapters/entries/http2.d.ts +5 -0
- package/dist/adapters/entries/react-native.d.ts +5 -0
- package/dist/adapters/entries/xhr.d.ts +5 -0
- package/dist/adapters/index.cjs +6 -6
- package/dist/cache/index.cjs +9 -9
- package/dist/crawler/crawler.cjs +26 -5
- package/dist/crawler/crawler.js +26 -5
- package/dist/crawler/index.cjs +40 -40
- package/dist/crawler.d.ts +10 -0
- package/dist/entries/crawler.cjs +4 -4
- package/dist/index.cjs +27 -27
- package/dist/index.d.ts +5 -0
- package/dist/internal/agents/index.cjs +10 -10
- package/dist/platform/browser.d.ts +5 -0
- package/dist/platform/bun.d.ts +5 -0
- package/dist/platform/deno.d.ts +5 -0
- package/dist/platform/node.d.ts +5 -0
- package/dist/platform/react-native.d.ts +5 -0
- package/dist/platform/worker.d.ts +5 -0
- package/dist/proxy/index.cjs +4 -4
- package/dist/proxy/manager.cjs +1 -1
- package/dist/proxy/manager.js +1 -1
- package/dist/queue/index.cjs +8 -8
- package/dist/queue/queue.cjs +3 -1
- package/dist/queue/queue.js +3 -1
- package/dist/responses/universal/index.cjs +11 -11
- package/dist/wget/asset-extractor.cjs +556 -0
- package/dist/wget/asset-extractor.js +553 -0
- package/dist/wget/asset-organizer.cjs +230 -0
- package/dist/wget/asset-organizer.js +227 -0
- package/dist/wget/download-cache.cjs +221 -0
- package/dist/wget/download-cache.js +218 -0
- package/dist/wget/downloader.cjs +607 -0
- package/dist/wget/downloader.js +604 -0
- package/dist/wget/file-writer.cjs +349 -0
- package/dist/wget/file-writer.js +346 -0
- package/dist/wget/filter-lists.cjs +1330 -0
- package/dist/wget/filter-lists.js +1330 -0
- package/dist/wget/index.cjs +633 -0
- package/dist/wget/index.d.ts +8486 -0
- package/dist/wget/index.js +614 -0
- package/dist/wget/link-converter.cjs +297 -0
- package/dist/wget/link-converter.js +294 -0
- package/dist/wget/progress.cjs +271 -0
- package/dist/wget/progress.js +266 -0
- package/dist/wget/resume.cjs +166 -0
- package/dist/wget/resume.js +163 -0
- package/dist/wget/robots.cjs +303 -0
- package/dist/wget/robots.js +300 -0
- package/dist/wget/types.cjs +200 -0
- package/dist/wget/types.js +197 -0
- package/dist/wget/url-filter.cjs +351 -0
- package/dist/wget/url-filter.js +348 -0
- package/package.json +6 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const { createHash } = require("node:crypto");
|
|
2
|
+
const { extname, basename, join } = require("node:path");
|
|
3
|
+
const DEFAULT_ASSET_FOLDERS = exports.DEFAULT_ASSET_FOLDERS = {
|
|
4
|
+
css: "css",
|
|
5
|
+
js: "js",
|
|
6
|
+
images: "images",
|
|
7
|
+
fonts: "fonts",
|
|
8
|
+
audio: "audio",
|
|
9
|
+
video: "video",
|
|
10
|
+
other: "assets"
|
|
11
|
+
};
|
|
12
|
+
const MIME_TO_ASSET = {
|
|
13
|
+
"text/css": "css",
|
|
14
|
+
"application/javascript": "js",
|
|
15
|
+
"application/x-javascript": "js",
|
|
16
|
+
"text/javascript": "js",
|
|
17
|
+
"application/ecmascript": "js",
|
|
18
|
+
"image/jpeg": "images",
|
|
19
|
+
"image/png": "images",
|
|
20
|
+
"image/gif": "images",
|
|
21
|
+
"image/webp": "images",
|
|
22
|
+
"image/svg+xml": "images",
|
|
23
|
+
"image/x-icon": "images",
|
|
24
|
+
"image/vnd.microsoft.icon": "images",
|
|
25
|
+
"image/bmp": "images",
|
|
26
|
+
"image/tiff": "images",
|
|
27
|
+
"image/avif": "images",
|
|
28
|
+
"font/woff": "fonts",
|
|
29
|
+
"font/woff2": "fonts",
|
|
30
|
+
"font/ttf": "fonts",
|
|
31
|
+
"font/otf": "fonts",
|
|
32
|
+
"application/font-woff": "fonts",
|
|
33
|
+
"application/font-woff2": "fonts",
|
|
34
|
+
"application/x-font-ttf": "fonts",
|
|
35
|
+
"application/x-font-otf": "fonts",
|
|
36
|
+
"application/vnd.ms-fontobject": "fonts",
|
|
37
|
+
"audio/mpeg": "audio",
|
|
38
|
+
"audio/mp3": "audio",
|
|
39
|
+
"audio/wav": "audio",
|
|
40
|
+
"audio/ogg": "audio",
|
|
41
|
+
"audio/webm": "audio",
|
|
42
|
+
"audio/aac": "audio",
|
|
43
|
+
"audio/flac": "audio",
|
|
44
|
+
"video/mp4": "video",
|
|
45
|
+
"video/webm": "video",
|
|
46
|
+
"video/ogg": "video",
|
|
47
|
+
"video/quicktime": "video",
|
|
48
|
+
"video/x-msvideo": "video",
|
|
49
|
+
"video/x-ms-wmv": "video"
|
|
50
|
+
};
|
|
51
|
+
const EXT_TO_ASSET = {
|
|
52
|
+
".css": "css",
|
|
53
|
+
".js": "js",
|
|
54
|
+
".mjs": "js",
|
|
55
|
+
".cjs": "js",
|
|
56
|
+
".jpg": "images",
|
|
57
|
+
".jpeg": "images",
|
|
58
|
+
".png": "images",
|
|
59
|
+
".gif": "images",
|
|
60
|
+
".webp": "images",
|
|
61
|
+
".svg": "images",
|
|
62
|
+
".ico": "images",
|
|
63
|
+
".bmp": "images",
|
|
64
|
+
".tiff": "images",
|
|
65
|
+
".tif": "images",
|
|
66
|
+
".avif": "images",
|
|
67
|
+
".woff": "fonts",
|
|
68
|
+
".woff2": "fonts",
|
|
69
|
+
".ttf": "fonts",
|
|
70
|
+
".otf": "fonts",
|
|
71
|
+
".eot": "fonts",
|
|
72
|
+
".mp3": "audio",
|
|
73
|
+
".wav": "audio",
|
|
74
|
+
".ogg": "audio",
|
|
75
|
+
".aac": "audio",
|
|
76
|
+
".flac": "audio",
|
|
77
|
+
".m4a": "audio",
|
|
78
|
+
".mp4": "video",
|
|
79
|
+
".webm": "video",
|
|
80
|
+
".ogv": "video",
|
|
81
|
+
".mov": "video",
|
|
82
|
+
".avi": "video",
|
|
83
|
+
".wmv": "video",
|
|
84
|
+
".mkv": "video"
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
class AssetOrganizer {
|
|
88
|
+
options;
|
|
89
|
+
hashCache;
|
|
90
|
+
urlToPath;
|
|
91
|
+
filenameVersions;
|
|
92
|
+
constructor(options) {
|
|
93
|
+
this.options = options;
|
|
94
|
+
this.hashCache = new Map;
|
|
95
|
+
this.urlToPath = new Map;
|
|
96
|
+
this.filenameVersions = new Map;
|
|
97
|
+
}
|
|
98
|
+
computeHash(content) {
|
|
99
|
+
return createHash("md5").update(content).digest("hex");
|
|
100
|
+
}
|
|
101
|
+
getAssetFolder(mimeType, url) {
|
|
102
|
+
const folders = {
|
|
103
|
+
...DEFAULT_ASSET_FOLDERS,
|
|
104
|
+
...this.options.assetFolders
|
|
105
|
+
};
|
|
106
|
+
if (mimeType) {
|
|
107
|
+
const normalizedMime = mimeType.split(";")[0].trim().toLowerCase();
|
|
108
|
+
const assetType = MIME_TO_ASSET[normalizedMime];
|
|
109
|
+
if (assetType) {
|
|
110
|
+
return folders[assetType];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const urlPath = new URL(url).pathname;
|
|
115
|
+
const ext = extname(urlPath).toLowerCase();
|
|
116
|
+
const assetType = EXT_TO_ASSET[ext];
|
|
117
|
+
if (assetType) {
|
|
118
|
+
return folders[assetType];
|
|
119
|
+
}
|
|
120
|
+
} catch {}
|
|
121
|
+
return folders.other;
|
|
122
|
+
}
|
|
123
|
+
shouldOrganize(mimeType, url) {
|
|
124
|
+
if (!this.options.organizeAssets) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (mimeType) {
|
|
128
|
+
const normalizedMime = mimeType.split(";")[0].trim().toLowerCase();
|
|
129
|
+
if (normalizedMime === "text/html" || normalizedMime === "application/xhtml+xml" || normalizedMime === "text/xml" || normalizedMime === "application/xml") {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const urlPath = new URL(url).pathname;
|
|
135
|
+
const ext = extname(urlPath).toLowerCase();
|
|
136
|
+
if (ext === ".html" || ext === ".htm" || ext === ".xhtml") {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
getOrganizedPath(url, content, mimeType) {
|
|
143
|
+
const existingPath = this.urlToPath.get(url);
|
|
144
|
+
if (existingPath) {
|
|
145
|
+
return {
|
|
146
|
+
path: existingPath,
|
|
147
|
+
isDuplicate: false
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const hash = this.computeHash(content);
|
|
151
|
+
const existing = this.hashCache.get(hash);
|
|
152
|
+
if (existing) {
|
|
153
|
+
this.urlToPath.set(url, existing.path);
|
|
154
|
+
return {
|
|
155
|
+
path: existing.path,
|
|
156
|
+
isDuplicate: true,
|
|
157
|
+
originalUrl: existing.originalUrl
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const folder = this.getAssetFolder(mimeType, url);
|
|
161
|
+
let filename = this.getFilename(url);
|
|
162
|
+
const basePath = join(folder, filename);
|
|
163
|
+
const finalPath = this.resolveCollision(basePath, hash);
|
|
164
|
+
this.hashCache.set(hash, {
|
|
165
|
+
hash,
|
|
166
|
+
path: finalPath,
|
|
167
|
+
originalUrl: url
|
|
168
|
+
});
|
|
169
|
+
this.urlToPath.set(url, finalPath);
|
|
170
|
+
return {
|
|
171
|
+
path: finalPath,
|
|
172
|
+
isDuplicate: false
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
getFilename(url) {
|
|
176
|
+
try {
|
|
177
|
+
const urlObj = new URL(url);
|
|
178
|
+
let pathname = urlObj.pathname;
|
|
179
|
+
if (pathname.endsWith("/")) {
|
|
180
|
+
pathname = pathname.slice(0, -1);
|
|
181
|
+
}
|
|
182
|
+
let filename = basename(pathname);
|
|
183
|
+
if (!filename || filename === "/") {
|
|
184
|
+
filename = "index";
|
|
185
|
+
}
|
|
186
|
+
const queryIndex = filename.indexOf("?");
|
|
187
|
+
if (queryIndex > 0) {
|
|
188
|
+
filename = filename.slice(0, queryIndex);
|
|
189
|
+
}
|
|
190
|
+
return filename;
|
|
191
|
+
} catch {
|
|
192
|
+
return "asset";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
resolveCollision(basePath, hash) {
|
|
196
|
+
const existingVersion = this.filenameVersions.get(basePath);
|
|
197
|
+
if (existingVersion === undefined) {
|
|
198
|
+
this.filenameVersions.set(basePath, 1);
|
|
199
|
+
return basePath;
|
|
200
|
+
}
|
|
201
|
+
const ext = extname(basePath);
|
|
202
|
+
const nameWithoutExt = basePath.slice(0, -ext.length || undefined);
|
|
203
|
+
const newVersion = existingVersion + 1;
|
|
204
|
+
this.filenameVersions.set(basePath, newVersion);
|
|
205
|
+
const versionedPath = `${nameWithoutExt}_v${newVersion}${ext}`;
|
|
206
|
+
return versionedPath;
|
|
207
|
+
}
|
|
208
|
+
getPathForUrl(url) {
|
|
209
|
+
return this.urlToPath.get(url);
|
|
210
|
+
}
|
|
211
|
+
getUrlMappings() {
|
|
212
|
+
return new Map(this.urlToPath);
|
|
213
|
+
}
|
|
214
|
+
clear() {
|
|
215
|
+
this.hashCache.clear();
|
|
216
|
+
this.urlToPath.clear();
|
|
217
|
+
this.filenameVersions.clear();
|
|
218
|
+
}
|
|
219
|
+
getStats() {
|
|
220
|
+
return {
|
|
221
|
+
uniqueFiles: this.hashCache.size,
|
|
222
|
+
duplicatesFound: this.urlToPath.size - this.hashCache.size,
|
|
223
|
+
totalUrls: this.urlToPath.size
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
exports.AssetOrganizer = AssetOrganizer;
|
|
229
|
+
exports.default = AssetOrganizer;
|
|
230
|
+
module.exports = Object.assign(AssetOrganizer, exports);
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { extname, basename, join } from "node:path";
|
|
3
|
+
export const DEFAULT_ASSET_FOLDERS = {
|
|
4
|
+
css: "css",
|
|
5
|
+
js: "js",
|
|
6
|
+
images: "images",
|
|
7
|
+
fonts: "fonts",
|
|
8
|
+
audio: "audio",
|
|
9
|
+
video: "video",
|
|
10
|
+
other: "assets"
|
|
11
|
+
};
|
|
12
|
+
const MIME_TO_ASSET = {
|
|
13
|
+
"text/css": "css",
|
|
14
|
+
"application/javascript": "js",
|
|
15
|
+
"application/x-javascript": "js",
|
|
16
|
+
"text/javascript": "js",
|
|
17
|
+
"application/ecmascript": "js",
|
|
18
|
+
"image/jpeg": "images",
|
|
19
|
+
"image/png": "images",
|
|
20
|
+
"image/gif": "images",
|
|
21
|
+
"image/webp": "images",
|
|
22
|
+
"image/svg+xml": "images",
|
|
23
|
+
"image/x-icon": "images",
|
|
24
|
+
"image/vnd.microsoft.icon": "images",
|
|
25
|
+
"image/bmp": "images",
|
|
26
|
+
"image/tiff": "images",
|
|
27
|
+
"image/avif": "images",
|
|
28
|
+
"font/woff": "fonts",
|
|
29
|
+
"font/woff2": "fonts",
|
|
30
|
+
"font/ttf": "fonts",
|
|
31
|
+
"font/otf": "fonts",
|
|
32
|
+
"application/font-woff": "fonts",
|
|
33
|
+
"application/font-woff2": "fonts",
|
|
34
|
+
"application/x-font-ttf": "fonts",
|
|
35
|
+
"application/x-font-otf": "fonts",
|
|
36
|
+
"application/vnd.ms-fontobject": "fonts",
|
|
37
|
+
"audio/mpeg": "audio",
|
|
38
|
+
"audio/mp3": "audio",
|
|
39
|
+
"audio/wav": "audio",
|
|
40
|
+
"audio/ogg": "audio",
|
|
41
|
+
"audio/webm": "audio",
|
|
42
|
+
"audio/aac": "audio",
|
|
43
|
+
"audio/flac": "audio",
|
|
44
|
+
"video/mp4": "video",
|
|
45
|
+
"video/webm": "video",
|
|
46
|
+
"video/ogg": "video",
|
|
47
|
+
"video/quicktime": "video",
|
|
48
|
+
"video/x-msvideo": "video",
|
|
49
|
+
"video/x-ms-wmv": "video"
|
|
50
|
+
};
|
|
51
|
+
const EXT_TO_ASSET = {
|
|
52
|
+
".css": "css",
|
|
53
|
+
".js": "js",
|
|
54
|
+
".mjs": "js",
|
|
55
|
+
".cjs": "js",
|
|
56
|
+
".jpg": "images",
|
|
57
|
+
".jpeg": "images",
|
|
58
|
+
".png": "images",
|
|
59
|
+
".gif": "images",
|
|
60
|
+
".webp": "images",
|
|
61
|
+
".svg": "images",
|
|
62
|
+
".ico": "images",
|
|
63
|
+
".bmp": "images",
|
|
64
|
+
".tiff": "images",
|
|
65
|
+
".tif": "images",
|
|
66
|
+
".avif": "images",
|
|
67
|
+
".woff": "fonts",
|
|
68
|
+
".woff2": "fonts",
|
|
69
|
+
".ttf": "fonts",
|
|
70
|
+
".otf": "fonts",
|
|
71
|
+
".eot": "fonts",
|
|
72
|
+
".mp3": "audio",
|
|
73
|
+
".wav": "audio",
|
|
74
|
+
".ogg": "audio",
|
|
75
|
+
".aac": "audio",
|
|
76
|
+
".flac": "audio",
|
|
77
|
+
".m4a": "audio",
|
|
78
|
+
".mp4": "video",
|
|
79
|
+
".webm": "video",
|
|
80
|
+
".ogv": "video",
|
|
81
|
+
".mov": "video",
|
|
82
|
+
".avi": "video",
|
|
83
|
+
".wmv": "video",
|
|
84
|
+
".mkv": "video"
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export class AssetOrganizer {
|
|
88
|
+
options;
|
|
89
|
+
hashCache;
|
|
90
|
+
urlToPath;
|
|
91
|
+
filenameVersions;
|
|
92
|
+
constructor(options) {
|
|
93
|
+
this.options = options;
|
|
94
|
+
this.hashCache = new Map;
|
|
95
|
+
this.urlToPath = new Map;
|
|
96
|
+
this.filenameVersions = new Map;
|
|
97
|
+
}
|
|
98
|
+
computeHash(content) {
|
|
99
|
+
return createHash("md5").update(content).digest("hex");
|
|
100
|
+
}
|
|
101
|
+
getAssetFolder(mimeType, url) {
|
|
102
|
+
const folders = {
|
|
103
|
+
...DEFAULT_ASSET_FOLDERS,
|
|
104
|
+
...this.options.assetFolders
|
|
105
|
+
};
|
|
106
|
+
if (mimeType) {
|
|
107
|
+
const normalizedMime = mimeType.split(";")[0].trim().toLowerCase();
|
|
108
|
+
const assetType = MIME_TO_ASSET[normalizedMime];
|
|
109
|
+
if (assetType) {
|
|
110
|
+
return folders[assetType];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const urlPath = new URL(url).pathname;
|
|
115
|
+
const ext = extname(urlPath).toLowerCase();
|
|
116
|
+
const assetType = EXT_TO_ASSET[ext];
|
|
117
|
+
if (assetType) {
|
|
118
|
+
return folders[assetType];
|
|
119
|
+
}
|
|
120
|
+
} catch {}
|
|
121
|
+
return folders.other;
|
|
122
|
+
}
|
|
123
|
+
shouldOrganize(mimeType, url) {
|
|
124
|
+
if (!this.options.organizeAssets) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (mimeType) {
|
|
128
|
+
const normalizedMime = mimeType.split(";")[0].trim().toLowerCase();
|
|
129
|
+
if (normalizedMime === "text/html" || normalizedMime === "application/xhtml+xml" || normalizedMime === "text/xml" || normalizedMime === "application/xml") {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const urlPath = new URL(url).pathname;
|
|
135
|
+
const ext = extname(urlPath).toLowerCase();
|
|
136
|
+
if (ext === ".html" || ext === ".htm" || ext === ".xhtml") {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
getOrganizedPath(url, content, mimeType) {
|
|
143
|
+
const existingPath = this.urlToPath.get(url);
|
|
144
|
+
if (existingPath) {
|
|
145
|
+
return {
|
|
146
|
+
path: existingPath,
|
|
147
|
+
isDuplicate: false
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const hash = this.computeHash(content);
|
|
151
|
+
const existing = this.hashCache.get(hash);
|
|
152
|
+
if (existing) {
|
|
153
|
+
this.urlToPath.set(url, existing.path);
|
|
154
|
+
return {
|
|
155
|
+
path: existing.path,
|
|
156
|
+
isDuplicate: true,
|
|
157
|
+
originalUrl: existing.originalUrl
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const folder = this.getAssetFolder(mimeType, url);
|
|
161
|
+
let filename = this.getFilename(url);
|
|
162
|
+
const basePath = join(folder, filename);
|
|
163
|
+
const finalPath = this.resolveCollision(basePath, hash);
|
|
164
|
+
this.hashCache.set(hash, {
|
|
165
|
+
hash,
|
|
166
|
+
path: finalPath,
|
|
167
|
+
originalUrl: url
|
|
168
|
+
});
|
|
169
|
+
this.urlToPath.set(url, finalPath);
|
|
170
|
+
return {
|
|
171
|
+
path: finalPath,
|
|
172
|
+
isDuplicate: false
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
getFilename(url) {
|
|
176
|
+
try {
|
|
177
|
+
const urlObj = new URL(url);
|
|
178
|
+
let pathname = urlObj.pathname;
|
|
179
|
+
if (pathname.endsWith("/")) {
|
|
180
|
+
pathname = pathname.slice(0, -1);
|
|
181
|
+
}
|
|
182
|
+
let filename = basename(pathname);
|
|
183
|
+
if (!filename || filename === "/") {
|
|
184
|
+
filename = "index";
|
|
185
|
+
}
|
|
186
|
+
const queryIndex = filename.indexOf("?");
|
|
187
|
+
if (queryIndex > 0) {
|
|
188
|
+
filename = filename.slice(0, queryIndex);
|
|
189
|
+
}
|
|
190
|
+
return filename;
|
|
191
|
+
} catch {
|
|
192
|
+
return "asset";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
resolveCollision(basePath, hash) {
|
|
196
|
+
const existingVersion = this.filenameVersions.get(basePath);
|
|
197
|
+
if (existingVersion === undefined) {
|
|
198
|
+
this.filenameVersions.set(basePath, 1);
|
|
199
|
+
return basePath;
|
|
200
|
+
}
|
|
201
|
+
const ext = extname(basePath);
|
|
202
|
+
const nameWithoutExt = basePath.slice(0, -ext.length || undefined);
|
|
203
|
+
const newVersion = existingVersion + 1;
|
|
204
|
+
this.filenameVersions.set(basePath, newVersion);
|
|
205
|
+
const versionedPath = `${nameWithoutExt}_v${newVersion}${ext}`;
|
|
206
|
+
return versionedPath;
|
|
207
|
+
}
|
|
208
|
+
getPathForUrl(url) {
|
|
209
|
+
return this.urlToPath.get(url);
|
|
210
|
+
}
|
|
211
|
+
getUrlMappings() {
|
|
212
|
+
return new Map(this.urlToPath);
|
|
213
|
+
}
|
|
214
|
+
clear() {
|
|
215
|
+
this.hashCache.clear();
|
|
216
|
+
this.urlToPath.clear();
|
|
217
|
+
this.filenameVersions.clear();
|
|
218
|
+
}
|
|
219
|
+
getStats() {
|
|
220
|
+
return {
|
|
221
|
+
uniqueFiles: this.hashCache.size,
|
|
222
|
+
duplicatesFound: this.urlToPath.size - this.hashCache.size,
|
|
223
|
+
totalUrls: this.urlToPath.size
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export default AssetOrganizer;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
const { promises: fs } = require("node:fs");
|
|
2
|
+
const { createHash } = require("node:crypto");
|
|
3
|
+
const { join, dirname } = require("node:path");
|
|
4
|
+
const { cwd } = require("node:process");
|
|
5
|
+
|
|
6
|
+
class DownloadCache {
|
|
7
|
+
outputDir;
|
|
8
|
+
baseUrl;
|
|
9
|
+
cacheDir;
|
|
10
|
+
cacheFile;
|
|
11
|
+
data = null;
|
|
12
|
+
dirty = false;
|
|
13
|
+
saveTimeout = null;
|
|
14
|
+
static VERSION = 1;
|
|
15
|
+
static CACHE_DIR = ".rezo-wget";
|
|
16
|
+
constructor(outputDir, baseUrl) {
|
|
17
|
+
this.outputDir = outputDir;
|
|
18
|
+
this.baseUrl = baseUrl;
|
|
19
|
+
this.cacheDir = join(cwd(), DownloadCache.CACHE_DIR);
|
|
20
|
+
const hash = this.generateCacheHash();
|
|
21
|
+
this.cacheFile = join(this.cacheDir, `${hash}.json`);
|
|
22
|
+
}
|
|
23
|
+
generateCacheHash() {
|
|
24
|
+
return createHash("md5").update(this.baseUrl).digest("hex").slice(0, 12);
|
|
25
|
+
}
|
|
26
|
+
urlHash(url) {
|
|
27
|
+
return createHash("md5").update(url).digest("hex");
|
|
28
|
+
}
|
|
29
|
+
async load() {
|
|
30
|
+
try {
|
|
31
|
+
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
32
|
+
const content = await fs.readFile(this.cacheFile, "utf-8");
|
|
33
|
+
const data = JSON.parse(content);
|
|
34
|
+
if (data.version !== DownloadCache.VERSION) {
|
|
35
|
+
this.data = this.createEmptyCache();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.data = data;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
this.data = this.createEmptyCache();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
createEmptyCache() {
|
|
44
|
+
return {
|
|
45
|
+
version: DownloadCache.VERSION,
|
|
46
|
+
created: Date.now(),
|
|
47
|
+
updated: Date.now(),
|
|
48
|
+
configHash: this.generateCacheHash(),
|
|
49
|
+
baseUrl: this.baseUrl,
|
|
50
|
+
entries: {}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async check(url) {
|
|
54
|
+
if (!this.data) {
|
|
55
|
+
await this.load();
|
|
56
|
+
}
|
|
57
|
+
const key = this.urlHash(url);
|
|
58
|
+
const entry = this.data.entries[key];
|
|
59
|
+
if (!entry) {
|
|
60
|
+
return { cached: false, reason: "not-found" };
|
|
61
|
+
}
|
|
62
|
+
for (const filename of entry.filenames) {
|
|
63
|
+
const fullPath = join(this.outputDir, filename);
|
|
64
|
+
try {
|
|
65
|
+
const stat = await fs.stat(fullPath);
|
|
66
|
+
if (stat.size === entry.totalBytes) {
|
|
67
|
+
return {
|
|
68
|
+
cached: true,
|
|
69
|
+
entry,
|
|
70
|
+
filename
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
if (entry.filenames.length > 0) {
|
|
76
|
+
return { cached: false, reason: "file-missing", entry };
|
|
77
|
+
}
|
|
78
|
+
return { cached: false, reason: "size-mismatch", entry };
|
|
79
|
+
}
|
|
80
|
+
get(url) {
|
|
81
|
+
if (!this.data)
|
|
82
|
+
return;
|
|
83
|
+
return this.data.entries[this.urlHash(url)];
|
|
84
|
+
}
|
|
85
|
+
set(url, entry) {
|
|
86
|
+
if (!this.data) {
|
|
87
|
+
this.data = this.createEmptyCache();
|
|
88
|
+
}
|
|
89
|
+
const key = this.urlHash(url);
|
|
90
|
+
const existing = this.data.entries[key];
|
|
91
|
+
this.data.entries[key] = {
|
|
92
|
+
url,
|
|
93
|
+
...entry,
|
|
94
|
+
filenames: existing ? [...new Set([...existing.filenames, ...entry.filenames])] : entry.filenames
|
|
95
|
+
};
|
|
96
|
+
this.data.updated = Date.now();
|
|
97
|
+
this.dirty = true;
|
|
98
|
+
this.scheduleSave();
|
|
99
|
+
}
|
|
100
|
+
addFilename(url, filename) {
|
|
101
|
+
if (!this.data)
|
|
102
|
+
return;
|
|
103
|
+
const key = this.urlHash(url);
|
|
104
|
+
const entry = this.data.entries[key];
|
|
105
|
+
if (entry && !entry.filenames.includes(filename)) {
|
|
106
|
+
entry.filenames.push(filename);
|
|
107
|
+
this.data.updated = Date.now();
|
|
108
|
+
this.dirty = true;
|
|
109
|
+
this.scheduleSave();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
delete(url) {
|
|
113
|
+
if (!this.data)
|
|
114
|
+
return;
|
|
115
|
+
const key = this.urlHash(url);
|
|
116
|
+
if (this.data.entries[key]) {
|
|
117
|
+
delete this.data.entries[key];
|
|
118
|
+
this.data.updated = Date.now();
|
|
119
|
+
this.dirty = true;
|
|
120
|
+
this.scheduleSave();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
has(url) {
|
|
124
|
+
if (!this.data)
|
|
125
|
+
return false;
|
|
126
|
+
return this.urlHash(url) in this.data.entries;
|
|
127
|
+
}
|
|
128
|
+
urls() {
|
|
129
|
+
if (!this.data)
|
|
130
|
+
return [];
|
|
131
|
+
return Object.values(this.data.entries).map((e) => e.url);
|
|
132
|
+
}
|
|
133
|
+
stats() {
|
|
134
|
+
if (!this.data) {
|
|
135
|
+
return { entries: 0, totalBytes: 0, filesCount: 0 };
|
|
136
|
+
}
|
|
137
|
+
const entries = Object.values(this.data.entries);
|
|
138
|
+
return {
|
|
139
|
+
entries: entries.length,
|
|
140
|
+
totalBytes: entries.reduce((sum, e) => sum + e.totalBytes, 0),
|
|
141
|
+
filesCount: entries.reduce((sum, e) => sum + e.filenames.length, 0)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
scheduleSave() {
|
|
145
|
+
if (this.saveTimeout) {
|
|
146
|
+
clearTimeout(this.saveTimeout);
|
|
147
|
+
}
|
|
148
|
+
this.saveTimeout = setTimeout(() => this.save(), 1000);
|
|
149
|
+
}
|
|
150
|
+
async save() {
|
|
151
|
+
if (!this.data || !this.dirty)
|
|
152
|
+
return;
|
|
153
|
+
if (this.saveTimeout) {
|
|
154
|
+
clearTimeout(this.saveTimeout);
|
|
155
|
+
this.saveTimeout = null;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
await fs.mkdir(dirname(this.cacheFile), { recursive: true });
|
|
159
|
+
await fs.writeFile(this.cacheFile, JSON.stringify(this.data, null, 2), "utf-8");
|
|
160
|
+
this.dirty = false;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error("Failed to save download cache:", error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
clear() {
|
|
166
|
+
if (this.data) {
|
|
167
|
+
this.data.entries = {};
|
|
168
|
+
this.data.updated = Date.now();
|
|
169
|
+
this.dirty = true;
|
|
170
|
+
this.scheduleSave();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async cleanup() {
|
|
174
|
+
if (!this.data)
|
|
175
|
+
return 0;
|
|
176
|
+
let removed = 0;
|
|
177
|
+
const entries = Object.entries(this.data.entries);
|
|
178
|
+
for (const [key, entry] of entries) {
|
|
179
|
+
let hasValidFile = false;
|
|
180
|
+
const validFilenames = [];
|
|
181
|
+
for (const filename of entry.filenames) {
|
|
182
|
+
const fullPath = join(this.outputDir, filename);
|
|
183
|
+
try {
|
|
184
|
+
await fs.stat(fullPath);
|
|
185
|
+
validFilenames.push(filename);
|
|
186
|
+
hasValidFile = true;
|
|
187
|
+
} catch {}
|
|
188
|
+
}
|
|
189
|
+
if (!hasValidFile) {
|
|
190
|
+
delete this.data.entries[key];
|
|
191
|
+
removed++;
|
|
192
|
+
} else if (validFilenames.length !== entry.filenames.length) {
|
|
193
|
+
entry.filenames = validFilenames;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (removed > 0) {
|
|
197
|
+
this.data.updated = Date.now();
|
|
198
|
+
this.dirty = true;
|
|
199
|
+
await this.save();
|
|
200
|
+
}
|
|
201
|
+
return removed;
|
|
202
|
+
}
|
|
203
|
+
async destroy() {
|
|
204
|
+
if (this.saveTimeout) {
|
|
205
|
+
clearTimeout(this.saveTimeout);
|
|
206
|
+
this.saveTimeout = null;
|
|
207
|
+
}
|
|
208
|
+
await this.save();
|
|
209
|
+
this.data = null;
|
|
210
|
+
}
|
|
211
|
+
get filePath() {
|
|
212
|
+
return this.cacheFile;
|
|
213
|
+
}
|
|
214
|
+
get dirPath() {
|
|
215
|
+
return this.cacheDir;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
exports.DownloadCache = DownloadCache;
|
|
220
|
+
exports.default = DownloadCache;
|
|
221
|
+
module.exports = Object.assign(DownloadCache, exports);
|