2015-11-04 22:30:28 +00:00
import gc
import HydrusConstants as HC
import HydrusData
2016-10-26 20:45:34 +00:00
import HydrusGlobals
2015-11-04 22:30:28 +00:00
import os
2016-04-14 01:54:29 +00:00
import psutil
2015-11-25 22:00:57 +00:00
import send2trash
import shutil
2016-05-04 21:50:55 +00:00
import stat
2015-11-04 22:30:28 +00:00
import subprocess
2015-11-25 22:00:57 +00:00
import sys
2015-11-04 22:30:28 +00:00
import tempfile
import threading
2015-11-25 22:00:57 +00:00
import traceback
2015-11-04 22:30:28 +00:00
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
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 :
gc . collect ( )
try :
os . remove ( temp_path )
except OSError :
2015-11-18 22:44:07 +00:00
HydrusData . Print ( ' Could not delete the temporary file ' + temp_path )
2015-11-04 22:30:28 +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 :
base_dir = HydrusGlobals . controller . GetDBDir ( )
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 :
base_dir = HydrusGlobals . controller . GetDBDir ( )
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 ) :
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
2017-03-29 19:39:34 +00:00
if ' Error 32 ' in HydrusData . ToUnicode ( 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
2016-08-24 18:36:56 +00:00
def FilterFreePaths ( paths ) :
free_paths = [ ]
for path in paths :
try :
os . rename ( path , path ) # rename a path to itself
free_paths . append ( path )
except OSError as e : # 'already in use by another process'
2016-11-09 23:13:22 +00:00
HydrusData . Print ( ' Already in use: ' + path )
2016-08-24 18:36:56 +00:00
return free_paths
2016-04-14 01:54:29 +00:00
def GetDevice ( path ) :
path = path . lower ( )
partition_infos = psutil . disk_partitions ( )
def sort_descending_mountpoint ( partition_info ) : # i.e. put '/home' before '/'
return - len ( partition_info . mountpoint )
2015-11-25 22:00:57 +00:00
2016-04-14 01:54:29 +00:00
partition_infos . sort ( key = sort_descending_mountpoint )
2016-01-06 21:17:20 +00:00
2016-04-14 01:54:29 +00:00
for partition_info in partition_infos :
if path . startswith ( partition_info . mountpoint . lower ( ) ) :
return partition_info . device
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
2015-11-04 22:30:28 +00:00
def GetTempFile ( ) : return tempfile . TemporaryFile ( )
def GetTempFileQuick ( ) : return tempfile . SpooledTemporaryFile ( max_size = 1024 * 1024 * 4 )
2016-11-30 20:24:17 +00:00
def GetTempPath ( suffix = ' ' ) :
return tempfile . mkstemp ( suffix = suffix , prefix = ' hydrus ' )
2017-03-08 23:23:12 +00:00
def HasSpaceForDBTransaction ( db_dir , num_bytes ) :
temp_dir = tempfile . gettempdir ( )
temp_disk_free_space = GetFreeSpace ( temp_dir )
a = GetDevice ( temp_dir )
b = GetDevice ( db_dir )
if GetDevice ( temp_dir ) == GetDevice ( db_dir ) :
space_needed = int ( num_bytes * 2.2 )
if temp_disk_free_space < space_needed :
return ( False , ' I believe you need about ' + HydrusData . ConvertIntToBytes ( space_needed ) + ' on your db \' s partition, which I think also holds your temporary path, but you only seem to have ' + HydrusData . ConvertIntToBytes ( temp_disk_free_space ) + ' . ' )
else :
space_needed = int ( num_bytes * 1.1 )
if temp_disk_free_space < space_needed :
return ( False , ' I believe you need about ' + HydrusData . ConvertIntToBytes ( space_needed ) + ' on your temporary path \' s partition, which I think is ' + temp_dir + ' , but you only seem to have ' + HydrusData . ConvertIntToBytes ( temp_disk_free_space ) + ' . ' )
db_disk_free_space = GetFreeSpace ( db_dir )
if db_disk_free_space < space_needed :
return ( False , ' I believe you need about ' + HydrusData . ConvertIntToBytes ( space_needed ) + ' on your db \' s partition, but you only seem to have ' + HydrusData . ConvertIntToBytes ( db_disk_free_space ) + ' . ' )
return ( True , ' You seem to have enough space! ' )
2015-11-04 22:30:28 +00:00
def LaunchDirectory ( path ) :
def do_it ( ) :
if HC . PLATFORM_WINDOWS :
os . startfile ( path )
else :
if HC . PLATFORM_OSX : cmd = [ ' open ' ]
elif HC . PLATFORM_LINUX : cmd = [ ' xdg-open ' ]
cmd . append ( path )
2017-04-12 21:46:46 +00:00
# setsid call un-childs this new process
process = subprocess . Popen ( cmd , preexec_fn = os . setsid , startupinfo = HydrusData . GetSubprocessStartupInfo ( ) )
2015-11-04 22:30:28 +00:00
process . wait ( )
process . communicate ( )
thread = threading . Thread ( target = do_it )
thread . daemon = True
thread . start ( )
def LaunchFile ( path ) :
def do_it ( ) :
if HC . PLATFORM_WINDOWS :
os . startfile ( path )
else :
if HC . PLATFORM_OSX : cmd = [ ' open ' ]
elif HC . PLATFORM_LINUX : cmd = [ ' xdg-open ' ]
cmd . append ( path )
2017-04-12 21:46:46 +00:00
# setsid call un-childs this new process
process = subprocess . Popen ( cmd , preexec_fn = os . setsid , startupinfo = HydrusData . GetSubprocessStartupInfo ( ) )
2015-11-04 22:30:28 +00:00
process . wait ( )
2017-04-12 21:46:46 +00:00
process . communicate ( )
2015-11-04 22:30:28 +00:00
thread = threading . Thread ( target = do_it )
thread . daemon = True
thread . start ( )
2016-08-17 20:07:22 +00:00
def MakeSureDirectoryExists ( path ) :
if not os . path . exists ( path ) :
os . makedirs ( path )
2016-05-04 21:50:55 +00:00
def MakeFileWritable ( path ) :
2016-08-17 20:07:22 +00:00
try :
os . chmod ( path , stat . S_IWRITE | stat . S_IREAD )
2017-01-18 22:52:39 +00:00
if os . path . isdir ( path ) :
for ( root , dirnames , filenames ) in os . walk ( path ) :
for filename in filenames :
sub_path = os . path . join ( root , filename )
os . chmod ( sub_path , stat . S_IWRITE | stat . S_IREAD )
2017-01-25 22:56:55 +00:00
except Exception as e :
2016-08-17 20:07:22 +00:00
pass
2016-05-04 21:50:55 +00:00
2016-06-08 20:27:22 +00:00
def MergeFile ( source , dest ) :
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
2016-08-17 20:07:22 +00:00
def MergeTree ( source , dest ) :
pauser = HydrusData . BigJobPauser ( )
if not os . path . exists ( dest ) :
shutil . move ( source , dest )
else :
MakeSureDirectoryExists ( dest )
num_errors = 0
for ( root , dirnames , filenames ) in os . walk ( source ) :
dest_root = root . replace ( source , dest )
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 )
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 )
ok = MergeFile ( source_path , dest_path )
if not ok :
num_errors + = 1
2017-03-02 02:14:56 +00:00
if num_errors == 0 :
DeletePath ( source )
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 :
2016-08-17 20:07:22 +00:00
# this overwrites on conflict without hassle
2016-06-01 20:04:15 +00:00
shutil . copy2 ( source , dest )
except Exception as e :
HydrusData . ShowText ( ' Trying to copy ' + source + ' to ' + dest + ' caused the following problem: ' )
HydrusData . ShowException ( e )
return False
return True
2016-01-06 21:17:20 +00:00
def MirrorTree ( source , dest ) :
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 ) :
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 )
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
return False
2015-11-04 22:30:28 +00:00
def ReadFileLikeAsBlocks ( f ) :
next_block = f . read ( HC . READ_BLOCK_SIZE )
while next_block != ' ' :
yield next_block
next_block = f . read ( HC . READ_BLOCK_SIZE )
2015-11-25 22:00:57 +00:00
def RecyclePath ( path ) :
original_path = path
if HC . PLATFORM_LINUX :
# send2trash for Linux tries to do some Python3 str() stuff in prepping non-str paths for recycling
if not isinstance ( path , str ) :
try :
path = path . encode ( sys . getfilesystemencoding ( ) )
except :
2016-06-08 20:27:22 +00:00
HydrusData . Print ( ' Trying to prepare ' + path + ' for recycling created this error: ' )
2016-10-19 20:02:56 +00:00
HydrusData . DebugPrint ( traceback . format_exc ( ) )
2015-11-25 22:00:57 +00:00
return
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. ' )
DeletePath ( original_path )
2017-01-18 22:52:39 +00:00