hydrus/hydrus/core/HydrusPaths.py

992 lines
26 KiB
Python
Raw Normal View History

2022-03-23 20:57:10 +00:00
import collections
2015-11-04 22:30:28 +00:00
import os
import typing
2016-04-14 01:54:29 +00:00
import psutil
2018-09-26 19:05:12 +00:00
import re
2015-11-25 22:00:57 +00:00
import send2trash
2017-06-07 22:05:15 +00:00
import shlex
2015-11-25 22:00:57 +00:00
import shutil
2016-05-04 21:50:55 +00:00
import stat
2015-11-04 22:30:28 +00:00
import subprocess
import threading
2015-11-25 22:00:57 +00:00
import traceback
2015-11-04 22:30:28 +00:00
2020-07-29 20:52:44 +00:00
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusThreading
2022-03-23 20:57:10 +00:00
mimes_to_default_thumbnail_paths = collections.defaultdict( lambda: os.path.join( HC.STATIC_DIR, 'hydrus.png' ) )
mimes_to_default_thumbnail_paths[ HC.APPLICATION_PDF ] = os.path.join( HC.STATIC_DIR, 'pdf.png' )
mimes_to_default_thumbnail_paths[ HC.APPLICATION_PSD ] = os.path.join( HC.STATIC_DIR, 'psd.png' )
mimes_to_default_thumbnail_paths[ HC.APPLICATION_CLIP ] = os.path.join( HC.STATIC_DIR, 'clip.png' )
for mime in HC.AUDIO:
path = os.path.join( HC.STATIC_DIR, 'audio.png' )
mimes_to_default_thumbnail_paths[ mime ] = os.path.join( path )
for mime in HC.VIDEO:
path = os.path.join( HC.STATIC_DIR, 'video.png' )
mimes_to_default_thumbnail_paths[ mime ] = os.path.join( path )
for mime in HC.ARCHIVES:
path = os.path.join( HC.STATIC_DIR, 'zip.png' )
mimes_to_default_thumbnail_paths[ mime ] = os.path.join( path )
2016-04-06 19:52:45 +00:00
def AppendPathUntilNoConflicts( path ):
( path_absent_ext, ext ) = os.path.splitext( path )
good_path_absent_ext = path_absent_ext
i = 0
while os.path.exists( good_path_absent_ext + ext ):
good_path_absent_ext = path_absent_ext + '_' + str( i )
i += 1
return good_path_absent_ext + ext
2016-10-26 20:45:34 +00:00
def ConvertAbsPathToPortablePath( abs_path, base_dir_override = None ):
2015-11-04 22:30:28 +00:00
2016-05-04 21:50:55 +00:00
try:
2016-10-26 20:45:34 +00:00
if base_dir_override is None:
2017-05-10 21:33:58 +00:00
base_dir = HG.controller.GetDBDir()
2016-10-26 20:45:34 +00:00
else:
base_dir = base_dir_override
portable_path = os.path.relpath( abs_path, base_dir )
if portable_path.startswith( '..' ):
portable_path = abs_path
2016-05-04 21:50:55 +00:00
except:
portable_path = abs_path
if HC.PLATFORM_WINDOWS:
portable_path = portable_path.replace( '\\', '/' ) # store seps as /, to maintain multiplatform uniformity
return portable_path
2015-11-04 22:30:28 +00:00
2016-10-26 20:45:34 +00:00
def ConvertPortablePathToAbsPath( portable_path, base_dir_override = None ):
2015-11-25 22:00:57 +00:00
2016-05-04 21:50:55 +00:00
portable_path = os.path.normpath( portable_path ) # collapses .. stuff and converts / to \\ for windows only
if os.path.isabs( portable_path ):
abs_path = portable_path
else:
2016-10-26 20:45:34 +00:00
if base_dir_override is None:
2017-05-10 21:33:58 +00:00
base_dir = HG.controller.GetDBDir()
2016-10-26 20:45:34 +00:00
else:
base_dir = base_dir_override
abs_path = os.path.normpath( os.path.join( base_dir, portable_path ) )
2016-05-04 21:50:55 +00:00
if not HC.PLATFORM_WINDOWS and not os.path.exists( abs_path ):
abs_path = abs_path.replace( '\\', '/' )
2015-11-25 22:00:57 +00:00
2015-12-02 22:32:18 +00:00
return abs_path
def CopyAndMergeTree( source, dest ):
2016-01-06 21:17:20 +00:00
pauser = HydrusData.BigJobPauser()
2016-08-17 20:07:22 +00:00
MakeSureDirectoryExists( dest )
2015-12-02 22:32:18 +00:00
2016-06-01 20:04:15 +00:00
num_errors = 0
2015-12-02 22:32:18 +00:00
for ( root, dirnames, filenames ) in os.walk( source ):
dest_root = root.replace( source, dest )
for dirname in dirnames:
2016-01-06 21:17:20 +00:00
pauser.Pause()
2015-12-02 22:32:18 +00:00
source_path = os.path.join( root, dirname )
dest_path = os.path.join( dest_root, dirname )
2016-08-17 20:07:22 +00:00
MakeSureDirectoryExists( dest_path )
2015-12-02 22:32:18 +00:00
shutil.copystat( source_path, dest_path )
for filename in filenames:
2016-06-01 20:04:15 +00:00
if num_errors > 5:
raise Exception( 'Too many errors, directory copy abandoned.' )
2016-01-06 21:17:20 +00:00
pauser.Pause()
2015-12-02 22:32:18 +00:00
source_path = os.path.join( root, filename )
dest_path = os.path.join( dest_root, filename )
2016-06-01 20:04:15 +00:00
ok = MirrorFile( source_path, dest_path )
if not ok:
2016-05-18 20:07:14 +00:00
2016-06-01 20:04:15 +00:00
num_errors += 1
2016-05-18 20:07:14 +00:00
2015-12-02 22:32:18 +00:00
2015-11-25 22:00:57 +00:00
2015-11-04 22:30:28 +00:00
def CopyFileLikeToFileLike( f_source, f_dest ):
for block in ReadFileLikeAsBlocks( f_source ): f_dest.write( block )
2015-11-25 22:00:57 +00:00
def DeletePath( path ):
2019-07-24 21:39:02 +00:00
if HG.file_report_mode:
HydrusData.ShowText( 'Deleting {}'.format( path ) )
HydrusData.ShowText( ''.join( traceback.format_stack() ) )
2015-11-25 22:00:57 +00:00
if os.path.exists( path ):
2022-01-12 22:14:50 +00:00
TryToMakeFileWriteable( path )
2016-05-04 21:50:55 +00:00
2016-06-08 20:27:22 +00:00
try:
2015-11-25 22:00:57 +00:00
2016-06-08 20:27:22 +00:00
if os.path.isdir( path ):
shutil.rmtree( path )
else:
os.remove( path )
2015-11-25 22:00:57 +00:00
2016-06-08 20:27:22 +00:00
except Exception as e:
2015-11-25 22:00:57 +00:00
2019-01-09 22:59:03 +00:00
if 'Error 32' in str( e ):
2016-10-19 20:02:56 +00:00
# file in use by another process
HydrusData.DebugPrint( 'Trying to delete ' + path + ' failed because it was in use by another process.' )
else:
HydrusData.ShowText( 'Trying to delete ' + path + ' caused the following error:' )
HydrusData.ShowException( e )
2015-11-25 22:00:57 +00:00
2016-06-08 20:27:22 +00:00
2021-02-17 18:22:44 +00:00
def DirectoryIsWriteable( path ):
2018-02-21 21:59:37 +00:00
2021-02-17 18:22:44 +00:00
while not os.path.exists( path ):
2018-05-23 21:05:06 +00:00
2021-02-17 18:22:44 +00:00
try:
path = os.path.dirname( path )
except:
return False
2022-07-13 21:35:17 +00:00
if not os.access( path, os.W_OK | os.X_OK ):
2021-02-17 18:22:44 +00:00
2022-07-13 21:35:17 +00:00
return False
2018-05-23 21:05:06 +00:00
2022-07-13 21:35:17 +00:00
# we'll actually do a file, since Program Files passes the above test lmaoooo
2018-02-21 21:59:37 +00:00
try:
2022-07-13 21:35:17 +00:00
# also, using tempfile.TemporaryFile actually loops on PermissionError from Windows lmaaaooooo, thinking this is an already existing file
2021-02-17 18:22:44 +00:00
# so, just do it manually!
2022-07-13 21:35:17 +00:00
test_path = os.path.join( path, 'hydrus_permission_test' )
2018-02-21 21:59:37 +00:00
2022-07-13 21:35:17 +00:00
with open( test_path, 'wb' ) as f:
f.write( b'If this file still exists, this directory can be written to but not deleted from.' )
2018-02-21 21:59:37 +00:00
2022-07-13 21:35:17 +00:00
os.unlink( test_path )
2018-02-21 21:59:37 +00:00
except:
return False
2022-07-13 21:35:17 +00:00
return True
def FileisWriteable( path: str ):
2022-01-12 22:14:50 +00:00
return os.access( path, os.W_OK )
2016-08-24 18:36:56 +00:00
def FilterFreePaths( paths ):
free_paths = []
for path in paths:
2019-01-23 22:19:16 +00:00
HydrusThreading.CheckIfThreadShuttingDown()
2017-11-08 22:07:12 +00:00
2018-01-24 23:09:42 +00:00
if PathIsFree( path ):
2016-08-24 18:36:56 +00:00
free_paths.append( path )
return free_paths
2017-09-20 19:47:31 +00:00
def GetDefaultLaunchPath():
if HC.PLATFORM_WINDOWS:
return 'windows is called directly'
2019-11-20 23:10:46 +00:00
elif HC.PLATFORM_MACOS:
2017-09-20 19:47:31 +00:00
return 'open "%path%"'
elif HC.PLATFORM_LINUX:
return 'xdg-open "%path%"'
2020-07-15 20:52:09 +00:00
elif HC.PLATFORM_HAIKU:
return 'open "%path%"'
2017-09-20 19:47:31 +00:00
def GetPartitionInfo( path ) -> typing.Optional[ typing.NamedTuple ]:
2016-04-14 01:54:29 +00:00
path = path.lower()
2019-07-31 22:01:02 +00:00
try:
2016-04-14 01:54:29 +00:00
2020-08-19 22:38:20 +00:00
for scan_network in ( False, True ):
2019-07-31 22:01:02 +00:00
2020-08-19 22:38:20 +00:00
partition_infos = psutil.disk_partitions( all = scan_network )
2019-07-31 22:01:02 +00:00
2020-08-19 22:38:20 +00:00
def sort_descending_mountpoint( partition_info ): # i.e. put '/home' before '/'
return - len( partition_info.mountpoint )
partition_infos.sort( key = sort_descending_mountpoint )
2016-04-14 01:54:29 +00:00
2020-08-19 22:38:20 +00:00
for partition_info in partition_infos:
2019-07-31 22:01:02 +00:00
2020-08-19 22:38:20 +00:00
if path.startswith( partition_info.mountpoint.lower() ):
return partition_info
2020-08-19 22:38:20 +00:00
2019-07-31 22:01:02 +00:00
2016-04-14 01:54:29 +00:00
2019-07-31 22:01:02 +00:00
except UnicodeDecodeError: # wew lad psutil on some russian lad's fun filesystem
return None
2016-04-14 01:54:29 +00:00
return None
2016-01-06 21:17:20 +00:00
def GetDevice( path ) -> typing.Optional[ str ]:
partition_info = GetPartitionInfo( path )
if partition_info is None:
return None
else:
return partition_info.device
def GetFileSystemType( path ):
partition_info = GetPartitionInfo( path )
if partition_info is None:
return None
else:
return partition_info.fstype
2017-02-08 22:27:00 +00:00
def GetFreeSpace( path ):
disk_usage = psutil.disk_usage( path )
return disk_usage.free
2022-07-13 21:35:17 +00:00
def GetTotalSpace( path ):
disk_usage = psutil.disk_usage( path )
return disk_usage.total
2015-11-04 22:30:28 +00:00
def LaunchDirectory( path ):
def do_it():
if HC.PLATFORM_WINDOWS:
os.startfile( path )
else:
2019-11-20 23:10:46 +00:00
if HC.PLATFORM_MACOS:
2017-06-07 22:05:15 +00:00
2019-01-09 22:59:03 +00:00
cmd = [ 'open', path ]
2017-06-07 22:05:15 +00:00
elif HC.PLATFORM_LINUX:
2019-01-09 22:59:03 +00:00
cmd = [ 'xdg-open', path ]
2017-06-07 22:05:15 +00:00
2020-07-15 20:52:09 +00:00
elif HC.PLATFORM_HAIKU:
cmd = [ 'open', path ]
2015-11-04 22:30:28 +00:00
2017-04-12 21:46:46 +00:00
# setsid call un-childs this new process
2019-01-09 22:59:03 +00:00
sbp_kwargs = HydrusData.GetSubprocessKWArgs()
2020-03-25 21:15:57 +00:00
preexec_fn = getattr( os, 'setsid', None )
2020-06-17 21:31:54 +00:00
HydrusData.CheckProgramIsNotShuttingDown()
2020-03-25 21:15:57 +00:00
process = subprocess.Popen( cmd, preexec_fn = preexec_fn, **sbp_kwargs )
2015-11-04 22:30:28 +00:00
2020-06-17 21:31:54 +00:00
HydrusThreading.SubprocessCommunicate( process )
2015-11-04 22:30:28 +00:00
thread = threading.Thread( target = do_it )
thread.daemon = True
thread.start()
2017-09-20 19:47:31 +00:00
def LaunchFile( path, launch_path = None ):
2015-11-04 22:30:28 +00:00
2017-09-20 19:47:31 +00:00
def do_it( launch_path ):
2015-11-04 22:30:28 +00:00
2017-09-20 19:47:31 +00:00
if HC.PLATFORM_WINDOWS and launch_path is None:
2015-11-04 22:30:28 +00:00
os.startfile( path )
else:
2017-09-20 19:47:31 +00:00
if launch_path is None:
2017-06-07 22:05:15 +00:00
2017-09-20 19:47:31 +00:00
launch_path = GetDefaultLaunchPath()
2017-06-07 22:05:15 +00:00
2017-04-12 21:46:46 +00:00
2019-01-09 22:59:03 +00:00
complete_launch_path = launch_path.replace( '%path%', path )
2015-11-04 22:30:28 +00:00
2019-05-15 20:35:00 +00:00
hide_terminal = False
2017-09-20 19:47:31 +00:00
if HC.PLATFORM_WINDOWS:
2019-01-09 22:59:03 +00:00
cmd = complete_launch_path
2019-05-15 20:35:00 +00:00
2017-09-20 19:47:31 +00:00
preexec_fn = None
else:
2019-01-09 22:59:03 +00:00
cmd = shlex.split( complete_launch_path )
2017-09-20 19:47:31 +00:00
2020-03-25 21:15:57 +00:00
preexec_fn = getattr( os, 'setsid', None )
2017-09-20 19:47:31 +00:00
2015-11-04 22:30:28 +00:00
2019-02-13 22:26:43 +00:00
if HG.subprocess_report_mode:
2018-08-01 20:44:57 +00:00
message = 'Attempting to launch ' + path + ' using command ' + repr( cmd ) + '.'
2019-02-13 22:26:43 +00:00
HydrusData.ShowText( message )
2018-08-01 20:44:57 +00:00
2017-09-20 19:47:31 +00:00
try:
2019-05-15 20:35:00 +00:00
sbp_kwargs = HydrusData.GetSubprocessKWArgs( hide_terminal = hide_terminal, text = True )
2019-01-09 22:59:03 +00:00
2020-06-17 21:31:54 +00:00
HydrusData.CheckProgramIsNotShuttingDown()
2019-01-16 22:40:53 +00:00
process = subprocess.Popen( cmd, preexec_fn = preexec_fn, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **sbp_kwargs )
2017-09-20 19:47:31 +00:00
2020-06-17 21:31:54 +00:00
( stdout, stderr ) = HydrusThreading.SubprocessCommunicate( process )
2018-04-11 22:30:40 +00:00
2019-02-13 22:26:43 +00:00
if HG.subprocess_report_mode:
2018-04-18 22:10:15 +00:00
if stdout is None and stderr is None:
HydrusData.ShowText( 'No stdout or stderr came back.' )
2018-04-11 22:30:40 +00:00
if stdout is not None:
2018-04-18 22:10:15 +00:00
HydrusData.ShowText( 'stdout: ' + repr( stdout ) )
2018-04-11 22:30:40 +00:00
if stderr is not None:
2018-04-18 22:10:15 +00:00
HydrusData.ShowText( 'stderr: ' + repr( stderr ) )
2018-04-11 22:30:40 +00:00
2017-09-20 19:47:31 +00:00
except Exception as e:
2019-01-09 22:59:03 +00:00
HydrusData.ShowText( 'Could not launch a file! Command used was:' + os.linesep + str( cmd ) )
2017-09-20 19:47:31 +00:00
HydrusData.ShowException( e )
2015-11-04 22:30:28 +00:00
2017-09-20 19:47:31 +00:00
thread = threading.Thread( target = do_it, args = ( launch_path, ) )
2015-11-04 22:30:28 +00:00
thread.daemon = True
thread.start()
2016-08-17 20:07:22 +00:00
def MakeSureDirectoryExists( path ):
2022-08-03 20:59:51 +00:00
it_exists_already = os.path.exists( path )
if it_exists_already:
if os.path.isdir( path ):
return
else:
raise Exception( 'Sorry, the desired directory "{}" already exists as a normal file!'.format( path ) )
2019-05-08 21:06:42 +00:00
os.makedirs( path, exist_ok = True )
2016-08-17 20:07:22 +00:00
def safe_copy2( source, dest ):
2016-06-08 20:27:22 +00:00
copy_metadata = True
if HC.PLATFORM_WINDOWS:
2017-10-25 21:45:15 +00:00
mtime = os.path.getmtime( source )
# this is 1980-01-01 UTC, before which Windows can have trouble copying lmaoooooo
if mtime < 315532800:
copy_metadata = False
2017-10-25 21:45:15 +00:00
if copy_metadata:
2016-08-17 20:07:22 +00:00
# this overwrites on conflict without hassle
shutil.copy2( source, dest )
2016-08-17 20:07:22 +00:00
else:
2016-06-08 20:27:22 +00:00
shutil.copy( source, dest )
def MergeFile( source, dest ):
# this can merge a file, but if it is given a dir it will just straight up overwrite not merge
2023-03-29 20:57:59 +00:00
if os.path.exists( source ) and os.path.exists( dest ) and os.path.samefile( source, dest ):
raise Exception( f'Woah, "{source}" and "{dest}" are the same file!' )
if not os.path.isdir( source ):
if PathsHaveSameSizeAndDate( source, dest ):
2016-06-08 20:27:22 +00:00
DeletePath( source )
2016-06-08 20:27:22 +00:00
return True
2016-06-08 20:27:22 +00:00
try:
# this overwrites on conflict without hassle
shutil.move( source, dest, copy_function = safe_copy2 )
except Exception as e:
HydrusData.ShowText( 'Trying to move ' + source + ' to ' + dest + ' caused the following problem:' )
HydrusData.ShowException( e )
return False
2016-06-08 20:27:22 +00:00
return True
2023-03-29 20:57:59 +00:00
2017-08-02 21:32:54 +00:00
def MergeTree( source, dest, text_update_hook = None ):
2016-08-17 20:07:22 +00:00
2023-03-29 20:57:59 +00:00
if os.path.exists( source ) and os.path.exists( dest ) and os.path.samefile( source, dest ):
raise Exception( f'Woah, "{source}" and "{dest}" are the same directory!' )
2016-08-17 20:07:22 +00:00
pauser = HydrusData.BigJobPauser()
if not os.path.exists( dest ):
2017-10-25 21:45:15 +00:00
try:
shutil.move( source, dest, copy_function = safe_copy2 )
2017-10-25 21:45:15 +00:00
except OSError:
# if there were read only files in source and this was partition to partition, the copy2 goes ok but the subsequent source unlink fails
# so, if it seems this has happened, let's just try a walking mergetree, which should be able to deal with these readonlies on a file-by-file basis
if os.path.exists( dest ):
MergeTree( source, dest, text_update_hook = text_update_hook )
2016-08-17 20:07:22 +00:00
else:
# I had a thing here that tried to optimise if dest existed but was empty, but it wasn't neat
num_errors = 0
for ( root, dirnames, filenames ) in os.walk( source ):
2016-08-17 20:07:22 +00:00
if text_update_hook is not None:
2016-08-17 20:07:22 +00:00
text_update_hook( 'Copying ' + root + '.' )
2016-08-17 20:07:22 +00:00
dest_root = root.replace( source, dest )
2017-08-02 21:32:54 +00:00
for dirname in dirnames:
pauser.Pause()
source_path = os.path.join( root, dirname )
dest_path = os.path.join( dest_root, dirname )
MakeSureDirectoryExists( dest_path )
shutil.copystat( source_path, dest_path )
2017-08-02 21:32:54 +00:00
for filename in filenames:
2016-08-17 20:07:22 +00:00
if num_errors > 5:
2016-08-17 20:07:22 +00:00
raise Exception( 'Too many errors, directory move abandoned.' )
2016-08-17 20:07:22 +00:00
pauser.Pause()
2016-08-17 20:07:22 +00:00
source_path = os.path.join( root, filename )
dest_path = os.path.join( dest_root, filename )
2016-08-17 20:07:22 +00:00
ok = MergeFile( source_path, dest_path )
if not ok:
2017-08-02 21:32:54 +00:00
num_errors += 1
2016-08-17 20:07:22 +00:00
if num_errors == 0:
DeletePath( source )
2017-03-02 02:14:56 +00:00
2016-08-17 20:07:22 +00:00
2023-03-29 20:57:59 +00:00
2016-06-01 20:04:15 +00:00
def MirrorFile( source, dest ):
2023-03-29 20:57:59 +00:00
if os.path.exists( source ) and os.path.exists( dest ) and os.path.samefile( source, dest ):
return True
2016-06-01 20:04:15 +00:00
if not PathsHaveSameSizeAndDate( source, dest ):
try:
2022-01-12 22:14:50 +00:00
TryToMakeFileWriteable( dest )
2018-06-06 21:27:02 +00:00
safe_copy2( source, dest )
2016-06-01 20:04:15 +00:00
except Exception as e:
HydrusData.ShowText( 'Trying to copy ' + source + ' to ' + dest + ' caused the following problem:' )
HydrusData.ShowException( e )
return False
return True
2017-07-12 20:03:45 +00:00
def MirrorTree( source, dest, text_update_hook = None, is_cancelled_hook = None ):
2016-01-06 21:17:20 +00:00
2023-03-29 20:57:59 +00:00
if os.path.exists( source ) and os.path.exists( dest ) and os.path.samefile( source, dest ):
return
2016-01-06 21:17:20 +00:00
pauser = HydrusData.BigJobPauser()
2016-08-17 20:07:22 +00:00
MakeSureDirectoryExists( dest )
2016-01-06 21:17:20 +00:00
2016-06-01 20:04:15 +00:00
num_errors = 0
2016-01-06 21:17:20 +00:00
for ( root, dirnames, filenames ) in os.walk( source ):
2017-07-12 20:03:45 +00:00
if is_cancelled_hook is not None and is_cancelled_hook():
return
if text_update_hook is not None:
text_update_hook( 'Copying ' + root + '.' )
2016-01-06 21:17:20 +00:00
dest_root = root.replace( source, dest )
surplus_dest_paths = { os.path.join( dest_root, dest_filename ) for dest_filename in os.listdir( dest_root ) }
for dirname in dirnames:
pauser.Pause()
source_path = os.path.join( root, dirname )
dest_path = os.path.join( dest_root, dirname )
surplus_dest_paths.discard( dest_path )
2016-08-17 20:07:22 +00:00
MakeSureDirectoryExists( dest_path )
2016-01-06 21:17:20 +00:00
shutil.copystat( source_path, dest_path )
for filename in filenames:
2016-06-01 20:04:15 +00:00
if num_errors > 5:
raise Exception( 'Too many errors, directory copy abandoned.' )
2016-01-06 21:17:20 +00:00
pauser.Pause()
source_path = os.path.join( root, filename )
dest_path = os.path.join( dest_root, filename )
surplus_dest_paths.discard( dest_path )
2016-06-01 20:04:15 +00:00
ok = MirrorFile( source_path, dest_path )
if not ok:
2016-01-06 21:17:20 +00:00
2016-06-01 20:04:15 +00:00
num_errors += 1
2016-01-06 21:17:20 +00:00
for dest_path in surplus_dest_paths:
pauser.Pause()
DeletePath( dest_path )
2023-03-29 20:57:59 +00:00
2017-06-07 22:05:15 +00:00
def OpenFileLocation( path ):
def do_it():
if HC.PLATFORM_WINDOWS:
2019-01-09 22:59:03 +00:00
cmd = [ 'explorer', '/select,', path ]
2017-06-07 22:05:15 +00:00
2019-11-20 23:10:46 +00:00
elif HC.PLATFORM_MACOS:
2017-06-07 22:05:15 +00:00
2019-01-09 22:59:03 +00:00
cmd = [ 'open', '-R', path ]
2017-06-07 22:05:15 +00:00
elif HC.PLATFORM_LINUX:
2017-12-13 22:33:07 +00:00
raise NotImplementedError( 'Linux cannot open file locations!' )
2017-06-07 22:05:15 +00:00
2020-07-15 20:52:09 +00:00
elif HC.PLATFORM_HAIKU:
raise NotImplementedError( 'Haiku cannot open file locations!' )
2017-06-07 22:05:15 +00:00
2019-01-09 22:59:03 +00:00
sbp_kwargs = HydrusData.GetSubprocessKWArgs( hide_terminal = False )
2020-06-17 21:31:54 +00:00
HydrusData.CheckProgramIsNotShuttingDown()
2019-01-09 22:59:03 +00:00
process = subprocess.Popen( cmd, **sbp_kwargs )
2017-06-07 22:05:15 +00:00
2020-06-17 21:31:54 +00:00
HydrusThreading.SubprocessCommunicate( process )
2017-06-07 22:05:15 +00:00
thread = threading.Thread( target = do_it )
thread.daemon = True
thread.start()
2016-01-06 21:17:20 +00:00
def PathsHaveSameSizeAndDate( path1, path2 ):
if os.path.exists( path1 ) and os.path.exists( path2 ):
2016-04-14 01:54:29 +00:00
same_size = os.path.getsize( path1 ) == os.path.getsize( path2 )
2016-05-18 20:07:14 +00:00
same_modified_time = int( os.path.getmtime( path1 ) ) == int( os.path.getmtime( path2 ) )
2016-01-06 21:17:20 +00:00
if same_size and same_modified_time:
return True
2018-01-24 23:09:42 +00:00
return False
def PathIsFree( path ):
try:
2019-11-20 23:10:46 +00:00
stat_result = os.stat( path )
current_bits = stat_result.st_mode
if not current_bits & stat.S_IWRITE:
# read-only file, cannot do the rename check
return True
2018-01-24 23:09:42 +00:00
os.rename( path, path ) # rename a path to itself
return True
2018-09-26 19:05:12 +00:00
except OSError as e: # 'already in use by another process' or an odd filename too long error
2018-01-24 23:09:42 +00:00
2018-09-26 19:05:12 +00:00
HydrusData.Print( 'Already in use/inaccessible: ' + path )
2018-01-24 23:09:42 +00:00
2016-01-06 21:17:20 +00:00
return False
2015-11-04 22:30:28 +00:00
def ReadFileLikeAsBlocks( f ):
next_block = f.read( HC.READ_BLOCK_SIZE )
2019-01-09 22:59:03 +00:00
while len( next_block ) > 0:
2015-11-04 22:30:28 +00:00
yield next_block
next_block = f.read( HC.READ_BLOCK_SIZE )
2015-11-25 22:00:57 +00:00
def RecyclePath( path ):
2019-07-24 21:39:02 +00:00
if HG.file_report_mode:
HydrusData.ShowText( 'Recycling {}'.format( path ) )
HydrusData.ShowText( ''.join( traceback.format_stack() ) )
2015-11-25 22:00:57 +00:00
if os.path.exists( path ):
2022-01-12 22:14:50 +00:00
TryToMakeFileWriteable( path )
2016-05-04 21:50:55 +00:00
2015-11-25 22:00:57 +00:00
try:
send2trash.send2trash( path )
except:
2016-06-08 20:27:22 +00:00
HydrusData.Print( 'Trying to recycle ' + path + ' created this error:' )
2016-10-19 20:02:56 +00:00
HydrusData.DebugPrint( traceback.format_exc() )
2015-11-25 22:00:57 +00:00
HydrusData.Print( 'It has been fully deleted instead.' )
2019-01-09 22:59:03 +00:00
DeletePath( path )
2015-11-25 22:00:57 +00:00
2017-01-18 22:52:39 +00:00
def SanitizeFilename( filename, force_ntfs = False ) -> str:
2018-09-26 19:05:12 +00:00
if HC.PLATFORM_WINDOWS or force_ntfs:
2018-09-26 19:05:12 +00:00
# \, /, :, *, ?, ", <, >, |
bad_characters = r'[\\/:*?"<>|]'
2018-09-26 19:05:12 +00:00
else:
bad_characters = '/'
2018-09-26 19:05:12 +00:00
return re.sub( bad_characters, '_', filename )
def SanitizePathForExport( directory_path, directories_and_filename ):
# this does not figure out the situation where the suffix directories cross a mount point to a new file system, but at that point it is user's job to fix
2018-09-26 19:05:12 +00:00
components = directories_and_filename.split( os.path.sep )
filename = components[-1]
suffix_directories = components[:-1]
force_ntfs = GetFileSystemType( directory_path ).lower() in ( 'ntfs', 'exfat' )
suffix_directories = [ SanitizeFilename( suffix_directory, force_ntfs = force_ntfs ) for suffix_directory in suffix_directories ]
filename = SanitizeFilename( filename, force_ntfs = force_ntfs )
sanitized_components = suffix_directories
sanitized_components.append( filename )
return os.path.join( *sanitized_components )
2022-01-12 22:14:50 +00:00
def TryToGiveFileNicePermissionBits( path ):
if not os.path.exists( path ):
return
try:
stat_result = os.stat( path )
current_bits = stat_result.st_mode
if HC.PLATFORM_WINDOWS:
# this is actually the same value as S_IWUSR, but let's not try to second guess ourselves
desired_bits = stat.S_IREAD | stat.S_IWRITE
else:
# typically guarantee 644 for regular files m8, but now we also take umask into account
try:
umask = os.umask( 0o022 )
os.umask( umask )
except:
umask = 0o022
desired_bits = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH ) & ~umask
if not ( desired_bits & current_bits ) == desired_bits:
os.chmod( path, current_bits | desired_bits )
except Exception as e:
HydrusData.Print( 'Wanted to add read and write permission to "{}", but had an error: {}'.format( path, str( e ) ) )
def TryToMakeFileWriteable( path ):
if not os.path.exists( path ):
return
if FileisWriteable( path ):
return
try:
stat_result = os.stat( path )
current_bits = stat_result.st_mode
if HC.PLATFORM_WINDOWS:
# this is actually the same value as S_IWUSR, but let's not try to second guess ourselves
desired_bits = stat.S_IREAD | stat.S_IWRITE
else:
# this only does what we want if we own the file, but only owners can non-sudo change permission anyway
desired_bits = stat.S_IWUSR
if not ( desired_bits & current_bits ) == desired_bits:
os.chmod( path, current_bits | desired_bits )
except Exception as e:
HydrusData.Print( 'Wanted to add user write permission to "{}", but had an error: {}'.format( path, str( e ) ) )