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