@samitouri / QOSami-HFS / commits / bb98e3cc

windows integration without confirmation dialog

Massimo Melina committed Oct 27, 2023 at 00:18 UTC bb98e3cce753b02e5e03c50862adfa6bf4e4fa4b
3 files changed +39 -42
admin/src/VfsMenuBar.ts
+3 -10
@@ -36,13 +36,14 @@ export default function VfsMenuBar({ status }: any) {
36 status?.platform === 'win32' && h(Btn, {
37 icon: Microsoft,
38 variant: 'outlined',
39 + doneMessage: true,
40 ...(!integrated?.is ? {
41 children: "System integration",
42 onClick: () => windowsIntegration().then(reload),
43 } : {
44 confirm: true,
45 children: "Remove integration",
45 - onClick: () => exec('windows_remove').then(reload),
46 + onClick: () => apiCall('windows_remove').then(reload),
47 })
48 }),
49 )
@@ -57,13 +58,5 @@ async function windowsIntegration() {
58 } }),
59 )
60 return await confirmDialog(msg)
60 - && exec('windows_integration')
61 -}
62 -
63 -async function exec(api: string) {
64 - const hint = alertDialog("Click YES to the next 2 dialogs. The second dialog may not appear, and you need to click on the bottom bar.", 'warning')
65 - const { finish } = await apiCall(api, {}, { timeout: false })
66 - hint.close()
67 - return finish ? alertDialog("To finish the process, please execute the file you'll find on your desktop: " + basename(finish))
68 - : alertDialog("Done!", 'success')
61 + && apiCall('windows_integration')
62 }
admin/src/mui.ts
+10 -4
@@ -17,7 +17,7 @@ import {
17 import { formatPerc, WIKI_URL } from '../../src/cross'
18 import { dontBotherWithKeys, useStateMounted } from '@hfs/shared'
19 import { Promisable } from '@hfs/mui-grid-form'
20 -import { alertDialog, confirmDialog } from './dialog'
20 +import { alertDialog, confirmDialog, toast } from './dialog'
21 import { LoadingButton, LoadingButtonProps } from '@mui/lab'
22 import { Link as RouterLink } from 'react-router-dom'
23
@@ -53,7 +53,7 @@ export function IconProgress({ icon, progress, offset, addTitle, sx }: IconProgr
53 value: (offset || 1e-7) * 100,
54 variant: 'determinate',
55 size: 32,
56 - sx: { display: 'flex', ...sx }, // workaround: without this the element is has 0 width when the space is crammy (monitor/file)
56 + sx: { display: 'flex', ...sx }, // workaround: without this the element has 0 width when the space is crammy (monitor/file)
57 }),
58 })
59 )
@@ -135,10 +135,11 @@ interface BtnProps extends Omit<LoadingButtonProps,'disabled'|'title'|'onClick'>
135 progress?: boolean | number
136 link?: string
137 confirm?: boolean | string
138 + doneMessage?: boolean | string
139 tooltipProps?: TooltipProps
140 onClick: (...args: Parameters<NonNullable<ButtonProps['onClick']>>) => Promisable<any>
141 }
141 -export function Btn({ icon, title, onClick, disabled, progress, link, tooltipProps, confirm, ...rest }: BtnProps) {
142 +export function Btn({ icon, title, onClick, disabled, progress, link, tooltipProps, confirm, doneMessage, ...rest }: BtnProps) {
143 const [loading, setLoading] = useStateMounted(false)
144 if (typeof disabled === 'string') {
145 title = disabled
@@ -160,7 +161,12 @@ export function Btn({ icon, title, onClick, disabled, progress, link, tooltipPro
161 const ret = onClick?.apply(this,args)
162 if (ret && ret instanceof Promise) {
163 setLoading(true)
163 - ret.catch(alertDialog).finally(()=> setLoading(false))
164 + ret.then(async res => {
165 + if (doneMessage)
166 + toast(doneMessage === true ? "Operation completed" : doneMessage, 'success')
167 + return res
168 + }, alertDialog)
169 + .finally(()=> setLoading(false))
170 }
171 }
172 })
src/api.vfs.ts
+26 -28
@@ -3,7 +3,7 @@
3 import { getNodeName, isSameFilenameAs, nodeIsDirectory, saveVfs, urlToNode, vfs, VfsNode, applyParentToChild,
4 permsFromParent } from './vfs'
5 import _ from 'lodash'
6 -import { stat, unlink, writeFile } from 'fs/promises'
6 +import { stat } from 'fs/promises'
7 import { ApiError, ApiHandlers, SendListReadable } from './apiMiddleware'
8 import { dirname, extname, join, resolve } from 'path'
9 import { dirStream, enforceFinal, isDirectory, isWindowsDrive, makeMatcher, PERM_KEYS, VfsNodeAdminSend } from './misc'
@@ -14,8 +14,6 @@ import {
14 import { getDrives } from './util-os'
15 import { Stats } from 'fs'
16 import { getBaseUrlOrDefault, getServerStatus } from './listen'
17 -import { homedir } from 'os'
18 -import open from 'open'
17 import { promisify } from 'util'
18 import { execFile } from 'child_process'
19
@@ -78,7 +76,7 @@ const apis: ApiHandlers = {
76 if (_.isEmpty(oldParent!.children))
77 delete oldParent!.children
78 ;(parentNode.children ||= []).push(fromNode)
81 - await saveVfs()
79 + saveVfs()
80 return {}
81 },
82
@@ -96,7 +94,7 @@ const apis: ApiHandlers = {
94 delete props.masks
95 Object.assign(n, props)
96 simplifyName(n)
99 - await saveVfs()
97 + saveVfs()
98 return n
99 },
100
@@ -123,7 +121,7 @@ const apis: ApiHandlers = {
121 child.name = name
122 simplifyName(child)
123 ;(parentNode.children ||= []).unshift(child)
126 - await saveVfs()
124 + saveVfs()
125 const link = getBaseUrlOrDefault()
126 + (parent ? enforceFinal('/', parent) : '/')
127 + encodeURIComponent(getNodeName(child))
@@ -211,18 +209,21 @@ const apis: ApiHandlers = {
209 },
210
211 async windows_integration() {
214 - return { finish: await windowsIntegration() }
212 + await windowsIntegration()
213 + return {}
214 },
215
216 async windows_integrated() {
217 return {
219 - is: await promisify(execFile)('reg', ['query', WINDOWS_REG_KEY])
218 + is: await reg('query', WINDOWS_REG_KEY)
219 .then(x => x.stdout.includes('REG_SZ'), () => false)
220 }
221 },
222
224 - async windows_remove() { // not using `reg delete` because it doesn't ask for admin permissions
225 - return { finish: await runReg(['*', 'Directory'].map(k => `\n\n[-${WINDOWS_REG_KEY.replace('*', k)}]`).join('')) }
223 + async windows_remove() {
224 + for (const k of ['*', 'Directory'])
225 + await reg('delete', WINDOWS_REG_KEY.replace('*',k), '/f')
226 + return {}
227 },
228
229 }
@@ -245,27 +246,24 @@ function simplifyName(node: VfsNode) {
246 delete node.name
247 }
248
248 -const WINDOWS_REG_KEY = 'HKEY_CLASSES_ROOT\\*\\shell\\AddToHFS3'
249 -const WINDOWS_REG_KEY2 = WINDOWS_REG_KEY + '\\command'
249 +const WINDOWS_REG_KEY = 'HKCU\\Software\\Classes\\*\\shell\\AddToHFS3'
250
251 -export async function runReg(content: string) {
252 - const path = homedir() + '\\desktop\\hfs-windows-menu.reg'
253 - await writeFile(path, 'Windows Registry Editor Version 5.00\n\n' + content, 'utf8')
254 - try {
255 - await open(path, { wait: true})
256 - await unlink(path)
257 - }
258 - catch { return path }
259 -}
251 +if (IS_WINDOWS) // legacy 0.49.0-beta7 2023-10-27. Remove in 0.50
252 + for (const k of ['*', 'Directory'])
253 + reg('delete', `HKCR\\${k}\\shell\\AddToHFS3`, '/f').catch(() => {})
254
255 export async function windowsIntegration() {
256 const status = await getServerStatus()
257 const url = 'http://localhost:' + status.http.port
264 - return runReg(['*', 'Directory'].map(k => `
265 -[${WINDOWS_REG_KEY.replace('*', k)}]
266 -@="Add to HFS (new)"
258 + for (const k of ['*', 'Directory']) {
259 + await reg('add', WINDOWS_REG_KEY.replace('*', k), '/ve', '/f', '/d', 'Add to HFS (new)')
260 + await reg('add', WINDOWS_REG_KEY.replace('*', k) + '\\command', '/ve', '/f', '/d', `powershell -Command "
261 + $j = '{ \\"source\\": "' + ('%1'|convertTo-json) + '" }'; $wsh = New-Object -ComObject Wscript.Shell;
262 + try { $res = Invoke-WebRequest -Uri '${url}/~/api/add_vfs' -Method POST -Headers @{ 'x-hfs-anti-csrf' = '1' } -ContentType 'application/json' -TimeoutSec 1 -Body $j;
263 + $json = $res.Content | ConvertFrom-Json; $link = $json.link; $link | Set-Clipboard; } catch { $wsh.Popup('Server is down', 0, 'Error', 16); }"`)
264 + }
265 +}
266
268 -[${WINDOWS_REG_KEY2.replace('*', k)}]
269 -@="powershell -Command \\"$p = '%1'.Replace('\\\\', '\\\\\\\\'); $j = '{ \\\\\\"source\\\\\\": \\\\\\"' + $p + '\\\\\\" }'; $wsh = New-Object -ComObject Wscript.Shell; try { $res = Invoke-WebRequest -Uri '${url}/~/api/add_vfs' -Method POST -Headers @{ 'x-hfs-anti-csrf' = '1' } -ContentType 'application/json' -TimeoutSec 1 -Body $j; $json = $res.Content | ConvertFrom-Json; $link = $json.link; $link | Set-Clipboard; } catch { $wsh.Popup('Server is down', 0, 'Error', 16); }\\""
270 - `).join(''))
271 -}
\ No newline at end of file
267 +function reg(...pars: string[]) {
268 + return promisify(execFile)('reg', pars)
269 +}