hydrus/hydrus/core/HydrusPaths.py

970 lines
26 KiB
Python
Raw Normal View History

2015-11-04 22:30:28 +00:00
import gc
import os
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 tempfile
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
2018-01-24 23:09:42 +00:00
TEMP_PATH_LOCK = threading.Lock()
IN_USE_TEMP_PATHS = set()
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
2020-04-08 21:10:11 +00:00
def CheckHasSpaceForDBTransaction( db_dir, num_bytes ):
if HG.no_db_temp_files:
space_needed = int( num_bytes * 1.1 )
approx_available_memory = psutil.virtual_memory().available * 4 / 5
if approx_available_memory < num_bytes:
raise Exception( 'I believe you need about ' + HydrusData.ToHumanBytes( space_needed ) + ' available memory, since you are running in no_db_temp_files mode, but you only seem to have ' + HydrusData.ToHumanBytes( approx_available_memory ) + '.' )
db_disk_free_space = GetFreeSpace( db_dir )
if db_disk_free_space < space_needed:
raise Exception( 'I believe you need about ' + HydrusData.ToHumanBytes( space_needed ) + ' on your db\'s partition, but you only seem to have ' + HydrusData.ToHumanBytes( db_disk_free_space ) + '.' )
else:
temp_dir = tempfile.gettempdir()
temp_disk_free_space = GetFreeSpace( temp_dir )
temp_and_db_on_same_device = GetDevice( temp_dir ) == GetDevice( db_dir )
if temp_and_db_on_same_device:
space_needed = int( num_bytes * 2.2 )
if temp_disk_free_space < space_needed:
raise Exception( 'I believe you need about ' + HydrusData.ToHumanBytes( space_needed ) + ' on your db\'s partition, which I think also holds your temporary path, but you only seem to have ' + HydrusData.ToHumanBytes( temp_disk_free_space ) + '.' )
else:
space_needed = int( num_bytes * 1.1 )
if temp_disk_free_space < space_needed:
raise Exception( 'I believe you need about ' + HydrusData.ToHumanBytes( space_needed ) + ' on your temporary path\'s partition, which I think is ' + temp_dir + ', but you only seem to have ' + HydrusData.ToHumanBytes( temp_disk_free_space ) + '.' )
db_disk_free_space = GetFreeSpace( db_dir )
if db_disk_free_space < space_needed:
raise Exception( 'I believe you need about ' + HydrusData.ToHumanBytes( space_needed ) + ' on your db\'s partition, but you only seem to have ' + HydrusData.ToHumanBytes( db_disk_free_space ) + '.' )
2015-11-04 22:30:28 +00:00
def CleanUpTempPath( os_file_handle, temp_path ):
try:
os.close( os_file_handle )
except OSError:
gc.collect()
try:
os.close( os_file_handle )
except OSError:
2015-11-18 22:44:07 +00:00
HydrusData.Print( 'Could not close the temporary file ' + temp_path )
2015-11-04 22:30:28 +00:00
return
try:
os.remove( temp_path )
except OSError:
2018-01-24 23:09:42 +00:00
with TEMP_PATH_LOCK:
2015-11-04 22:30:28 +00:00
2018-01-24 23:09:42 +00:00
IN_USE_TEMP_PATHS.add( ( HydrusData.GetNow(), temp_path ) )
2015-11-04 22:30:28 +00:00
2018-01-24 23:09:42 +00:00
def CleanUpOldTempPaths():
with TEMP_PATH_LOCK:
data = list( IN_USE_TEMP_PATHS )
for row in data:
2015-11-04 22:30:28 +00:00
2018-01-24 23:09:42 +00:00
( time_failed, temp_path ) = row
2015-11-04 22:30:28 +00:00
2018-01-24 23:09:42 +00:00
if HydrusData.TimeHasPassed( time_failed + 60 ):
try:
os.remove( temp_path )
IN_USE_TEMP_PATHS.discard( row )
except OSError:
if HydrusData.TimeHasPassed( time_failed + 600 ):
IN_USE_TEMP_PATHS.discard( row )
2017-11-29 21:48:23 +00:00
2015-11-04 22:30:28 +00:00
2018-01-24 23:09:42 +00:00
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 ):
2016-05-04 21:50:55 +00:00
MakeFileWritable( path )
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
2018-02-21 21:59:37 +00:00
def DirectoryIsWritable( path ):
2018-05-23 21:05:06 +00:00
if not os.path.exists( path ):
return False
2018-02-21 21:59:37 +00:00
try:
t = tempfile.TemporaryFile( dir = path )
t.close()
return True
except:
return False
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
2019-05-01 21:24:42 +00:00
def GetCurrentTempDir():
return tempfile.gettempdir()
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
2016-04-14 01:54:29 +00:00
def GetDevice( path ):
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.device
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
2017-02-08 22:27:00 +00:00
def GetFreeSpace( path ):
disk_usage = psutil.disk_usage( path )
return disk_usage.free
2018-05-09 20:23:00 +00:00
def GetTempDir( dir = None ):
2017-09-20 19:47:31 +00:00
2018-05-09 20:23:00 +00:00
return tempfile.mkdtemp( prefix = 'hydrus', dir = dir )
2017-09-20 19:47:31 +00:00
2019-06-05 19:42:39 +00:00
def SetEnvTempDir( path ):
2019-11-20 23:10:46 +00:00
if os.path.exists( path ) and not os.path.isdir( path ):
2019-06-05 19:42:39 +00:00
2019-11-20 23:10:46 +00:00
raise Exception( 'The given temp directory, "{}", does not seem to be a directory!'.format( path ) )
2019-06-05 19:42:39 +00:00
2019-11-20 23:10:46 +00:00
try:
2019-06-05 19:42:39 +00:00
2019-11-20 23:10:46 +00:00
MakeSureDirectoryExists( path )
except Exception as e:
raise Exception( 'Could not create the temp dir: {}'.format( e ) )
2019-06-05 19:42:39 +00:00
if not DirectoryIsWritable( path ):
raise Exception( 'The given temp directory, "{}", does not seem to be writable-to!'.format( path ) )
for tmp_name in ( 'TMPDIR', 'TEMP', 'TMP' ):
if tmp_name in os.environ:
2019-12-05 05:29:32 +00:00
os.environ[ tmp_name ] = path
2019-06-05 19:42:39 +00:00
tempfile.tempdir = path
2018-05-09 20:23:00 +00:00
def GetTempPath( suffix = '', dir = None ):
2017-09-20 19:47:31 +00:00
2018-05-09 20:23:00 +00:00
return tempfile.mkstemp( suffix = suffix, prefix = 'hydrus', dir = dir )
2016-11-30 20:24:17 +00:00
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 ):
2019-05-08 21:06:42 +00:00
os.makedirs( path, exist_ok = True )
2016-08-17 20:07:22 +00:00
2019-06-05 19:42:39 +00:00
def MakeFileWritable( path ):
2016-05-04 21:50:55 +00:00
2018-06-06 21:27:02 +00:00
if not os.path.exists( path ):
return
2016-08-17 20:07:22 +00:00
try:
2019-04-03 22:45:57 +00:00
stat_result = os.stat( path )
2016-08-17 20:07:22 +00:00
2019-04-03 22:45:57 +00:00
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
2019-07-17 22:10:19 +00:00
desired_bits = stat.S_IREAD | stat.S_IWRITE
2019-04-03 22:45:57 +00:00
else:
2019-07-17 22:10:19 +00:00
# guarantee 644 for regular files m8
desired_bits = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
2019-04-03 22:45:57 +00:00
2019-07-17 22:10:19 +00:00
if not ( desired_bits & current_bits ) == desired_bits:
2019-04-03 22:45:57 +00:00
2019-07-17 22:10:19 +00:00
os.chmod( path, current_bits | desired_bits )
2019-04-03 22:45:57 +00:00
2017-01-25 22:56:55 +00:00
except Exception as e:
2016-08-17 20:07:22 +00:00
2019-04-03 22:45:57 +00:00
HydrusData.Print( 'Wanted to add write permission to "{}", but had an error: {}'.format( path, str( e ) ) )
2016-08-17 20:07:22 +00:00
2016-05-04 21:50:55 +00:00
2016-06-08 20:27:22 +00:00
def MergeFile( source, dest ):
2017-10-25 21:45:15 +00:00
if not os.path.isdir( source ):
MakeFileWritable( source )
2016-08-17 20:07:22 +00:00
if PathsHaveSameSizeAndDate( source, dest ):
DeletePath( source )
else:
2016-06-08 20:27:22 +00:00
try:
2016-08-17 20:07:22 +00:00
# this overwrites on conflict without hassle
2016-06-08 20:27:22 +00:00
shutil.move( source, dest )
except Exception as e:
HydrusData.ShowText( 'Trying to move ' + source + ' to ' + dest + ' caused the following problem:' )
HydrusData.ShowException( e )
return False
return True
2017-08-02 21:32:54 +00:00
def MergeTree( source, dest, text_update_hook = None ):
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 )
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:
2017-08-02 21:32:54 +00:00
if len( os.listdir( dest ) ) == 0:
2016-08-17 20:07:22 +00:00
2017-08-02 21:32:54 +00:00
for filename in os.listdir( source ):
2016-08-17 20:07:22 +00:00
2017-08-02 21:32:54 +00:00
source_path = os.path.join( source, filename )
dest_path = os.path.join( dest, filename )
2016-08-17 20:07:22 +00:00
2017-10-25 21:45:15 +00:00
if not os.path.isdir( source_path ):
MakeFileWritable( source_path )
2017-08-02 21:32:54 +00:00
shutil.move( source_path, dest_path )
2016-08-17 20:07:22 +00:00
2017-08-02 21:32:54 +00:00
else:
num_errors = 0
for ( root, dirnames, filenames ) in os.walk( source ):
2016-08-17 20:07:22 +00:00
2017-08-02 21:32:54 +00:00
if text_update_hook is not None:
2016-08-17 20:07:22 +00:00
2017-08-02 21:32:54 +00:00
text_update_hook( 'Copying ' + root + '.' )
2016-08-17 20:07:22 +00:00
2017-08-02 21:32:54 +00:00
dest_root = root.replace( source, dest )
2016-08-17 20:07:22 +00:00
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 )
2016-08-17 20:07:22 +00:00
2017-08-02 21:32:54 +00:00
for filename in filenames:
if num_errors > 5:
raise Exception( 'Too many errors, directory move abandoned.' )
pauser.Pause()
source_path = os.path.join( root, filename )
dest_path = os.path.join( dest_root, filename )
2016-08-17 20:07:22 +00:00
2017-08-02 21:32:54 +00:00
ok = MergeFile( source_path, dest_path )
if not ok:
num_errors += 1
2016-08-17 20:07:22 +00:00
2017-08-02 21:32:54 +00:00
if num_errors == 0:
DeletePath( source )
2017-03-02 02:14:56 +00:00
2016-08-17 20:07:22 +00:00
2016-06-01 20:04:15 +00:00
def MirrorFile( source, dest ):
if not PathsHaveSameSizeAndDate( source, dest ):
try:
2018-06-06 21:27:02 +00:00
MakeFileWritable( dest )
2020-05-13 19:03:16 +00:00
copy_metadata = True
if HC.PLATFORM_WINDOWS:
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
if copy_metadata:
# this overwrites on conflict without hassle
shutil.copy2( source, dest )
else:
shutil.copy( 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
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 )
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 ):
2016-05-04 21:50:55 +00:00
MakeFileWritable( path )
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
2018-09-26 19:05:12 +00:00
def SanitizeFilename( filename ):
if HC.PLATFORM_WINDOWS:
# \, /, :, *, ?, ", <, >, |
2019-01-09 22:59:03 +00:00
filename = re.sub( r'\\|/|:|\*|\?|"|<|>|\|', '_', filename )
2018-09-26 19:05:12 +00:00
else:
2019-01-09 22:59:03 +00:00
filename = re.sub( '/', '_', filename )
2018-09-26 19:05:12 +00:00
return filename