25
25
from sys import platform
26
26
import os
27
27
from shutil import which
28
+ import ctypes
29
+ import string
28
30
29
31
# Import QT libraries
30
32
from PySide6 .QtCore import Qt , QSettings
@@ -69,13 +71,18 @@ def __init__(self):
69
71
# https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#file-and-directory-names
70
72
# Disallow the following [<,>,.,",|,?,*] - note, we still need directory characters to declare a path
71
73
self .lineEdit_mountPoint .setValidator (QtGui .QRegularExpressionValidator (r'^[^<>."|?\0*]*$' ,self ))
74
+ self .button_browse .setText ('Drive Letter' )
75
+ self .button_browse .setToolTip ('Select an unused drive letter for mounting' )
76
+ self .button_browse .clicked .connect (self .chooseDriveLetter )
72
77
else :
73
78
# Allow anything BUT Nul
74
79
# Note: Different versions of Python don't like the embedded null character, send in the raw string instead
75
80
self .lineEdit_mountPoint .setValidator (QtGui .QRegularExpressionValidator (r'^[^\0]*$' ,self ))
81
+ self .button_browse .setText ('Browse' )
82
+ self .button_browse .setToolTip ('Browse to a pre-existing directory to mount' )
83
+ self .button_browse .clicked .connect (self .getFileDirInput )
76
84
77
85
# Set up the signals for all the interactive entities
78
- self .button_browse .clicked .connect (self .getFileDirInput )
79
86
self .button_config .clicked .connect (self .showSettingsWidget )
80
87
self .button_mount .clicked .connect (self .mountBucket )
81
88
self .button_unmount .clicked .connect (self .unmountBucket )
@@ -84,11 +91,9 @@ def __init__(self):
84
91
self .lineEdit_mountPoint .editingFinished .connect (self .updateMountPointInSettings )
85
92
self .dropDown_bucketSelect .currentIndexChanged .connect (self .modifyPipeline )
86
93
if platform == 'win32' :
87
- self .lineEdit_mountPoint .setToolTip ('Designate a new location to mount the bucket, do not create the directory' )
88
- self .button_browse .setToolTip ("Browse to a new location but don't create a new directory" )
94
+ self .lineEdit_mountPoint .setToolTip ('Designate a drive letter to mount to' )
89
95
else :
90
96
self .lineEdit_mountPoint .setToolTip ('Designate a location to mount the bucket - the directory must already exist' )
91
- self .button_browse .setToolTip ('Browse to a pre-existing directory' )
92
97
93
98
def checkConfigDirectory (self ):
94
99
workingDir = self .getWorkingDir ()
@@ -162,12 +167,16 @@ def mountBucket(self):
162
167
except ValueError as e :
163
168
self .addOutputText (f"Invalid mount path: { str (e )} " )
164
169
return
165
- directory = os .path .join (directory , mountDirSuffix )
166
170
# get config path
167
171
configPath = os .path .join (self .getWorkingDir (), 'config.yaml' )
168
172
169
173
# on Windows, the mount directory should not exist (yet)
170
174
if platform == 'win32' :
175
+ drive , tail = os .path .splitdrive (directory )
176
+ # Only append the cloudfuse suffix if not mounting to a drive letter
177
+ if not (drive and (tail == '' or tail in ['\\ ' , '/' ])):
178
+ directory = os .path .join (directory , mountDirSuffix )
179
+
171
180
if os .path .exists (directory ):
172
181
self .addOutputText (f"Directory { directory } already exists! Aborting new mount." )
173
182
self .errorMessageBox (f"Error: Cloudfuse needs to create the directory { directory } , but it already exists!" )
@@ -226,7 +235,10 @@ def unmountBucket(self):
226
235
directory = str (self .lineEdit_mountPoint .text ())
227
236
commandParts = []
228
237
# TODO: properly handle unmount. This is relying on the line_edit not being changed by the user.
229
- directory = os .path .join (directory , mountDirSuffix )
238
+ drive , tail = os .path .splitdrive (directory )
239
+ # Only append the cloudfuse suffix if not mounting to a drive letter
240
+ if not (drive and (tail == '' or tail in ['\\ ' , '/' ])):
241
+ directory = os .path .join (directory , mountDirSuffix )
230
242
commandParts = [cloudfuseCli , 'unmount' , directory ]
231
243
if platform != 'win32' :
232
244
commandParts .append ('--lazy' )
@@ -267,7 +279,7 @@ def runCommand(self, commandParts):
267
279
# run command
268
280
try :
269
281
process = subprocess .run (
270
- commandParts ,
282
+ commandParts ,
271
283
capture_output = True ,
272
284
creationflags = subprocess .CREATE_NO_WINDOW if hasattr (subprocess , 'CREATE_NO_WINDOW' ) else 0 )
273
285
stdOut = process .stdout .decode ().strip ()
@@ -291,3 +303,30 @@ def errorMessageBox(self, messageString, titleString='Error'):
291
303
msg .setText (messageString )
292
304
# Show the message box
293
305
msg .exec ()
306
+
307
+ def chooseDriveLetter (self ):
308
+ unused_letters = get_unused_driver_letters ()
309
+ if not unused_letters :
310
+ QtWidgets .QMessageBox .warning (self , 'No Drive Letters' , 'No unused drive letters available.' )
311
+ return
312
+
313
+ drive , ok = QtWidgets .QInputDialog .getItem (
314
+ self ,
315
+ 'Select Drive Letter' ,
316
+ 'Available drive letters:' ,
317
+ unused_letters ,
318
+ 0 ,
319
+ False
320
+ )
321
+ if ok and drive :
322
+ self .lineEdit_mountPoint .setText (f"{ drive } " )
323
+ self .updateMountPointInSettings ()
324
+
325
+ def get_unused_driver_letters ():
326
+ bitmask = ctypes .windll .kernel32 .GetLogicalDrives ()
327
+ unused = []
328
+ for letter in string .ascii_uppercase :
329
+ if not ((bitmask & 1 ) or (letter == 'A' or letter == 'B' )):
330
+ unused .append (letter + ':' )
331
+ bitmask >>= 1
332
+ return unused
0 commit comments