Zero-copy FTP/HTTP Daemon compatible with all POSIX systems
| 1 | /* ══ API LAYER ════════════════════════════════════════════════════════════ |
| 2 | * Centralized fetch wrappers for all backend endpoints. |
| 3 | * ES5 compatible for PS5 browser. |
| 4 | * ═════════════════════════════════════════════════════════════════════════ */ |
| 5 | |
| 6 | var ZFTPD = ZFTPD || {}; |
| 7 | |
| 8 | (function (Z) { |
| 9 | 'use strict'; |
| 10 | |
| 11 | var api = {}; |
| 12 | |
| 13 | /* ── Internal helpers ── */ |
| 14 | function get(url) { |
| 15 | return fetch(url).then(function (r) { |
| 16 | if (!r.ok) throw new Error('HTTP ' + r.status); |
| 17 | return r.json(); |
| 18 | }); |
| 19 | } |
| 20 | |
| 21 | function post(url, body) { |
| 22 | var opts = { |
| 23 | method: 'POST', |
| 24 | headers: { 'X-CSRF-Token': Z.csrf() } |
| 25 | }; |
| 26 | if (body !== undefined) { |
| 27 | opts.headers['Content-Type'] = 'application/json'; |
| 28 | opts.body = JSON.stringify(body); |
| 29 | } |
| 30 | return fetch(url, opts).then(function (r) { |
| 31 | if (!r.ok) throw new Error('HTTP ' + r.status); |
| 32 | return r.json(); |
| 33 | }); |
| 34 | } |
| 35 | |
| 36 | /* ── Directory listing ── */ |
| 37 | api.list = function (path) { |
| 38 | return get('/api/list?path=' + Z.E(path)); |
| 39 | }; |
| 40 | |
| 41 | /* ── Directory size (lazy) ── */ |
| 42 | api.dirsize = function (path) { |
| 43 | return get('/api/dirsize?path=' + Z.E(path)); |
| 44 | }; |
| 45 | |
| 46 | /* ── Stats ── */ |
| 47 | api.stats = function (path) { |
| 48 | return get('/api/stats?path=' + Z.E(path)); |
| 49 | }; |
| 50 | |
| 51 | api.statsRam = function () { |
| 52 | return get('/api/stats/ram'); |
| 53 | }; |
| 54 | |
| 55 | api.statsSystem = function () { |
| 56 | return get('/api/stats/system'); |
| 57 | }; |
| 58 | |
| 59 | /* ── Disk ── */ |
| 60 | api.diskInfo = function () { |
| 61 | return get('/api/disk/info'); |
| 62 | }; |
| 63 | |
| 64 | api.diskTree = function (path) { |
| 65 | return get('/api/disk/tree?path=' + Z.E(path)); |
| 66 | }; |
| 67 | |
| 68 | /* ── Processes ── */ |
| 69 | api.processes = function () { |
| 70 | return get('/api/processes'); |
| 71 | }; |
| 72 | |
| 73 | api.processKill = function (pid) { |
| 74 | return post('/api/process/kill', { pid: pid }); |
| 75 | }; |
| 76 | |
| 77 | /* ── File operations (require ENABLE_WEB_UPLOAD) ── */ |
| 78 | api.createFile = function (dirPath, name) { |
| 79 | return fetch('/api/create_file?path=' + Z.E(dirPath) + '&name=' + Z.E(name), { |
| 80 | method: 'POST', |
| 81 | headers: { 'Content-Type': 'text/plain', 'X-CSRF-Token': Z.csrf() }, |
| 82 | body: '' |
| 83 | }).then(function (r) { |
| 84 | if (!r.ok) throw new Error('HTTP ' + r.status); |
| 85 | return r.json(); |
| 86 | }); |
| 87 | }; |
| 88 | |
| 89 | api.mkdir = function (dirPath, name) { |
| 90 | return post('/api/mkdir?path=' + Z.E(dirPath) + '&name=' + Z.E(name)); |
| 91 | }; |
| 92 | |
| 93 | api.del = function (path, recursive) { |
| 94 | var url = '/api/delete?path=' + Z.E(path); |
| 95 | if (recursive) url += '&recursive=1'; |
| 96 | return post(url); |
| 97 | }; |
| 98 | |
| 99 | api.rename = function (path, newName) { |
| 100 | return post('/api/rename?path=' + Z.E(path) + '&name=' + Z.E(newName)); |
| 101 | }; |
| 102 | |
| 103 | api.copy = function (srcPath, dstDir, totalSize) { |
| 104 | var url = '/api/copy?path=' + Z.E(srcPath) + '&dst=' + Z.E(dstDir); |
| 105 | if (totalSize) url += '&totalsize=' + totalSize; |
| 106 | return post(url); |
| 107 | }; |
| 108 | |
| 109 | api.copyProgress = function () { |
| 110 | return get('/api/copy_progress'); |
| 111 | }; |
| 112 | |
| 113 | api.copyPause = function () { |
| 114 | return post('/api/copy_pause'); |
| 115 | }; |
| 116 | |
| 117 | api.copyCancel = function () { |
| 118 | return post('/api/copy_cancel'); |
| 119 | }; |
| 120 | |
| 121 | /* ── Network reset ── */ |
| 122 | api.networkReset = function () { |
| 123 | return post('/api/network/reset'); |
| 124 | }; |
| 125 | |
| 126 | /* ── Upload (XMLHttpRequest for progress tracking) ── */ |
| 127 | api.upload = function (dirPath, file, onProgress) { |
| 128 | return new Promise(function (resolve, reject) { |
| 129 | var xhr = new XMLHttpRequest(); |
| 130 | xhr.open('POST', '/api/upload?path=' + Z.E(dirPath) + '&name=' + Z.E(file.name), true); |
| 131 | var token = Z.csrf(); |
| 132 | if (token) xhr.setRequestHeader('X-CSRF-Token', token); |
| 133 | xhr.upload.onprogress = function (e) { |
| 134 | if (e.lengthComputable && onProgress) { |
| 135 | onProgress(Math.floor(e.loaded / e.total * 100), e.loaded, e.total); |
| 136 | } |
| 137 | }; |
| 138 | xhr.onload = function () { |
| 139 | if (xhr.status >= 200 && xhr.status < 300) resolve(xhr); |
| 140 | else reject(new Error('HTTP ' + xhr.status)); |
| 141 | }; |
| 142 | xhr.onerror = function () { reject(new Error('Network error')); }; |
| 143 | xhr.send(file); |
| 144 | /* Return xhr handle for cancellation */ |
| 145 | resolve._xhr = xhr; |
| 146 | }); |
| 147 | }; |
| 148 | |
| 149 | /* ── Download URL ── */ |
| 150 | api.downloadUrl = function (path) { |
| 151 | return '/api/download?path=' + Z.E(path); |
| 152 | }; |
| 153 | |
| 154 | /* ── Game metadata (Phase 4 — stub for now) ── */ |
| 155 | api.gameMeta = function (path) { |
| 156 | return get('/api/game/meta?path=' + Z.E(path)); |
| 157 | }; |
| 158 | |
| 159 | api.gameIconUrl = function (path) { |
| 160 | return '/api/game/icon?path=' + Z.E(path); |
| 161 | }; |
| 162 | |
| 163 | /* ── Games management ── */ |
| 164 | api.gamesInstalled = function () { |
| 165 | return get('/api/admin/games/installed'); |
| 166 | }; |
| 167 | |
| 168 | api.gameInstalledIconUrl = function (id, path) { |
| 169 | var u = '/api/admin/games/icon?id=' + Z.E(id || ''); |
| 170 | if (path) u += '&path=' + Z.E(path); |
| 171 | return u; |
| 172 | }; |
| 173 | |
| 174 | api.gamesRepairVisibility = function (id) { |
| 175 | var u = '/api/admin/games/repair_visibility'; |
| 176 | if (id) u += '?id=' + Z.E(id); |
| 177 | return post(u); |
| 178 | }; |
| 179 | |
| 180 | api.gameLaunch = function (id, path) { |
| 181 | if (id) { |
| 182 | return get('/api/admin/launch?id=' + Z.E(id)); |
| 183 | } |
| 184 | return get('/api/admin/launch?path=' + Z.E(path || '')); |
| 185 | }; |
| 186 | |
| 187 | api.gameUninstall = function (id) { |
| 188 | return post('/api/admin/games/uninstall?id=' + Z.E(id || '')); |
| 189 | }; |
| 190 | |
| 191 | api.gameInstall = function (path) { |
| 192 | return post('/api/admin/games/install?path=' + Z.E(path || '')); |
| 193 | }; |
| 194 | |
| 195 | api.gameReinstall = function (path) { |
| 196 | return post('/api/admin/games/reinstall?path=' + Z.E(path || '')); |
| 197 | }; |
| 198 | |
| 199 | api.gameInstallStatus = function () { |
| 200 | return get('/api/admin/games/install_status'); |
| 201 | }; |
| 202 | |
| 203 | /* ── File copy cancel (Phase 5 — stub) ── */ |
| 204 | api.copyCancel = function () { |
| 205 | return post('/api/copy_cancel'); |
| 206 | }; |
| 207 | |
| 208 | /* ── Archive extraction ── */ |
| 209 | api.extract = function (archivePath, dstDir) { |
| 210 | return post('/api/extract?path=' + Z.E(archivePath) + '&dst=' + Z.E(dstDir)); |
| 211 | }; |
| 212 | |
| 213 | api.extractProgress = function () { |
| 214 | return get('/api/extract_progress'); |
| 215 | }; |
| 216 | |
| 217 | api.extractCancel = function () { |
| 218 | return post('/api/extract_cancel'); |
| 219 | }; |
| 220 | |
| 221 | /* ── Download Manager (Phase 6 — stub for now) ── */ |
| 222 | api.downloadStart = function (url, dst) { |
| 223 | return post('/api/download/start', { url: url, dst: dst }); |
| 224 | }; |
| 225 | |
| 226 | api.downloadStatus = function () { |
| 227 | return get('/api/download/status'); |
| 228 | }; |
| 229 | |
| 230 | api.downloadPause = function (id) { |
| 231 | return post('/api/download/pause', { id: id }); |
| 232 | }; |
| 233 | |
| 234 | api.downloadCancel = function (id) { |
| 235 | return post('/api/download/cancel', { id: id }); |
| 236 | }; |
| 237 | |
| 238 | Z.api = api; |
| 239 | |
| 240 | })(ZFTPD); |
| 241 |