962 lines
33 KiB
Python
962 lines
33 KiB
Python
from . import ClientConstants as CC
|
|
from . import ClientExporting
|
|
from . import ClientGUIACDropdown
|
|
from . import ClientGUICommon
|
|
from . import ClientGUIDialogsQuick
|
|
from . import ClientGUIListBoxes
|
|
from . import ClientGUIListCtrl
|
|
from . import ClientGUIScrolledPanels
|
|
from . import ClientGUIScrolledPanelsEdit
|
|
from . import ClientGUITime
|
|
from . import ClientGUITopLevelWindows
|
|
from . import ClientSearch
|
|
from . import ClientTags
|
|
from . import HydrusConstants as HC
|
|
from . import HydrusData
|
|
from . import HydrusExceptions
|
|
from . import HydrusGlobals as HG
|
|
from . import HydrusPaths
|
|
import os
|
|
import time
|
|
import traceback
|
|
from qtpy import QtCore as QC
|
|
from qtpy import QtWidgets as QW
|
|
from . import QtPorting as QP
|
|
|
|
class EditExportFoldersPanel( ClientGUIScrolledPanels.EditPanel ):
|
|
|
|
def __init__( self, parent, export_folders ):
|
|
|
|
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
|
|
|
|
self._export_folders_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
|
|
|
|
columns = [ ( 'name', 20 ), ( 'path', -1 ), ( 'type', 12 ), ( 'query', 16 ), ( 'paused', 8 ), ( 'period', 16 ), ( 'phrase', 20 ) ]
|
|
|
|
self._export_folders = ClientGUIListCtrl.BetterListCtrl( self._export_folders_panel, 'export_folders', 6, 40, columns, self._ConvertExportFolderToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit )
|
|
|
|
self._export_folders_panel.SetListCtrl( self._export_folders )
|
|
|
|
self._export_folders_panel.AddButton( 'add', self._AddFolder )
|
|
self._export_folders_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
|
|
self._export_folders_panel.AddDeleteButton()
|
|
|
|
#
|
|
|
|
self._export_folders.AddDatas( export_folders )
|
|
|
|
vbox = QP.VBoxLayout()
|
|
|
|
intro = 'Here you can set the client to regularly export a certain query to a particular location.'
|
|
|
|
QP.AddToLayout( vbox, ClientGUICommon.BetterStaticText(self,intro), CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._export_folders_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self.widget().setLayout( vbox )
|
|
|
|
|
|
def _AddFolder( self ):
|
|
|
|
new_options = HG.client_controller.new_options
|
|
|
|
phrase = new_options.GetString( 'export_phrase' )
|
|
|
|
name = 'export folder'
|
|
path = ''
|
|
export_type = HC.EXPORT_FOLDER_TYPE_REGULAR
|
|
delete_from_client_after_export = False
|
|
file_search_context = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY )
|
|
period = 15 * 60
|
|
|
|
export_folder = ClientExporting.ExportFolder( name, path, export_type = export_type, delete_from_client_after_export = delete_from_client_after_export, file_search_context = file_search_context, period = period, phrase = phrase )
|
|
|
|
with ClientGUITopLevelWindows.DialogEdit( self, 'edit export folder' ) as dlg:
|
|
|
|
panel = EditExportFolderPanel( dlg, export_folder )
|
|
|
|
dlg.SetPanel( panel )
|
|
|
|
if dlg.exec() == QW.QDialog.Accepted:
|
|
|
|
export_folder = panel.GetValue()
|
|
|
|
export_folder.SetNonDupeName( self._GetExistingNames() )
|
|
|
|
self._export_folders.AddDatas( ( export_folder, ) )
|
|
|
|
|
|
|
|
|
|
def _ConvertExportFolderToListCtrlTuples( self, export_folder ):
|
|
|
|
( name, path, export_type, delete_from_client_after_export, file_search_context, run_regularly, period, phrase, last_checked, paused, run_now ) = export_folder.ToTuple()
|
|
|
|
if export_type == HC.EXPORT_FOLDER_TYPE_REGULAR:
|
|
|
|
pretty_export_type = 'regular'
|
|
|
|
elif export_type == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE:
|
|
|
|
pretty_export_type = 'synchronise'
|
|
|
|
|
|
if delete_from_client_after_export:
|
|
|
|
pretty_export_type += ' and deleting from the client!'
|
|
|
|
|
|
pretty_file_search_context = ', '.join( predicate.ToString( with_count = False ) for predicate in file_search_context.GetPredicates() )
|
|
|
|
if run_regularly:
|
|
|
|
pretty_period = HydrusData.TimeDeltaToPrettyTimeDelta( period )
|
|
|
|
else:
|
|
|
|
pretty_period = 'not running regularly'
|
|
|
|
|
|
if run_now:
|
|
|
|
pretty_period += ' (running after dialog ok)'
|
|
|
|
|
|
if paused:
|
|
|
|
pretty_paused = 'yes'
|
|
|
|
else:
|
|
|
|
pretty_paused = ''
|
|
|
|
|
|
pretty_phrase = phrase
|
|
|
|
display_tuple = ( name, path, pretty_export_type, pretty_file_search_context, pretty_paused, pretty_period, pretty_phrase )
|
|
|
|
sort_tuple = ( name, path, pretty_export_type, pretty_file_search_context, paused, period, phrase )
|
|
|
|
return ( display_tuple, sort_tuple )
|
|
|
|
|
|
def _Edit( self ):
|
|
|
|
export_folders = self._export_folders.GetData( only_selected = True )
|
|
|
|
for export_folder in export_folders:
|
|
|
|
with ClientGUITopLevelWindows.DialogEdit( self, 'edit export folder' ) as dlg:
|
|
|
|
panel = EditExportFolderPanel( dlg, export_folder )
|
|
|
|
dlg.SetPanel( panel )
|
|
|
|
if dlg.exec() == QW.QDialog.Accepted:
|
|
|
|
edited_export_folder = panel.GetValue()
|
|
|
|
self._export_folders.DeleteDatas( ( export_folder, ) )
|
|
|
|
edited_export_folder.SetNonDupeName( self._GetExistingNames() )
|
|
|
|
self._export_folders.AddDatas( ( edited_export_folder, ) )
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
def _GetExistingNames( self ):
|
|
|
|
existing_names = { export_folder.GetName() for export_folder in self._export_folders.GetData() }
|
|
|
|
return existing_names
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
export_folders = self._export_folders.GetData()
|
|
|
|
return export_folders
|
|
|
|
|
|
class EditExportFolderPanel( ClientGUIScrolledPanels.EditPanel ):
|
|
|
|
def __init__( self, parent, export_folder ):
|
|
|
|
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
|
|
|
|
self._export_folder = export_folder
|
|
|
|
( name, path, export_type, delete_from_client_after_export, file_search_context, run_regularly, period, phrase, self._last_checked, paused, run_now ) = self._export_folder.ToTuple()
|
|
|
|
self._path_box = ClientGUICommon.StaticBox( self, 'name and location' )
|
|
|
|
self._name = QW.QLineEdit( self._path_box )
|
|
|
|
self._path = QP.DirPickerCtrl( self._path_box )
|
|
|
|
#
|
|
|
|
self._type_box = ClientGUICommon.StaticBox( self, 'type of export' )
|
|
|
|
self._type = ClientGUICommon.BetterChoice( self._type_box )
|
|
self._type.addItem( 'regular', HC.EXPORT_FOLDER_TYPE_REGULAR )
|
|
self._type.addItem( 'synchronise', HC.EXPORT_FOLDER_TYPE_SYNCHRONISE )
|
|
|
|
self._delete_from_client_after_export = QW.QCheckBox( self._type_box )
|
|
|
|
#
|
|
|
|
self._query_box = ClientGUICommon.StaticBox( self, 'query to export' )
|
|
|
|
self._page_key = 'export folders placeholder'
|
|
|
|
self._tag_autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._query_box, self._page_key, file_search_context, allow_all_known_files = False, force_system_everything = True )
|
|
|
|
#
|
|
|
|
self._period_box = ClientGUICommon.StaticBox( self, 'export period' )
|
|
|
|
self._period = ClientGUITime.TimeDeltaButton( self._period_box, min = 3 * 60, days = True, hours = True, minutes = True )
|
|
|
|
self._run_regularly = QW.QCheckBox( self._period_box )
|
|
|
|
self._paused = QW.QCheckBox( self._period_box )
|
|
|
|
self._run_now = QW.QCheckBox( self._period_box )
|
|
|
|
#
|
|
|
|
self._phrase_box = ClientGUICommon.StaticBox( self, 'filenames' )
|
|
|
|
self._pattern = QW.QLineEdit( self._phrase_box )
|
|
|
|
self._examples = ClientGUICommon.ExportPatternButton( self._phrase_box )
|
|
|
|
#
|
|
|
|
self._name.setText( name )
|
|
|
|
self._path.SetPath( path )
|
|
|
|
self._type.SetValue( export_type )
|
|
|
|
self._delete_from_client_after_export.setChecked( delete_from_client_after_export )
|
|
|
|
self._period.SetValue( period )
|
|
|
|
self._run_regularly.setChecked( run_regularly )
|
|
|
|
self._paused.setChecked( paused )
|
|
|
|
self._run_now.setChecked( run_now )
|
|
|
|
self._pattern.setText( phrase )
|
|
|
|
#
|
|
|
|
rows = []
|
|
|
|
rows.append( ( 'name: ', self._name ) )
|
|
rows.append( ( 'folder path: ', self._path ) )
|
|
|
|
gridbox = ClientGUICommon.WrapInGrid( self._path_box, rows )
|
|
|
|
self._path_box.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
#
|
|
|
|
text = '''regular - try to export the files to the directory, overwriting if the filesize if different
|
|
|
|
synchronise - try to export the files to the directory, overwriting if the filesize if different, and delete anything else in the directory
|
|
|
|
If you select synchronise, be careful!'''
|
|
|
|
st = ClientGUICommon.BetterStaticText( self._type_box, label = text )
|
|
st.setWordWrap( True )
|
|
|
|
self._type_box.Add( st, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
self._type_box.Add( self._type, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
rows = []
|
|
|
|
rows.append( ( 'delete files from client after export: ', self._delete_from_client_after_export ) )
|
|
|
|
gridbox = ClientGUICommon.WrapInGrid( self._type_box, rows )
|
|
|
|
self._type_box.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
self._query_box.Add( self._tag_autocomplete )
|
|
|
|
self._period_box.Add( self._period, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
rows = []
|
|
|
|
rows.append( ( 'run regularly?: ', self._run_regularly ) )
|
|
rows.append( ( 'paused: ', self._paused ) )
|
|
rows.append( ( 'run on dialog ok: ', self._run_now ) )
|
|
|
|
gridbox = ClientGUICommon.WrapInGrid( self._period_box, rows )
|
|
|
|
self._period_box.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
phrase_hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( phrase_hbox, self._pattern, CC.FLAGS_EXPAND_BOTH_WAYS )
|
|
QP.AddToLayout( phrase_hbox, self._examples, CC.FLAGS_VCENTER )
|
|
|
|
self._phrase_box.Add( phrase_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
vbox = QP.VBoxLayout()
|
|
|
|
QP.AddToLayout( vbox, self._path_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._type_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._query_box, CC.FLAGS_EXPAND_BOTH_WAYS )
|
|
QP.AddToLayout( vbox, self._period_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._phrase_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self.widget().setLayout( vbox )
|
|
|
|
self._UpdateTypeDeleteUI()
|
|
|
|
self._type.currentIndexChanged.connect( self._UpdateTypeDeleteUI )
|
|
self._delete_from_client_after_export.clicked.connect( self.EventDeleteFilesAfterExport )
|
|
|
|
|
|
def _UpdateTypeDeleteUI( self ):
|
|
|
|
if self._type.GetValue() == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE:
|
|
|
|
self._delete_from_client_after_export.setEnabled( False )
|
|
|
|
if self._delete_from_client_after_export.isChecked():
|
|
|
|
self._delete_from_client_after_export.setChecked( False )
|
|
|
|
|
|
else:
|
|
|
|
self._delete_from_client_after_export.setEnabled( True )
|
|
|
|
|
|
|
|
def CanOK( self ):
|
|
|
|
if self._delete_from_client_after_export.isChecked():
|
|
|
|
message = 'You have set this export folder to delete the files from the client after export! Are you absolutely sure this is what you want?'
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message )
|
|
|
|
if result != QW.QDialog.Accepted:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
def EventDeleteFilesAfterExport( self ):
|
|
|
|
if self._delete_from_client_after_export.isChecked():
|
|
|
|
QW.QMessageBox.warning( self, 'Warning', 'This will delete the exported files from your client after the export! If you do not know what this means, uncheck it!' )
|
|
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
name = self._name.text()
|
|
|
|
path = self._path.GetPath()
|
|
|
|
export_type = self._type.GetValue()
|
|
|
|
delete_from_client_after_export = self._delete_from_client_after_export.isChecked()
|
|
|
|
file_search_context = self._tag_autocomplete.GetFileSearchContext()
|
|
|
|
run_regularly = self._run_regularly.isChecked()
|
|
|
|
period = self._period.GetValue()
|
|
|
|
if self._path.GetPath() in ( '', None ):
|
|
|
|
raise HydrusExceptions.VetoException( 'You must enter a folder path to export to!' )
|
|
|
|
|
|
phrase = self._pattern.text()
|
|
|
|
try:
|
|
|
|
ClientExporting.ParseExportPhrase( phrase )
|
|
|
|
except Exception as e:
|
|
|
|
raise HydrusExceptions.VetoException( 'Could not parse that export phrase! ' + str( e ) )
|
|
|
|
|
|
run_now = self._run_now.isChecked()
|
|
|
|
paused = self._paused.isChecked()
|
|
|
|
export_folder = ClientExporting.ExportFolder( name, path = path, export_type = export_type, delete_from_client_after_export = delete_from_client_after_export, file_search_context = file_search_context, run_regularly = run_regularly, period = period, phrase = phrase, last_checked = self._last_checked, paused = paused, run_now = run_now )
|
|
|
|
return export_folder
|
|
|
|
|
|
class ReviewExportFilesPanel( ClientGUIScrolledPanels.ReviewPanel ):
|
|
|
|
def __init__( self, parent, flat_media, do_export_and_then_quit = False ):
|
|
|
|
ClientGUIScrolledPanels.ReviewPanel.__init__( self, parent )
|
|
|
|
new_options = HG.client_controller.new_options
|
|
|
|
self._media_to_paths = {}
|
|
self._existing_filenames = set()
|
|
self._last_phrase_used = ''
|
|
self._last_dir_used = ''
|
|
|
|
self._tags_box = ClientGUICommon.StaticBoxSorterForListBoxTags( self, 'files\' tags' )
|
|
|
|
services_manager = HG.client_controller.services_manager
|
|
|
|
self._neighbouring_txt_tag_service_keys = services_manager.FilterValidServiceKeys( new_options.GetKeyList( 'default_neighbouring_txt_tag_service_keys' ) )
|
|
|
|
t = ClientGUIListBoxes.ListBoxTagsSelection( self._tags_box, ClientTags.TAG_DISPLAY_SIBLINGS_AND_PARENTS, include_counts = True )
|
|
|
|
self._tags_box.SetTagsBox( t )
|
|
|
|
self._tags_box.setMinimumSize( QC.QSize( 220, 300 ) )
|
|
|
|
columns = [ ( 'number', 8 ), ( 'filetype', 20 ), ( 'expected path', -1 ) ]
|
|
|
|
self._paths = ClientGUIListCtrl.BetterListCtrl( self, 'export_files', 24, 64, columns, self._ConvertDataToListCtrlTuples, use_simple_delete = True )
|
|
|
|
self._paths.Sort( 0 )
|
|
|
|
self._export_path_box = ClientGUICommon.StaticBox( self, 'export path' )
|
|
|
|
self._directory_picker = QP.DirPickerCtrl( self._export_path_box )
|
|
self._directory_picker.dirPickerChanged.connect( self._RefreshPaths )
|
|
|
|
self._open_location = QW.QPushButton( 'open this location', self._export_path_box )
|
|
self._open_location.clicked.connect( self.EventOpenLocation )
|
|
|
|
self._filenames_box = ClientGUICommon.StaticBox( self, 'filenames' )
|
|
|
|
self._pattern = QW.QLineEdit( self._filenames_box )
|
|
|
|
self._update = QW.QPushButton( 'update', self._filenames_box )
|
|
self._update.clicked.connect( self._RefreshPaths )
|
|
|
|
self._examples = ClientGUICommon.ExportPatternButton( self._filenames_box )
|
|
|
|
self._delete_files_after_export = QW.QCheckBox( 'delete files from client after export?', self )
|
|
self._delete_files_after_export.setObjectName( 'HydrusWarning' )
|
|
|
|
self._export_symlinks = QW.QCheckBox( 'EXPERIMENTAL: export symlinks', self )
|
|
self._export_symlinks.setObjectName( 'HydrusWarning' )
|
|
|
|
text = 'This will export all the files\' tags, newline separated, into .txts beside the files themselves.'
|
|
|
|
self._export_tag_txts_services_button = ClientGUICommon.BetterButton( self, 'set .txt services', self._SetTxtServices )
|
|
|
|
self._export_tag_txts = QW.QCheckBox( 'export tags to .txt files?', self )
|
|
self._export_tag_txts.setToolTip( text )
|
|
self._export_tag_txts.clicked.connect( self.EventExportTagTxtsChanged )
|
|
|
|
self._export = QW.QPushButton( 'export', self )
|
|
self._export.clicked.connect( self._DoExport )
|
|
|
|
#
|
|
|
|
export_path = ClientExporting.GetExportPath()
|
|
|
|
self._directory_picker.SetPath( export_path )
|
|
|
|
phrase = new_options.GetString( 'export_phrase' )
|
|
|
|
self._pattern.setText( phrase )
|
|
|
|
if len( self._neighbouring_txt_tag_service_keys ) > 0:
|
|
|
|
self._export_tag_txts.setChecked( True )
|
|
|
|
|
|
self._paths.SetData( list( enumerate( flat_media ) ) )
|
|
|
|
self._delete_files_after_export.setChecked( HG.client_controller.new_options.GetBoolean( 'delete_files_after_export' ) )
|
|
self._delete_files_after_export.clicked.connect( self.EventDeleteFilesChanged )
|
|
|
|
if not HG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
|
|
|
|
self._export_symlinks.setVisible( False )
|
|
|
|
|
|
#
|
|
|
|
top_hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( top_hbox, self._tags_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( top_hbox, self._paths, CC.FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( hbox, self._directory_picker, CC.FLAGS_EXPAND_BOTH_WAYS )
|
|
QP.AddToLayout( hbox, self._open_location, CC.FLAGS_VCENTER )
|
|
|
|
self._export_path_box.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( hbox, self._pattern, CC.FLAGS_EXPAND_BOTH_WAYS )
|
|
QP.AddToLayout( hbox, self._update, CC.FLAGS_VCENTER )
|
|
QP.AddToLayout( hbox, self._examples, CC.FLAGS_VCENTER )
|
|
|
|
self._filenames_box.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
txt_hbox = QP.HBoxLayout()
|
|
|
|
QP.AddToLayout( txt_hbox, self._export_tag_txts_services_button, CC.FLAGS_VCENTER )
|
|
QP.AddToLayout( txt_hbox, self._export_tag_txts, CC.FLAGS_VCENTER )
|
|
|
|
vbox = QP.VBoxLayout()
|
|
|
|
QP.AddToLayout( vbox, top_hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
|
QP.AddToLayout( vbox, self._export_path_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._filenames_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
|
QP.AddToLayout( vbox, self._delete_files_after_export, CC.FLAGS_LONE_BUTTON )
|
|
QP.AddToLayout( vbox, self._export_symlinks, CC.FLAGS_LONE_BUTTON )
|
|
QP.AddToLayout( vbox, txt_hbox, CC.FLAGS_LONE_BUTTON )
|
|
QP.AddToLayout( vbox, self._export, CC.FLAGS_LONE_BUTTON )
|
|
|
|
self.widget().setLayout( vbox )
|
|
|
|
self._RefreshTags()
|
|
|
|
self._UpdateTxtButton()
|
|
|
|
HG.client_controller.CallAfterQtSafe( self._export, self._export.setFocus, QC.Qt.OtherFocusReason)
|
|
|
|
self._paths.itemSelectionChanged.connect( self._RefreshTags )
|
|
|
|
if do_export_and_then_quit:
|
|
|
|
QP.CallAfter( self._DoExport, True )
|
|
|
|
|
|
|
|
def _ConvertDataToListCtrlTuples( self, data ):
|
|
|
|
directory = self._directory_picker.GetPath()
|
|
|
|
( ordering_index, media ) = data
|
|
|
|
number = ordering_index
|
|
mime = media.GetMime()
|
|
|
|
try:
|
|
|
|
path = self._GetPath( media )
|
|
|
|
except Exception as e:
|
|
|
|
path = str( e )
|
|
|
|
|
|
pretty_number = HydrusData.ToHumanInt( ordering_index + 1 )
|
|
pretty_mime = HC.mime_string_lookup[ mime ]
|
|
|
|
pretty_path = path
|
|
|
|
if not path.startswith( directory ):
|
|
|
|
pretty_path = 'INVALID, above destination directory: ' + path
|
|
|
|
|
|
display_tuple = ( pretty_number, pretty_mime, pretty_path )
|
|
sort_tuple = ( number, pretty_mime, path )
|
|
|
|
return ( display_tuple, sort_tuple )
|
|
|
|
|
|
def _DoExport( self, quit_afterwards = False ):
|
|
|
|
delete_afterwards = self._delete_files_after_export.isChecked()
|
|
export_symlinks = self._export_symlinks.isChecked() and not delete_afterwards
|
|
|
|
if quit_afterwards:
|
|
|
|
message = 'Export as shown?'
|
|
|
|
if delete_afterwards:
|
|
|
|
message += os.linesep * 2
|
|
message += 'THE FILES WILL BE DELETED FROM THE CLIENT AFTERWARDS'
|
|
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message )
|
|
|
|
if result != QW.QDialog.Accepted:
|
|
|
|
self.parentWidget().close()
|
|
|
|
return
|
|
|
|
|
|
elif delete_afterwards:
|
|
|
|
message = 'THE FILES WILL BE DELETED FROM THE CLIENT AFTERWARDS'
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message )
|
|
|
|
if result != QW.QDialog.Accepted:
|
|
|
|
return
|
|
|
|
|
|
|
|
self._RefreshPaths()
|
|
|
|
export_tag_txts = self._export_tag_txts.isChecked()
|
|
|
|
if self._export_tag_txts.isChecked():
|
|
|
|
neighbouring_txt_tag_service_keys = self._neighbouring_txt_tag_service_keys
|
|
|
|
else:
|
|
|
|
neighbouring_txt_tag_service_keys = []
|
|
|
|
|
|
directory = self._directory_picker.GetPath()
|
|
|
|
HydrusPaths.MakeSureDirectoryExists( directory )
|
|
|
|
pattern = self._pattern.text()
|
|
|
|
HG.client_controller.new_options.SetString( 'export_phrase', pattern )
|
|
|
|
try:
|
|
|
|
terms = ClientExporting.ParseExportPhrase( pattern )
|
|
|
|
except Exception as e:
|
|
|
|
QW.QMessageBox.critical( self, 'Error', str(e) )
|
|
|
|
return
|
|
|
|
|
|
client_files_manager = HG.client_controller.client_files_manager
|
|
|
|
self._export.setEnabled( False )
|
|
|
|
to_do = self._paths.GetData()
|
|
|
|
num_to_do = len( to_do )
|
|
|
|
def qt_update_label( text ):
|
|
|
|
if not QP.isValid( self ) or not QP.isValid( self._export ) or not self._export:
|
|
|
|
return
|
|
|
|
|
|
self._export.setText( text )
|
|
|
|
|
|
def qt_done( quit_afterwards ):
|
|
|
|
if not QP.isValid( self ) or not QP.isValid( self._export ) or not self._export:
|
|
|
|
return
|
|
|
|
|
|
self._export.setEnabled( True )
|
|
|
|
if quit_afterwards:
|
|
|
|
QP.CallAfter( self.parentWidget().close() )
|
|
|
|
|
|
|
|
def do_it( directory, neighbouring_txt_tag_service_keys, delete_afterwards, export_symlinks, quit_afterwards ):
|
|
|
|
pauser = HydrusData.BigJobPauser()
|
|
|
|
for ( index, ( ordering_index, media ) ) in enumerate( to_do ):
|
|
|
|
try:
|
|
|
|
QP.CallAfter( qt_update_label, HydrusData.ConvertValueRangeToPrettyString(index+1,num_to_do) )
|
|
|
|
hash = media.GetHash()
|
|
mime = media.GetMime()
|
|
|
|
path = self._GetPath( media )
|
|
|
|
path = os.path.normpath( path )
|
|
|
|
if not path.startswith( directory ):
|
|
|
|
raise Exception( 'It seems a destination path was above the main export directory! The file was "{}" and its destination path was "{}".'.format( hash.hex(), path ) )
|
|
|
|
|
|
path_dir = os.path.dirname( path )
|
|
|
|
HydrusPaths.MakeSureDirectoryExists( path_dir )
|
|
|
|
if export_tag_txts:
|
|
|
|
tags_manager = media.GetTagsManager()
|
|
|
|
tags = set()
|
|
|
|
for service_key in neighbouring_txt_tag_service_keys:
|
|
|
|
current_tags = tags_manager.GetCurrent( service_key, ClientTags.TAG_DISPLAY_SIBLINGS_AND_PARENTS )
|
|
|
|
tags.update( current_tags )
|
|
|
|
|
|
tags = list( tags )
|
|
|
|
tags.sort()
|
|
|
|
txt_path = path + '.txt'
|
|
|
|
with open( txt_path, 'w', encoding = 'utf-8' ) as f:
|
|
|
|
f.write( os.linesep.join( tags ) )
|
|
|
|
|
|
|
|
source_path = client_files_manager.GetFilePath( hash, mime, check_file_exists = False )
|
|
|
|
if export_symlinks:
|
|
|
|
os.symlink( source_path, path )
|
|
|
|
else:
|
|
|
|
HydrusPaths.MirrorFile( source_path, path )
|
|
|
|
HydrusPaths.MakeFileWritable( path )
|
|
|
|
|
|
except:
|
|
|
|
QP.CallAfter( QW.QMessageBox.information, self, 'Information', 'Encountered a problem while attempting to export file with index '+str(ordering_index+1)+':'+os.linesep*2+traceback.format_exc() )
|
|
|
|
break
|
|
|
|
|
|
pauser.Pause()
|
|
|
|
|
|
if delete_afterwards:
|
|
|
|
QP.CallAfter( qt_update_label, 'deleting' )
|
|
|
|
deletee_hashes = { media.GetHash() for ( ordering_index, media ) in to_do }
|
|
|
|
chunks_of_hashes = HydrusData.SplitListIntoChunks( deletee_hashes, 64 )
|
|
|
|
reason = 'Deleted after manual export to "{}".'.format( directory )
|
|
|
|
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, chunk_of_hashes, reason = reason ) for chunk_of_hashes in chunks_of_hashes ]
|
|
|
|
for content_update in content_updates:
|
|
|
|
HG.client_controller.WriteSynchronous( 'content_updates', { CC.LOCAL_FILE_SERVICE_KEY : [ content_update ] } )
|
|
|
|
|
|
|
|
QP.CallAfter( qt_update_label, 'done!' )
|
|
|
|
time.sleep( 1 )
|
|
|
|
QP.CallAfter( qt_update_label, 'export' )
|
|
|
|
QP.CallAfter( qt_done, quit_afterwards )
|
|
|
|
|
|
HG.client_controller.CallToThread( do_it, directory, neighbouring_txt_tag_service_keys, delete_afterwards, export_symlinks, quit_afterwards )
|
|
|
|
|
|
def _GetPath( self, media ):
|
|
|
|
if media in self._media_to_paths:
|
|
|
|
return self._media_to_paths[ media ]
|
|
|
|
|
|
directory = self._directory_picker.GetPath()
|
|
|
|
pattern = self._pattern.text()
|
|
|
|
terms = ClientExporting.ParseExportPhrase( pattern )
|
|
|
|
filename = ClientExporting.GenerateExportFilename( directory, media, terms )
|
|
|
|
i = 1
|
|
|
|
while filename in self._existing_filenames:
|
|
|
|
filename = ClientExporting.GenerateExportFilename( directory, media, terms, append_number = i )
|
|
|
|
i += 1
|
|
|
|
|
|
path = os.path.join( directory, filename )
|
|
|
|
path = os.path.normpath( path )
|
|
|
|
self._existing_filenames.add( filename )
|
|
self._media_to_paths[ media ] = path
|
|
|
|
return path
|
|
|
|
|
|
def _RefreshPaths( self ):
|
|
|
|
pattern = self._pattern.text()
|
|
dir_path = self._directory_picker.GetPath()
|
|
|
|
if pattern == self._last_phrase_used and dir_path == self._last_dir_used:
|
|
|
|
return
|
|
|
|
|
|
self._last_phrase_used = pattern
|
|
self._last_dir_used = dir_path
|
|
|
|
HG.client_controller.new_options.SetString( 'export_phrase', pattern )
|
|
|
|
self._existing_filenames = set()
|
|
self._media_to_paths = {}
|
|
|
|
self._paths.UpdateDatas()
|
|
|
|
|
|
def _RefreshTags( self ):
|
|
|
|
data = self._paths.GetData( only_selected = True )
|
|
|
|
if len( data ) == 0:
|
|
|
|
data = self._paths.GetData()
|
|
|
|
|
|
all_media = [ media for ( ordering_index, media ) in data ]
|
|
|
|
self._tags_box.SetTagsByMedia( all_media )
|
|
|
|
|
|
def _SetTxtServices( self ):
|
|
|
|
services_manager = HG.client_controller.services_manager
|
|
|
|
tag_services = services_manager.GetServices( HC.REAL_TAG_SERVICES )
|
|
|
|
choice_tuples = [ ( service.GetName(), service.GetServiceKey(), service.GetServiceKey() in self._neighbouring_txt_tag_service_keys ) for service in tag_services ]
|
|
|
|
with ClientGUITopLevelWindows.DialogEdit( self, 'select tag services' ) as dlg:
|
|
|
|
panel = ClientGUIScrolledPanelsEdit.EditChooseMultiple( dlg, choice_tuples )
|
|
|
|
dlg.SetPanel( panel )
|
|
|
|
if dlg.exec() == QW.QDialog.Accepted:
|
|
|
|
self._neighbouring_txt_tag_service_keys = panel.GetValue()
|
|
|
|
HG.client_controller.new_options.SetKeyList( 'default_neighbouring_txt_tag_service_keys', self._neighbouring_txt_tag_service_keys )
|
|
|
|
|
|
|
|
if len( self._neighbouring_txt_tag_service_keys ) == 0:
|
|
|
|
self._export_tag_txts.setChecked( False )
|
|
|
|
|
|
self._UpdateTxtButton()
|
|
|
|
|
|
def _UpdateTxtButton( self ):
|
|
|
|
if self._export_tag_txts.isChecked():
|
|
|
|
self._export_tag_txts_services_button.setEnabled( True )
|
|
|
|
else:
|
|
|
|
self._export_tag_txts_services_button.setEnabled( False )
|
|
|
|
|
|
if len( self._neighbouring_txt_tag_service_keys ) == 0:
|
|
|
|
tt = 'No services set.'
|
|
|
|
else:
|
|
|
|
names = [ HG.client_controller.services_manager.GetName( service_key ) for service_key in self._neighbouring_txt_tag_service_keys ]
|
|
|
|
tt = ', '.join( names )
|
|
|
|
|
|
self._export_tag_txts_services_button.setToolTip( tt )
|
|
|
|
|
|
def EventExport( self, event ):
|
|
|
|
self._DoExport()
|
|
|
|
|
|
def EventDeleteFilesChanged( self ):
|
|
|
|
value = self._delete_files_after_export.isChecked()
|
|
|
|
HG.client_controller.new_options.SetBoolean( 'delete_files_after_export', value )
|
|
|
|
if value:
|
|
|
|
self._export_symlinks.setChecked( False )
|
|
|
|
|
|
|
|
def EventExportTagTxtsChanged( self ):
|
|
|
|
turning_on = self._export_tag_txts.isChecked()
|
|
|
|
self._UpdateTxtButton()
|
|
|
|
if turning_on:
|
|
|
|
self._SetTxtServices()
|
|
|
|
else:
|
|
|
|
HG.client_controller.new_options.SetKeyList( 'default_neighbouring_txt_tag_service_keys', [] )
|
|
|
|
|
|
|
|
def EventOpenLocation( self ):
|
|
|
|
directory = self._directory_picker.GetPath()
|
|
|
|
if directory is not None and directory != '':
|
|
|
|
HydrusPaths.LaunchDirectory( directory )
|
|
|
|
|
|
|