hydrus/include/HydrusPaths.py

634 lines
16 KiB
Python
Raw Normal View History

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