pinokiod 8.0.17 → 8.0.19
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/kernel/bin/conda.js +1 -21
- package/kernel/bin/index.js +0 -15
- package/kernel/path_removal.js +51 -50
- package/kernel/windows_restart_manager.js +4 -38
- package/package.json +1 -1
- package/server/index.js +13 -86
- package/server/public/common.js +27 -39
- package/server/public/style.css +3 -26
- package/server/views/app.ejs +154 -26
- package/server/views/download.ejs +1 -7
- package/server/views/editor.ejs +1 -7
- package/server/views/env_editor.ejs +1 -7
- package/server/views/file_explorer.ejs +1 -7
- package/server/views/index.ejs +1 -7
- package/server/views/install.ejs +2 -21
- package/server/views/net.ejs +1 -7
- package/server/views/partials/home_server_popover.ejs +3 -1
- package/server/views/partials/home_server_popover_assets.ejs +24 -22
- package/server/views/settings.ejs +2 -14
- package/server/views/terminal.ejs +1 -7
- package/test/conda-bin.test.js +0 -31
- package/test/home-server-popover-ui.test.js +109 -0
- package/test/launch-settings-ui.test.js +52 -19
- package/test/path-removal-ui.test.js +56 -27
- package/test/path-removal.test.js +203 -121
- package/test/shell-conda-runtime-guard.test.js +8 -2
- package/test/startup-git-index-route.test.js +0 -52
- package/test/windows-path-removal.integration.test.js +2 -12
package/kernel/bin/conda.js
CHANGED
|
@@ -254,27 +254,7 @@ report_errors: false`)
|
|
|
254
254
|
}
|
|
255
255
|
async removeInstallPath(target, ondata) {
|
|
256
256
|
try {
|
|
257
|
-
|
|
258
|
-
if (error && error.code === "ENOENT") {
|
|
259
|
-
return null
|
|
260
|
-
}
|
|
261
|
-
throw error
|
|
262
|
-
})
|
|
263
|
-
if (!stat) {
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
if (stat.isSymbolicLink()) {
|
|
267
|
-
await removePath(target, {
|
|
268
|
-
force: true,
|
|
269
|
-
operation: "Updating Miniforge",
|
|
270
|
-
})
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
await removePath(target, {
|
|
274
|
-
recursive: true,
|
|
275
|
-
force: true,
|
|
276
|
-
operation: "Updating Miniforge",
|
|
277
|
-
})
|
|
257
|
+
await removePath(target)
|
|
278
258
|
} catch (error) {
|
|
279
259
|
if (isPathRemovalBlockedError(error)) {
|
|
280
260
|
if (typeof ondata === "function") {
|
package/kernel/bin/index.js
CHANGED
|
@@ -715,22 +715,7 @@ class Bin {
|
|
|
715
715
|
let res = await Util.filepicker(req, ondata, this.kernel)
|
|
716
716
|
return res
|
|
717
717
|
}
|
|
718
|
-
async runInstallExclusive(task) {
|
|
719
|
-
const previous = this.installQueue || Promise.resolve()
|
|
720
|
-
let release
|
|
721
|
-
const current = new Promise((resolve) => { release = resolve })
|
|
722
|
-
this.installQueue = previous.then(() => current)
|
|
723
|
-
await previous
|
|
724
|
-
try {
|
|
725
|
-
return await task()
|
|
726
|
-
} finally {
|
|
727
|
-
release()
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
718
|
async install(req, ondata) {
|
|
731
|
-
return await this.runInstallExclusive(() => this.installRequirements(req, ondata))
|
|
732
|
-
}
|
|
733
|
-
async installRequirements(req, ondata) {
|
|
734
719
|
/*
|
|
735
720
|
req.params := {
|
|
736
721
|
client: {
|
package/kernel/path_removal.js
CHANGED
|
@@ -3,7 +3,6 @@ const path = require("path")
|
|
|
3
3
|
const { inspectWindowsFileLocks } = require("./windows_restart_manager")
|
|
4
4
|
|
|
5
5
|
const PATH_REMOVAL_BLOCKED_CODE = "PINOKIO_PATH_REMOVE_BLOCKED"
|
|
6
|
-
const PATH_REMOVAL_PENDING_CODE = "PINOKIO_PATH_REMOVE_PENDING"
|
|
7
6
|
const REMAINING_PREVIEW_LIMIT = 20
|
|
8
7
|
|
|
9
8
|
class PathRemovalBlockedError extends Error {
|
|
@@ -16,21 +15,17 @@ class PathRemovalBlockedError extends Error {
|
|
|
16
15
|
this.name = "PathRemovalBlockedError"
|
|
17
16
|
this.code = PATH_REMOVAL_BLOCKED_CODE
|
|
18
17
|
this.target = details.target
|
|
19
|
-
this.operation = details.operation || "Removing files"
|
|
20
18
|
this.causeCode = cause && cause.code ? cause.code : ""
|
|
21
19
|
const remaining = Array.isArray(details.remaining) ? details.remaining : []
|
|
22
20
|
this.remaining = remaining.slice(0, REMAINING_PREVIEW_LIMIT)
|
|
23
21
|
this.remainingCount = remaining.length
|
|
24
22
|
this.blockers = blockers
|
|
25
|
-
this.cause = cause
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
toJSON() {
|
|
29
26
|
return {
|
|
30
27
|
code: this.code,
|
|
31
|
-
message: this.message,
|
|
32
28
|
target: this.target,
|
|
33
|
-
operation: this.operation,
|
|
34
29
|
causeCode: this.causeCode,
|
|
35
30
|
remaining: this.remaining,
|
|
36
31
|
remainingCount: this.remainingCount,
|
|
@@ -39,21 +34,25 @@ class PathRemovalBlockedError extends Error {
|
|
|
39
34
|
}
|
|
40
35
|
}
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.cause = cause
|
|
48
|
-
}
|
|
37
|
+
const isPathRemovalBlockedError = (error) => !!(error && error.code === PATH_REMOVAL_BLOCKED_CODE)
|
|
38
|
+
|
|
39
|
+
const isInside = (target, candidate) => {
|
|
40
|
+
const relative = path.relative(target, candidate)
|
|
41
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative))
|
|
49
42
|
}
|
|
50
43
|
|
|
51
|
-
const
|
|
52
|
-
|
|
44
|
+
const pathExists = async (target, fsPromises) => {
|
|
45
|
+
try {
|
|
46
|
+
await fsPromises.lstat(target)
|
|
47
|
+
return true
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return !error || error.code !== "ENOENT"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
53
52
|
|
|
54
53
|
const collectRemaining = async (target, fsPromises = fs.promises) => {
|
|
55
|
-
const
|
|
56
|
-
const
|
|
54
|
+
const remaining = []
|
|
55
|
+
const regularFiles = []
|
|
57
56
|
const pending = [path.resolve(target)]
|
|
58
57
|
|
|
59
58
|
while (pending.length > 0) {
|
|
@@ -65,28 +64,40 @@ const collectRemaining = async (target, fsPromises = fs.promises) => {
|
|
|
65
64
|
if (error && error.code === "ENOENT") {
|
|
66
65
|
continue
|
|
67
66
|
}
|
|
68
|
-
|
|
67
|
+
remaining.push(current)
|
|
69
68
|
continue
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
if (!stat.isDirectory()) {
|
|
72
|
+
remaining.push(current)
|
|
73
|
+
if (stat.isFile()) {
|
|
74
|
+
regularFiles.push(current)
|
|
75
|
+
}
|
|
75
76
|
continue
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
let entries
|
|
79
80
|
try {
|
|
80
|
-
entries = await fsPromises.readdir(current
|
|
81
|
-
} catch (
|
|
81
|
+
entries = await fsPromises.readdir(current)
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error.code !== "ENOENT") {
|
|
84
|
+
remaining.push(current)
|
|
85
|
+
}
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
if (entries.length === 0) {
|
|
89
|
+
remaining.push(current)
|
|
82
90
|
continue
|
|
83
91
|
}
|
|
84
92
|
for (const entry of entries) {
|
|
85
|
-
|
|
93
|
+
const child = path.resolve(current, entry)
|
|
94
|
+
if (isInside(target, child)) {
|
|
95
|
+
pending.push(child)
|
|
96
|
+
}
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
99
|
|
|
89
|
-
return {
|
|
100
|
+
return { remaining, regularFiles }
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
const createPathRemover = (dependencies = {}) => {
|
|
@@ -94,44 +105,36 @@ const createPathRemover = (dependencies = {}) => {
|
|
|
94
105
|
const platform = dependencies.platform || process.platform
|
|
95
106
|
const inspectLocks = dependencies.inspectLocks || inspectWindowsFileLocks
|
|
96
107
|
|
|
97
|
-
return async (target
|
|
108
|
+
return async (target) => {
|
|
98
109
|
const resolvedTarget = path.resolve(target)
|
|
99
|
-
const {
|
|
100
|
-
operation = "Removing files",
|
|
101
|
-
diagnose = true,
|
|
102
|
-
...rmOptions
|
|
103
|
-
} = options
|
|
104
110
|
|
|
105
111
|
try {
|
|
106
|
-
await fsPromises.rm(resolvedTarget,
|
|
107
|
-
return { removed: true, target: resolvedTarget }
|
|
112
|
+
await fsPromises.rm(resolvedTarget, { recursive: true, force: true })
|
|
108
113
|
} catch (error) {
|
|
109
114
|
if (platform !== "win32") {
|
|
110
115
|
throw error
|
|
111
116
|
}
|
|
112
|
-
if (!diagnose) {
|
|
113
|
-
throw new PathRemovalPendingError(resolvedTarget, error)
|
|
114
|
-
}
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
if (!(await pathExists(resolvedTarget, fsPromises))) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
const diagnosis = await collectRemaining(resolvedTarget, fsPromises)
|
|
122
|
+
if (!(await pathExists(resolvedTarget, fsPromises))) {
|
|
123
|
+
return
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
let
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
console.warn("[path removal] Restart Manager inspection failed", inspectionError)
|
|
126
|
+
let blockers = []
|
|
127
|
+
if (diagnosis.regularFiles.length > 0) {
|
|
128
|
+
blockers = await inspectLocks(diagnosis.regularFiles).catch(() => [])
|
|
129
|
+
if (!Array.isArray(blockers)) blockers = []
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
const
|
|
132
|
+
const remaining = diagnosis.remaining.length > 0 ? diagnosis.remaining : [resolvedTarget]
|
|
129
133
|
|
|
130
134
|
throw new PathRemovalBlockedError({
|
|
131
135
|
target: resolvedTarget,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
blockers: inspection.blockers,
|
|
136
|
+
remaining,
|
|
137
|
+
blockers,
|
|
135
138
|
}, error)
|
|
136
139
|
}
|
|
137
140
|
}
|
|
@@ -141,10 +144,8 @@ const removePath = createPathRemover()
|
|
|
141
144
|
|
|
142
145
|
module.exports = {
|
|
143
146
|
PATH_REMOVAL_BLOCKED_CODE,
|
|
144
|
-
|
|
145
|
-
collectRemaining,
|
|
147
|
+
PathRemovalBlockedError,
|
|
146
148
|
createPathRemover,
|
|
147
149
|
isPathRemovalBlockedError,
|
|
148
|
-
isPathRemovalPendingError,
|
|
149
150
|
removePath,
|
|
150
151
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const path = require("path")
|
|
2
|
-
|
|
3
1
|
const ERROR_SUCCESS = 0
|
|
4
2
|
const ERROR_MORE_DATA = 234
|
|
5
3
|
const CCH_RM_SESSION_KEY = 32
|
|
@@ -16,7 +14,6 @@ const initialize = () => {
|
|
|
16
14
|
|
|
17
15
|
const koffi = require("koffi")
|
|
18
16
|
const library = koffi.load("rstrtmgr.dll")
|
|
19
|
-
const kernel32 = koffi.load("kernel32.dll")
|
|
20
17
|
const fileTime = koffi.struct({
|
|
21
18
|
dwLowDateTime: "uint32_t",
|
|
22
19
|
dwHighDateTime: "uint32_t",
|
|
@@ -42,34 +39,10 @@ const initialize = () => {
|
|
|
42
39
|
registerResources: library.func("uint32_t __stdcall RmRegisterResources(uint32_t session, uint32_t fileCount, const str16 *files, uint32_t applicationCount, const void *applications, uint32_t serviceCount, const str16 *services)"),
|
|
43
40
|
getList: library.func("uint32_t __stdcall RmGetList(uint32_t session, _Out_ uint32_t *needed, _Inout_ uint32_t *count, _Out_ void *processes, _Out_ uint32_t *rebootReasons)"),
|
|
44
41
|
endSession: library.func("uint32_t __stdcall RmEndSession(uint32_t session)"),
|
|
45
|
-
openProcess: kernel32.func("void * __stdcall OpenProcess(uint32_t access, int32_t inheritHandle, uint32_t processId)"),
|
|
46
|
-
queryFullProcessImageName: kernel32.func("int32_t __stdcall QueryFullProcessImageNameW(void *process, uint32_t flags, _Out_ void *filename, _Inout_ uint32_t *size)"),
|
|
47
|
-
closeHandle: kernel32.func("int32_t __stdcall CloseHandle(void *handle)"),
|
|
48
42
|
}
|
|
49
43
|
return bindings
|
|
50
44
|
}
|
|
51
45
|
|
|
52
|
-
const queryProcessImage = (api, pid) => {
|
|
53
|
-
const PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
|
54
|
-
const handle = api.openProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid)
|
|
55
|
-
if (!handle) {
|
|
56
|
-
return ""
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const capacity = 32768
|
|
60
|
-
const buffer = Buffer.alloc(capacity * 2)
|
|
61
|
-
const size = [capacity]
|
|
62
|
-
if (!api.queryFullProcessImageName(handle, 0, buffer, size)) {
|
|
63
|
-
return ""
|
|
64
|
-
}
|
|
65
|
-
return String(api.koffi.decode.string16(buffer) || "")
|
|
66
|
-
} catch (_) {
|
|
67
|
-
return ""
|
|
68
|
-
} finally {
|
|
69
|
-
api.closeHandle(handle)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
46
|
const normalizeProcessInfo = (entry) => {
|
|
74
47
|
const uniqueProcess = entry && entry.Process ? entry.Process : {}
|
|
75
48
|
const pid = Number(uniqueProcess.dwProcessId) || 0
|
|
@@ -82,13 +55,13 @@ const normalizeProcessInfo = (entry) => {
|
|
|
82
55
|
|
|
83
56
|
const inspectWindowsFileLocks = async (files) => {
|
|
84
57
|
if (process.platform !== "win32") {
|
|
85
|
-
return
|
|
58
|
+
return []
|
|
86
59
|
}
|
|
87
60
|
|
|
88
61
|
const resources = Array.from(new Set((Array.isArray(files) ? files : [])
|
|
89
62
|
.filter((file) => typeof file === "string" && file.length > 0)))
|
|
90
63
|
if (resources.length === 0) {
|
|
91
|
-
return
|
|
64
|
+
return []
|
|
92
65
|
}
|
|
93
66
|
|
|
94
67
|
const api = initialize()
|
|
@@ -110,7 +83,7 @@ const inspectWindowsFileLocks = async (files) => {
|
|
|
110
83
|
let rebootReasons = [0]
|
|
111
84
|
status = api.getList(session[0], needed, count, null, rebootReasons)
|
|
112
85
|
if (status === ERROR_SUCCESS && needed[0] === 0) {
|
|
113
|
-
return
|
|
86
|
+
return []
|
|
114
87
|
}
|
|
115
88
|
if (status !== ERROR_MORE_DATA && status !== ERROR_SUCCESS) {
|
|
116
89
|
throw new Error(`RmGetList failed with Windows error ${status}`)
|
|
@@ -128,14 +101,7 @@ const inspectWindowsFileLocks = async (files) => {
|
|
|
128
101
|
const blockers = entries
|
|
129
102
|
.map(normalizeProcessInfo)
|
|
130
103
|
.filter((entry) => entry.pid > 0)
|
|
131
|
-
|
|
132
|
-
const imagePath = queryProcessImage(api, entry.pid)
|
|
133
|
-
return {
|
|
134
|
-
...entry,
|
|
135
|
-
executable: imagePath ? path.win32.basename(imagePath) : "",
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
return { blockers }
|
|
104
|
+
return blockers
|
|
139
105
|
}
|
|
140
106
|
if (status !== ERROR_MORE_DATA) {
|
|
141
107
|
throw new Error(`RmGetList failed with Windows error ${status}`)
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -34,7 +34,7 @@ const Git = require("../kernel/git")
|
|
|
34
34
|
const TerminalApi = require('../kernel/api/terminal')
|
|
35
35
|
const PluginSources = require("../kernel/plugin_sources")
|
|
36
36
|
const ManagedSkills = require("../kernel/managed_skills")
|
|
37
|
-
const { isPathRemovalBlockedError,
|
|
37
|
+
const { isPathRemovalBlockedError, removePath } = require("../kernel/path_removal")
|
|
38
38
|
|
|
39
39
|
const git = require('isomorphic-git')
|
|
40
40
|
const http = require('isomorphic-git/http/node')
|
|
@@ -63,40 +63,6 @@ const ex = fn => (req, res, next) => {
|
|
|
63
63
|
Promise.resolve(fn(req, res, next)).catch(next);
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
const isPathInsideRoot = (candidatePath, rootPath) => {
|
|
67
|
-
const relative = path.relative(rootPath, candidatePath)
|
|
68
|
-
return relative === "" || (
|
|
69
|
-
relative !== ".."
|
|
70
|
-
&& !relative.startsWith(`..${path.sep}`)
|
|
71
|
-
&& !path.isAbsolute(relative)
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const resolveAppRemovalPath = async (apiRoot, name) => {
|
|
76
|
-
if (typeof name !== "string" || !name || name.includes("\0")) {
|
|
77
|
-
return null
|
|
78
|
-
}
|
|
79
|
-
const root = path.resolve(apiRoot)
|
|
80
|
-
const target = path.resolve(root, name)
|
|
81
|
-
if (target === root || !isPathInsideRoot(target, root)) {
|
|
82
|
-
return null
|
|
83
|
-
}
|
|
84
|
-
const stat = await fs.promises.lstat(target).catch((error) => {
|
|
85
|
-
if (error && error.code === "ENOENT") return null
|
|
86
|
-
throw error
|
|
87
|
-
})
|
|
88
|
-
if (stat && !stat.isSymbolicLink()) {
|
|
89
|
-
const [realRoot, realTarget] = await Promise.all([
|
|
90
|
-
fs.promises.realpath(root),
|
|
91
|
-
fs.promises.realpath(target),
|
|
92
|
-
])
|
|
93
|
-
if (realTarget === realRoot || !isPathInsideRoot(realTarget, realRoot)) {
|
|
94
|
-
return null
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return target
|
|
98
|
-
}
|
|
99
|
-
|
|
100
66
|
|
|
101
67
|
|
|
102
68
|
|
|
@@ -16297,50 +16263,24 @@ class Server {
|
|
|
16297
16263
|
console.timeEnd("/p/" + req.params.name + "/" + d)
|
|
16298
16264
|
}))
|
|
16299
16265
|
this.app.post("/pinokio/delete", ex(async (req, res) => {
|
|
16300
|
-
if (!privacyFilterCache.isInstallRequestAllowed(req)) {
|
|
16301
|
-
res.status(403).json({ error: "Cross-site deletion is not allowed" })
|
|
16302
|
-
return
|
|
16303
|
-
}
|
|
16304
16266
|
try {
|
|
16305
16267
|
if (req.body.type === 'conda-runtime') {
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
16310
|
-
recursive: true,
|
|
16311
|
-
force: true,
|
|
16312
|
-
diagnose: req.body.background !== true,
|
|
16313
|
-
operation: "Updating Miniforge",
|
|
16314
|
-
})
|
|
16315
|
-
}
|
|
16316
|
-
})
|
|
16268
|
+
for (const runtimeName of ["miniconda", "miniforge"]) {
|
|
16269
|
+
const runtimePath = this.kernel.path("bin", runtimeName)
|
|
16270
|
+
await removePath(runtimePath)
|
|
16271
|
+
}
|
|
16317
16272
|
res.json({ success: true })
|
|
16318
16273
|
} else if (req.body.type === 'bin') {
|
|
16319
16274
|
let folderPath = this.kernel.path("bin")
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
force: true,
|
|
16326
|
-
diagnose: req.body.background !== true,
|
|
16327
|
-
operation: "Resetting managed tools",
|
|
16328
|
-
})
|
|
16329
|
-
console.log(`[fresh install] removing bin: complete (${((Date.now() - removalStartedAt) / 1000).toFixed(1)}s)`)
|
|
16330
|
-
await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
16331
|
-
const refreshStartedAt = Date.now()
|
|
16332
|
-
await this.kernel.bin.refreshInstalled()
|
|
16333
|
-
console.log(`[fresh install] refreshing installed state: complete (${((Date.now() - refreshStartedAt) / 1000).toFixed(1)}s)`)
|
|
16334
|
-
})
|
|
16275
|
+
const removalStartedAt = Date.now()
|
|
16276
|
+
console.log(`[fresh install] removing bin: start ${folderPath}`)
|
|
16277
|
+
await removePath(folderPath)
|
|
16278
|
+
console.log(`[fresh install] removing bin: complete (${((Date.now() - removalStartedAt) / 1000).toFixed(1)}s)`)
|
|
16279
|
+
await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
16335
16280
|
res.json({ success: true })
|
|
16336
16281
|
} else if (req.body.type === 'cache') {
|
|
16337
16282
|
let folderPath = this.kernel.path("cache")
|
|
16338
|
-
await removePath(folderPath
|
|
16339
|
-
recursive: true,
|
|
16340
|
-
force: true,
|
|
16341
|
-
diagnose: req.body.background !== true,
|
|
16342
|
-
operation: "Clearing the Pinokio cache",
|
|
16343
|
-
})
|
|
16283
|
+
await removePath(folderPath)
|
|
16344
16284
|
await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
16345
16285
|
await Environment.ensurePinokioCacheDirs(this.kernel, {
|
|
16346
16286
|
throwOnFailure: true
|
|
@@ -16357,17 +16297,8 @@ class Server {
|
|
|
16357
16297
|
}
|
|
16358
16298
|
res.json({ success: true })
|
|
16359
16299
|
} else if (req.body.name) {
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
res.status(400).json({ error: "Invalid app deletion path" })
|
|
16363
|
-
return
|
|
16364
|
-
}
|
|
16365
|
-
await removePath(folderPath, {
|
|
16366
|
-
recursive: true,
|
|
16367
|
-
force: true,
|
|
16368
|
-
diagnose: req.body.background !== true,
|
|
16369
|
-
operation: `Deleting ${req.body.name}`,
|
|
16370
|
-
})
|
|
16300
|
+
let folderPath = this.kernel.path("api", req.body.name)
|
|
16301
|
+
await removePath(folderPath)
|
|
16371
16302
|
// await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
16372
16303
|
await new Promise((resolve, reject) => {
|
|
16373
16304
|
setTimeout(() => {
|
|
@@ -16378,10 +16309,6 @@ class Server {
|
|
|
16378
16309
|
res.json({ success: true })
|
|
16379
16310
|
}
|
|
16380
16311
|
} catch(err) {
|
|
16381
|
-
if (isPathRemovalPendingError(err)) {
|
|
16382
|
-
res.status(423).json({ success: false, pending: true })
|
|
16383
|
-
return
|
|
16384
|
-
}
|
|
16385
16312
|
if (isPathRemovalBlockedError(err)) {
|
|
16386
16313
|
res.status(423).json({ success: false, error: err.toJSON() })
|
|
16387
16314
|
return
|
package/server/public/common.js
CHANGED
|
@@ -27,7 +27,6 @@ const PinokioPathRemoval = (() => {
|
|
|
27
27
|
const renderBlocker = (blocker) => {
|
|
28
28
|
const name = blocker.name || blocker.serviceName || `Process ${blocker.pid || ""}`
|
|
29
29
|
const detail = [
|
|
30
|
-
blocker.executable && blocker.executable !== name ? blocker.executable : "",
|
|
31
30
|
blocker.serviceName && blocker.serviceName !== name ? blocker.serviceName : "",
|
|
32
31
|
blocker.pid ? `PID ${blocker.pid}` : "",
|
|
33
32
|
].filter(Boolean).join(" · ")
|
|
@@ -44,7 +43,7 @@ const PinokioPathRemoval = (() => {
|
|
|
44
43
|
const itemLabel = itemCount === 1 ? "one remaining item" : `${itemCount || "some"} remaining items`
|
|
45
44
|
const causeCode = details.causeCode ? ` (${escapeHtml(details.causeCode)})` : ""
|
|
46
45
|
const explanation = blockers.length > 0
|
|
47
|
-
? `Windows couldn't remove ${itemLabel} because
|
|
46
|
+
? `Windows couldn't remove ${itemLabel} because one or more files are still in use.`
|
|
48
47
|
: `Windows couldn't remove ${itemLabel}${causeCode}. No application was identified. Check permissions or security software.`
|
|
49
48
|
const blockerHtml = blockers.length > 0
|
|
50
49
|
? `<ul class="pinokio-path-blocker-list">${blockers.map(renderBlocker).join("")}</ul>`
|
|
@@ -69,27 +68,15 @@ const PinokioPathRemoval = (() => {
|
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
const parseResponse = async (response) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
const fallbackResponse = typeof response.clone === "function" ? response.clone() : null
|
|
76
|
-
let payload
|
|
77
|
-
try {
|
|
78
|
-
payload = await response.json()
|
|
79
|
-
} catch (_) {
|
|
80
|
-
const text = fallbackResponse && typeof fallbackResponse.text === "function"
|
|
81
|
-
? await fallbackResponse.text()
|
|
82
|
-
: ""
|
|
83
|
-
throw new Error(text || `Request failed (${response.status || "unknown"})`)
|
|
84
|
-
}
|
|
85
|
-
if (!response.ok && !blockedDetails(payload) && !(payload && payload.pending === true)) {
|
|
71
|
+
const payload = await response.json()
|
|
72
|
+
if (!response.ok && !blockedDetails(payload)) {
|
|
86
73
|
const message = payload && payload.error
|
|
87
74
|
throw new Error(typeof message === "string" ? message : `Request failed (${response.status})`)
|
|
88
75
|
}
|
|
89
76
|
return payload
|
|
90
77
|
}
|
|
91
78
|
|
|
92
|
-
const show =
|
|
79
|
+
const show = ({ details, retry }) => {
|
|
93
80
|
if (typeof Swal === "undefined" || !Swal || typeof Swal.fire !== "function") {
|
|
94
81
|
return { cancelled: true }
|
|
95
82
|
}
|
|
@@ -99,7 +86,7 @@ const PinokioPathRemoval = (() => {
|
|
|
99
86
|
let settled = false
|
|
100
87
|
let timer = null
|
|
101
88
|
|
|
102
|
-
return
|
|
89
|
+
return new Promise((resolve) => {
|
|
103
90
|
const finish = (result) => {
|
|
104
91
|
if (settled) return
|
|
105
92
|
settled = true
|
|
@@ -123,18 +110,20 @@ const PinokioPathRemoval = (() => {
|
|
|
123
110
|
retrying = true
|
|
124
111
|
if (manual && typeof Swal.disableButtons === "function") Swal.disableButtons()
|
|
125
112
|
try {
|
|
126
|
-
const result = await retry(
|
|
113
|
+
const result = await retry()
|
|
127
114
|
if (settled) return false
|
|
128
|
-
if (result && result.pending === true) {
|
|
129
|
-
return false
|
|
130
|
-
}
|
|
131
115
|
const nextBlocked = blockedDetails(result)
|
|
132
116
|
if (nextBlocked) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
117
|
+
if (manual) {
|
|
118
|
+
current = nextBlocked
|
|
119
|
+
update()
|
|
120
|
+
Swal.showValidationMessage("Windows still cannot remove the remaining items.")
|
|
121
|
+
}
|
|
136
122
|
return false
|
|
137
123
|
}
|
|
124
|
+
if (result && result.error) {
|
|
125
|
+
throw new Error(typeof result.error === "string" ? result.error : "Retry failed.")
|
|
126
|
+
}
|
|
138
127
|
finish(result)
|
|
139
128
|
return true
|
|
140
129
|
} catch (error) {
|
|
@@ -147,7 +136,7 @@ const PinokioPathRemoval = (() => {
|
|
|
147
136
|
}
|
|
148
137
|
|
|
149
138
|
Swal.fire({
|
|
150
|
-
title:
|
|
139
|
+
title: "File removal paused",
|
|
151
140
|
html: render(current),
|
|
152
141
|
showCancelButton: true,
|
|
153
142
|
confirmButtonText: "Try again now",
|
|
@@ -157,13 +146,7 @@ const PinokioPathRemoval = (() => {
|
|
|
157
146
|
didOpen: () => {
|
|
158
147
|
timer = setInterval(() => attempt(false), RETRY_INTERVAL)
|
|
159
148
|
},
|
|
160
|
-
preConfirm:
|
|
161
|
-
const done = await attempt(true)
|
|
162
|
-
return done ? true : false
|
|
163
|
-
},
|
|
164
|
-
willClose: () => {
|
|
165
|
-
if (timer) clearInterval(timer)
|
|
166
|
-
},
|
|
149
|
+
preConfirm: () => attempt(true),
|
|
167
150
|
}).then((result) => {
|
|
168
151
|
if (!settled && !result.isConfirmed) {
|
|
169
152
|
finish({ cancelled: true })
|
|
@@ -172,9 +155,14 @@ const PinokioPathRemoval = (() => {
|
|
|
172
155
|
})
|
|
173
156
|
}
|
|
174
157
|
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
158
|
+
const tryRemove = async (payload) => parseResponse(await fetch("/pinokio/delete", {
|
|
159
|
+
method: "post",
|
|
160
|
+
headers: { "Content-Type": "application/json" },
|
|
161
|
+
body: JSON.stringify(payload),
|
|
162
|
+
}))
|
|
163
|
+
|
|
164
|
+
const remove = async (payload) => {
|
|
165
|
+
const initial = await tryRemove(payload)
|
|
178
166
|
const details = blockedDetails(initial)
|
|
179
167
|
if (!details) {
|
|
180
168
|
return initial
|
|
@@ -184,14 +172,14 @@ const PinokioPathRemoval = (() => {
|
|
|
184
172
|
}
|
|
185
173
|
return show({
|
|
186
174
|
details,
|
|
187
|
-
retry: (
|
|
175
|
+
retry: () => tryRemove(payload),
|
|
188
176
|
})
|
|
189
177
|
}
|
|
190
178
|
|
|
191
179
|
return {
|
|
192
|
-
|
|
193
|
-
request,
|
|
180
|
+
remove,
|
|
194
181
|
show,
|
|
182
|
+
tryRemove,
|
|
195
183
|
}
|
|
196
184
|
})()
|
|
197
185
|
|
package/server/public/style.css
CHANGED
|
@@ -6985,10 +6985,6 @@ body.dark .swal2-popup.swal2-modal:not(.loader-popup):not(.pinokio-download-moda
|
|
|
6985
6985
|
}
|
|
6986
6986
|
|
|
6987
6987
|
.pinokio-path-blocker-item {
|
|
6988
|
-
display: flex;
|
|
6989
|
-
align-items: baseline;
|
|
6990
|
-
justify-content: space-between;
|
|
6991
|
-
gap: 16px;
|
|
6992
6988
|
padding: 8px 0;
|
|
6993
6989
|
border-bottom: 1px solid rgba(127, 127, 127, 0.18);
|
|
6994
6990
|
}
|
|
@@ -6998,26 +6994,18 @@ body.dark .swal2-popup.swal2-modal:not(.loader-popup):not(.pinokio-download-moda
|
|
|
6998
6994
|
}
|
|
6999
6995
|
|
|
7000
6996
|
.pinokio-path-blocker-detail {
|
|
6997
|
+
display: block;
|
|
7001
6998
|
font-size: 12px;
|
|
7002
6999
|
overflow-wrap: anywhere;
|
|
7003
|
-
text-align: right;
|
|
7004
7000
|
}
|
|
7005
7001
|
|
|
7006
7002
|
.pinokio-path-blocker-files {
|
|
7007
7003
|
margin: 0 0 14px;
|
|
7008
7004
|
}
|
|
7009
7005
|
|
|
7010
|
-
.pinokio-path-blocker-files summary {
|
|
7011
|
-
cursor: pointer;
|
|
7012
|
-
}
|
|
7013
|
-
|
|
7014
|
-
.pinokio-path-blocker-files div {
|
|
7015
|
-
display: grid;
|
|
7016
|
-
gap: 6px;
|
|
7017
|
-
margin-top: 8px;
|
|
7018
|
-
}
|
|
7019
|
-
|
|
7020
7006
|
.pinokio-path-blocker-files code {
|
|
7007
|
+
display: block;
|
|
7008
|
+
margin-top: 6px;
|
|
7021
7009
|
overflow-wrap: anywhere;
|
|
7022
7010
|
white-space: normal;
|
|
7023
7011
|
}
|
|
@@ -7027,14 +7015,3 @@ body.dark .swal2-popup.swal2-modal:not(.loader-popup):not(.pinokio-download-moda
|
|
|
7027
7015
|
align-items: center;
|
|
7028
7016
|
gap: 8px;
|
|
7029
7017
|
}
|
|
7030
|
-
|
|
7031
|
-
@media (max-width: 560px) {
|
|
7032
|
-
.pinokio-path-blocker-item {
|
|
7033
|
-
display: grid;
|
|
7034
|
-
gap: 2px;
|
|
7035
|
-
}
|
|
7036
|
-
|
|
7037
|
-
.pinokio-path-blocker-detail {
|
|
7038
|
-
text-align: left;
|
|
7039
|
-
}
|
|
7040
|
-
}
|