pinokiod 8.0.16 → 8.0.18
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 +3 -21
- package/kernel/path_removal.js +51 -50
- package/kernel/windows_restart_manager.js +4 -38
- package/package.json +1 -1
- package/server/index.js +9 -73
- package/server/public/common.js +27 -39
- package/server/public/style.css +3 -26
- package/server/views/app.ejs +1 -7
- 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 +4 -21
- package/server/views/net.ejs +1 -7
- package/server/views/settings.ejs +2 -14
- package/server/views/terminal.ejs +1 -7
- package/test/conda-bin.test.js +47 -0
- package/test/path-removal-ui.test.js +114 -26
- 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
|
@@ -185,7 +185,9 @@ report_errors: false`)
|
|
|
185
185
|
install_path_exists = await this.kernel.exists(`bin/${CONDA_ROOT_DIR}`)
|
|
186
186
|
if (install_path_exists) {
|
|
187
187
|
console.log("Removing existing install path...", install_path)
|
|
188
|
+
ondata({ raw: `removing existing Miniforge installation...\r\n` })
|
|
188
189
|
await this.removeInstallPath(install_path, ondata)
|
|
190
|
+
ondata({ raw: `existing Miniforge installation removed.\r\n` })
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
// 2. run the script
|
|
@@ -252,27 +254,7 @@ report_errors: false`)
|
|
|
252
254
|
}
|
|
253
255
|
async removeInstallPath(target, ondata) {
|
|
254
256
|
try {
|
|
255
|
-
|
|
256
|
-
if (error && error.code === "ENOENT") {
|
|
257
|
-
return null
|
|
258
|
-
}
|
|
259
|
-
throw error
|
|
260
|
-
})
|
|
261
|
-
if (!stat) {
|
|
262
|
-
return
|
|
263
|
-
}
|
|
264
|
-
if (stat.isSymbolicLink()) {
|
|
265
|
-
await removePath(target, {
|
|
266
|
-
force: true,
|
|
267
|
-
operation: "Updating Miniforge",
|
|
268
|
-
})
|
|
269
|
-
return
|
|
270
|
-
}
|
|
271
|
-
await removePath(target, {
|
|
272
|
-
recursive: true,
|
|
273
|
-
force: true,
|
|
274
|
-
operation: "Updating Miniforge",
|
|
275
|
-
})
|
|
257
|
+
await removePath(target)
|
|
276
258
|
} catch (error) {
|
|
277
259
|
if (isPathRemovalBlockedError(error)) {
|
|
278
260
|
if (typeof ondata === "function") {
|
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,41 +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
16268
|
for (const runtimeName of ["miniconda", "miniforge"]) {
|
|
16307
16269
|
const runtimePath = this.kernel.path("bin", runtimeName)
|
|
16308
|
-
await removePath(runtimePath
|
|
16309
|
-
recursive: true,
|
|
16310
|
-
force: true,
|
|
16311
|
-
diagnose: req.body.background !== true,
|
|
16312
|
-
operation: "Updating Miniforge",
|
|
16313
|
-
})
|
|
16270
|
+
await removePath(runtimePath)
|
|
16314
16271
|
}
|
|
16315
16272
|
res.json({ success: true })
|
|
16316
16273
|
} else if (req.body.type === 'bin') {
|
|
16317
16274
|
let folderPath = this.kernel.path("bin")
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
operation: "Resetting managed tools",
|
|
16323
|
-
})
|
|
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)`)
|
|
16324
16279
|
await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
16325
|
-
await this.kernel.bin.refreshInstalled()
|
|
16326
16280
|
res.json({ success: true })
|
|
16327
16281
|
} else if (req.body.type === 'cache') {
|
|
16328
16282
|
let folderPath = this.kernel.path("cache")
|
|
16329
|
-
await removePath(folderPath
|
|
16330
|
-
recursive: true,
|
|
16331
|
-
force: true,
|
|
16332
|
-
diagnose: req.body.background !== true,
|
|
16333
|
-
operation: "Clearing the Pinokio cache",
|
|
16334
|
-
})
|
|
16283
|
+
await removePath(folderPath)
|
|
16335
16284
|
await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
16336
16285
|
await Environment.ensurePinokioCacheDirs(this.kernel, {
|
|
16337
16286
|
throwOnFailure: true
|
|
@@ -16348,17 +16297,8 @@ class Server {
|
|
|
16348
16297
|
}
|
|
16349
16298
|
res.json({ success: true })
|
|
16350
16299
|
} else if (req.body.name) {
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
res.status(400).json({ error: "Invalid app deletion path" })
|
|
16354
|
-
return
|
|
16355
|
-
}
|
|
16356
|
-
await removePath(folderPath, {
|
|
16357
|
-
recursive: true,
|
|
16358
|
-
force: true,
|
|
16359
|
-
diagnose: req.body.background !== true,
|
|
16360
|
-
operation: `Deleting ${req.body.name}`,
|
|
16361
|
-
})
|
|
16300
|
+
let folderPath = this.kernel.path("api", req.body.name)
|
|
16301
|
+
await removePath(folderPath)
|
|
16362
16302
|
// await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
16363
16303
|
await new Promise((resolve, reject) => {
|
|
16364
16304
|
setTimeout(() => {
|
|
@@ -16369,10 +16309,6 @@ class Server {
|
|
|
16369
16309
|
res.json({ success: true })
|
|
16370
16310
|
}
|
|
16371
16311
|
} catch(err) {
|
|
16372
|
-
if (isPathRemovalPendingError(err)) {
|
|
16373
|
-
res.status(423).json({ success: false, pending: true })
|
|
16374
|
-
return
|
|
16375
|
-
}
|
|
16376
16312
|
if (isPathRemovalBlockedError(err)) {
|
|
16377
16313
|
res.status(423).json({ success: false, error: err.toJSON() })
|
|
16378
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
|
-
}
|
package/server/views/app.ejs
CHANGED
|
@@ -11866,13 +11866,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
11866
11866
|
actions: "hidden"
|
|
11867
11867
|
}
|
|
11868
11868
|
});
|
|
11869
|
-
let res = await PinokioPathRemoval.
|
|
11870
|
-
method: "post",
|
|
11871
|
-
headers: {
|
|
11872
|
-
"Content-Type": "application/json"
|
|
11873
|
-
},
|
|
11874
|
-
body: JSON.stringify({ name, background })
|
|
11875
|
-
}))
|
|
11869
|
+
let res = await PinokioPathRemoval.remove({ name })
|
|
11876
11870
|
console.log("res", res)
|
|
11877
11871
|
Swal.close()
|
|
11878
11872
|
if (res) {
|
|
@@ -231,13 +231,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
231
231
|
if (proceed) {
|
|
232
232
|
document.querySelector(".reset-bin-loading").classList.remove("hidden")
|
|
233
233
|
document.querySelector("#del-bin").classList.add("hidden")
|
|
234
|
-
let res = await PinokioPathRemoval.
|
|
235
|
-
method: "post",
|
|
236
|
-
headers: {
|
|
237
|
-
"Content-Type": "application/json"
|
|
238
|
-
},
|
|
239
|
-
body: JSON.stringify({ type: "bin", background })
|
|
240
|
-
}))
|
|
234
|
+
let res = await PinokioPathRemoval.remove({ type: "bin" })
|
|
241
235
|
console.log(res)
|
|
242
236
|
document.querySelector(".reset-bin-loading").classList.add("hidden")
|
|
243
237
|
if (res.cancelled) {
|