hydrus/hydrus/client/gui/ClientGUIExport.py

961 lines
34 KiB
Python
Raw Normal View History

2018-11-14 23:10:55 +00:00
import os
import time
import traceback
2020-04-22 21:00:35 +00:00
2019-11-14 03:56:30 +00:00
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
2020-04-22 21:00:35 +00:00
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientExporting
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUIACDropdown
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIDialogsQuick
from hydrus.client.gui import ClientGUIScrolledPanels
from hydrus.client.gui import ClientGUIScrolledPanelsEdit
from hydrus.client.gui import ClientGUITime
2020-04-29 21:44:12 +00:00
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
2020-04-22 21:00:35 +00:00
from hydrus.client.gui import QtPorting as QP
2020-07-15 20:52:09 +00:00
from hydrus.client.gui.lists import ClientGUIListBoxes
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
2018-11-14 23:10:55 +00:00
class EditExportFoldersPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, export_folders ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
2018-11-28 22:31:04 +00:00
self._export_folders_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
2018-11-14 23:10:55 +00:00
2020-07-15 20:52:09 +00:00
self._export_folders = ClientGUIListCtrl.BetterListCtrl( self._export_folders_panel, CGLC.COLUMN_LIST_EXPORT_FOLDERS.ID, 6, self._ConvertExportFolderToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit )
2018-11-14 23:10:55 +00:00
2018-11-28 22:31:04 +00:00
self._export_folders_panel.SetListCtrl( self._export_folders )
2018-11-14 23:10:55 +00:00
2018-11-28 22:31:04 +00:00
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()
2018-11-14 23:10:55 +00:00
2018-11-28 22:31:04 +00:00
#
2018-11-14 23:10:55 +00:00
2018-11-28 22:31:04 +00:00
self._export_folders.AddDatas( export_folders )
2018-11-14 23:10:55 +00:00
2020-07-15 20:52:09 +00:00
self._export_folders.Sort()
2020-05-06 21:31:41 +00:00
2019-11-14 03:56:30 +00:00
vbox = QP.VBoxLayout()
2018-11-14 23:10:55 +00:00
intro = 'Here you can set the client to regularly export a certain query to a particular location.'
2019-11-14 03:56:30 +00:00
QP.AddToLayout( vbox, ClientGUICommon.BetterStaticText(self,intro), CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._export_folders_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self.widget().setLayout( vbox )
2018-11-14 23:10:55 +00:00
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
2018-11-28 22:31:04 +00:00
delete_from_client_after_export = False
2018-11-14 23:10:55 +00:00
file_search_context = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY )
period = 15 * 60
2018-11-28 22:31:04 +00:00
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 )
2018-11-14 23:10:55 +00:00
2020-04-29 21:44:12 +00:00
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit export folder' ) as dlg:
2018-11-14 23:10:55 +00:00
panel = EditExportFolderPanel( dlg, export_folder )
dlg.SetPanel( panel )
2019-11-14 03:56:30 +00:00
if dlg.exec() == QW.QDialog.Accepted:
2018-11-14 23:10:55 +00:00
export_folder = panel.GetValue()
export_folder.SetNonDupeName( self._GetExistingNames() )
self._export_folders.AddDatas( ( export_folder, ) )
def _ConvertExportFolderToListCtrlTuples( self, export_folder ):
2019-05-01 21:24:42 +00:00
( name, path, export_type, delete_from_client_after_export, file_search_context, run_regularly, period, phrase, last_checked, paused, run_now ) = export_folder.ToTuple()
2018-11-14 23:10:55 +00:00
if export_type == HC.EXPORT_FOLDER_TYPE_REGULAR:
pretty_export_type = 'regular'
elif export_type == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE:
pretty_export_type = 'synchronise'
2018-11-28 22:31:04 +00:00
if delete_from_client_after_export:
pretty_export_type += ' and deleting from the client!'
2019-01-09 22:59:03 +00:00
pretty_file_search_context = ', '.join( predicate.ToString( with_count = False ) for predicate in file_search_context.GetPredicates() )
2018-11-14 23:10:55 +00:00
2019-05-01 21:24:42 +00:00
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 = ''
2018-11-14 23:10:55 +00:00
pretty_phrase = phrase
2019-05-01 21:24:42 +00:00
display_tuple = ( name, path, pretty_export_type, pretty_file_search_context, pretty_paused, pretty_period, pretty_phrase )
2018-11-14 23:10:55 +00:00
2019-05-01 21:24:42 +00:00
sort_tuple = ( name, path, pretty_export_type, pretty_file_search_context, paused, period, phrase )
2018-11-14 23:10:55 +00:00
return ( display_tuple, sort_tuple )
2018-11-28 22:31:04 +00:00
def _Edit( self ):
2018-11-14 23:10:55 +00:00
export_folders = self._export_folders.GetData( only_selected = True )
for export_folder in export_folders:
2020-04-29 21:44:12 +00:00
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit export folder' ) as dlg:
2018-11-14 23:10:55 +00:00
panel = EditExportFolderPanel( dlg, export_folder )
dlg.SetPanel( panel )
2019-11-14 03:56:30 +00:00
if dlg.exec() == QW.QDialog.Accepted:
2018-11-14 23:10:55 +00:00
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
2018-11-28 22:31:04 +00:00
def _GetExistingNames( self ):
2018-11-14 23:10:55 +00:00
2018-11-28 22:31:04 +00:00
existing_names = { export_folder.GetName() for export_folder in self._export_folders.GetData() }
2018-11-14 23:10:55 +00:00
2018-11-28 22:31:04 +00:00
return existing_names
2018-11-14 23:10:55 +00:00
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
2019-05-01 21:24:42 +00:00
( 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()
2018-11-14 23:10:55 +00:00
self._path_box = ClientGUICommon.StaticBox( self, 'name and location' )
2019-11-14 03:56:30 +00:00
self._name = QW.QLineEdit( self._path_box )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self._path = QP.DirPickerCtrl( self._path_box )
2018-11-14 23:10:55 +00:00
#
self._type_box = ClientGUICommon.StaticBox( self, 'type of export' )
self._type = ClientGUICommon.BetterChoice( self._type_box )
2019-11-14 03:56:30 +00:00
self._type.addItem( 'regular', HC.EXPORT_FOLDER_TYPE_REGULAR )
self._type.addItem( 'synchronise', HC.EXPORT_FOLDER_TYPE_SYNCHRONISE )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self._delete_from_client_after_export = QW.QCheckBox( self._type_box )
2018-11-28 22:31:04 +00:00
2018-11-14 23:10:55 +00:00
#
self._query_box = ClientGUICommon.StaticBox( self, 'query to export' )
self._page_key = 'export folders placeholder'
2020-03-18 21:35:57 +00:00
self._tag_autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._query_box, self._page_key, file_search_context, allow_all_known_files = False, force_system_everything = True )
2018-11-14 23:10:55 +00:00
#
self._period_box = ClientGUICommon.StaticBox( self, 'export period' )
self._period = ClientGUITime.TimeDeltaButton( self._period_box, min = 3 * 60, days = True, hours = True, minutes = True )
2019-11-14 03:56:30 +00:00
self._run_regularly = QW.QCheckBox( self._period_box )
2019-05-01 21:24:42 +00:00
2019-11-14 03:56:30 +00:00
self._paused = QW.QCheckBox( self._period_box )
2019-05-01 21:24:42 +00:00
2019-11-14 03:56:30 +00:00
self._run_now = QW.QCheckBox( self._period_box )
2019-05-01 21:24:42 +00:00
2018-11-14 23:10:55 +00:00
#
self._phrase_box = ClientGUICommon.StaticBox( self, 'filenames' )
2019-11-14 03:56:30 +00:00
self._pattern = QW.QLineEdit( self._phrase_box )
2018-11-14 23:10:55 +00:00
self._examples = ClientGUICommon.ExportPatternButton( self._phrase_box )
#
2019-11-14 03:56:30 +00:00
self._name.setText( name )
2018-11-14 23:10:55 +00:00
self._path.SetPath( path )
2019-07-24 21:39:02 +00:00
self._type.SetValue( export_type )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self._delete_from_client_after_export.setChecked( delete_from_client_after_export )
2018-11-28 22:31:04 +00:00
2018-11-14 23:10:55 +00:00
self._period.SetValue( period )
2019-11-14 03:56:30 +00:00
self._run_regularly.setChecked( run_regularly )
2019-05-01 21:24:42 +00:00
2019-11-14 03:56:30 +00:00
self._paused.setChecked( paused )
2019-05-01 21:24:42 +00:00
2019-11-14 03:56:30 +00:00
self._run_now.setChecked( run_now )
2019-05-01 21:24:42 +00:00
2019-11-14 03:56:30 +00:00
self._pattern.setText( phrase )
2018-11-14 23:10:55 +00:00
#
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 )
2019-11-20 23:10:46 +00:00
st.setWordWrap( True )
2018-11-14 23:10:55 +00:00
self._type_box.Add( st, CC.FLAGS_EXPAND_PERPENDICULAR )
self._type_box.Add( self._type, CC.FLAGS_EXPAND_PERPENDICULAR )
2018-11-28 22:31:04 +00:00
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 )
2020-03-11 21:52:11 +00:00
self._query_box.Add( self._tag_autocomplete )
2018-11-14 23:10:55 +00:00
self._period_box.Add( self._period, CC.FLAGS_EXPAND_PERPENDICULAR )
2019-05-01 21:24:42 +00:00
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 )
2019-11-14 03:56:30 +00:00
phrase_hbox = QP.HBoxLayout()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
QP.AddToLayout( phrase_hbox, self._pattern, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( phrase_hbox, self._examples, CC.FLAGS_VCENTER )
2018-11-14 23:10:55 +00:00
self._phrase_box.Add( phrase_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
2019-11-14 03:56:30 +00:00
vbox = QP.VBoxLayout()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
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 )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self.widget().setLayout( vbox )
2018-11-14 23:10:55 +00:00
2018-11-28 22:31:04 +00:00
self._UpdateTypeDeleteUI()
2019-11-14 03:56:30 +00:00
self._type.currentIndexChanged.connect( self._UpdateTypeDeleteUI )
self._delete_from_client_after_export.clicked.connect( self.EventDeleteFilesAfterExport )
2018-11-28 22:31:04 +00:00
def _UpdateTypeDeleteUI( self ):
2019-07-24 21:39:02 +00:00
if self._type.GetValue() == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE:
2018-11-28 22:31:04 +00:00
2019-11-14 03:56:30 +00:00
self._delete_from_client_after_export.setEnabled( False )
2018-11-28 22:31:04 +00:00
2019-11-14 03:56:30 +00:00
if self._delete_from_client_after_export.isChecked():
2018-11-28 22:31:04 +00:00
2019-11-14 03:56:30 +00:00
self._delete_from_client_after_export.setChecked( False )
2018-11-28 22:31:04 +00:00
else:
2019-11-14 03:56:30 +00:00
self._delete_from_client_after_export.setEnabled( True )
2018-11-28 22:31:04 +00:00
2020-04-29 21:44:12 +00:00
def UserIsOKToOK( self ):
2018-11-28 22:31:04 +00:00
2019-11-14 03:56:30 +00:00
if self._delete_from_client_after_export.isChecked():
2018-11-28 22:31:04 +00:00
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?'
2019-09-05 00:05:32 +00:00
result = ClientGUIDialogsQuick.GetYesNo( self, message )
2019-11-14 03:56:30 +00:00
if result != QW.QDialog.Accepted:
2018-11-28 22:31:04 +00:00
2019-09-05 00:05:32 +00:00
return False
2018-11-28 22:31:04 +00:00
return True
2019-11-14 03:56:30 +00:00
def EventDeleteFilesAfterExport( self ):
2018-11-28 22:31:04 +00:00
2019-11-14 03:56:30 +00:00
if self._delete_from_client_after_export.isChecked():
2018-11-28 22:31:04 +00:00
2019-11-14 03:56:30 +00:00
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!' )
2018-11-28 22:31:04 +00:00
2018-11-14 23:10:55 +00:00
def GetValue( self ):
2019-11-14 03:56:30 +00:00
name = self._name.text()
2018-11-14 23:10:55 +00:00
2019-01-09 22:59:03 +00:00
path = self._path.GetPath()
2018-11-14 23:10:55 +00:00
2019-07-24 21:39:02 +00:00
export_type = self._type.GetValue()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
delete_from_client_after_export = self._delete_from_client_after_export.isChecked()
2018-11-28 22:31:04 +00:00
2020-03-11 21:52:11 +00:00
file_search_context = self._tag_autocomplete.GetFileSearchContext()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
run_regularly = self._run_regularly.isChecked()
2019-05-01 21:24:42 +00:00
2018-11-14 23:10:55 +00:00
period = self._period.GetValue()
2019-12-05 05:29:32 +00:00
2018-11-14 23:10:55 +00:00
if self._path.GetPath() in ( '', None ):
raise HydrusExceptions.VetoException( 'You must enter a folder path to export to!' )
2019-11-14 03:56:30 +00:00
phrase = self._pattern.text()
2018-11-14 23:10:55 +00:00
try:
ClientExporting.ParseExportPhrase( phrase )
except Exception as e:
2019-01-09 22:59:03 +00:00
raise HydrusExceptions.VetoException( 'Could not parse that export phrase! ' + str( e ) )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
run_now = self._run_now.isChecked()
2019-05-01 21:24:42 +00:00
2019-11-14 03:56:30 +00:00
paused = self._paused.isChecked()
2019-05-01 21:24:42 +00:00
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 )
2018-11-14 23:10:55 +00:00
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 = ''
2020-03-25 21:15:57 +00:00
self._tags_box = ClientGUIListBoxes.StaticBoxSorterForListBoxTags( self, 'files\' tags' )
2018-11-14 23:10:55 +00:00
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' ) )
2020-03-25 21:15:57 +00:00
t = ClientGUIListBoxes.ListBoxTagsMedia( self._tags_box, ClientTags.TAG_DISPLAY_SIBLINGS_AND_PARENTS, include_counts = True )
2018-11-14 23:10:55 +00:00
self._tags_box.SetTagsBox( t )
2020-02-26 22:28:52 +00:00
self._tags_box.setMinimumSize( QC.QSize( 220, 300 ) )
2018-11-14 23:10:55 +00:00
2020-07-15 20:52:09 +00:00
self._paths = ClientGUIListCtrl.BetterListCtrl( self, CGLC.COLUMN_LIST_EXPORT_FILES.ID, 24, self._ConvertDataToListCtrlTuples, use_simple_delete = True )
2018-11-14 23:10:55 +00:00
2020-07-15 20:52:09 +00:00
self._paths.Sort()
2018-11-14 23:10:55 +00:00
self._export_path_box = ClientGUICommon.StaticBox( self, 'export path' )
2019-11-14 03:56:30 +00:00
self._directory_picker = QP.DirPickerCtrl( self._export_path_box )
self._directory_picker.dirPickerChanged.connect( self._RefreshPaths )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self._open_location = QW.QPushButton( 'open this location', self._export_path_box )
self._open_location.clicked.connect( self.EventOpenLocation )
2018-11-14 23:10:55 +00:00
self._filenames_box = ClientGUICommon.StaticBox( self, 'filenames' )
2019-11-14 03:56:30 +00:00
self._pattern = QW.QLineEdit( self._filenames_box )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self._update = QW.QPushButton( 'update', self._filenames_box )
self._update.clicked.connect( self._RefreshPaths )
2018-11-14 23:10:55 +00:00
self._examples = ClientGUICommon.ExportPatternButton( self._filenames_box )
2019-11-14 03:56:30 +00:00
self._delete_files_after_export = QW.QCheckBox( 'delete files from client after export?', self )
2020-02-26 22:28:52 +00:00
self._delete_files_after_export.setObjectName( 'HydrusWarning' )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self._export_symlinks = QW.QCheckBox( 'EXPERIMENTAL: export symlinks', self )
2020-02-26 22:28:52 +00:00
self._export_symlinks.setObjectName( 'HydrusWarning' )
2019-01-23 22:19:16 +00:00
2018-11-14 23:10:55 +00:00
text = 'This will export all the files\' tags, newline separated, into .txts beside the files themselves.'
2019-09-25 21:34:18 +00:00
self._export_tag_txts_services_button = ClientGUICommon.BetterButton( self, 'set .txt services', self._SetTxtServices )
2019-11-14 03:56:30 +00:00
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 )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self._export = QW.QPushButton( 'export', self )
self._export.clicked.connect( self._DoExport )
2018-11-14 23:10:55 +00:00
#
export_path = ClientExporting.GetExportPath()
self._directory_picker.SetPath( export_path )
phrase = new_options.GetString( 'export_phrase' )
2019-11-14 03:56:30 +00:00
self._pattern.setText( phrase )
2018-11-14 23:10:55 +00:00
if len( self._neighbouring_txt_tag_service_keys ) > 0:
2019-11-14 03:56:30 +00:00
self._export_tag_txts.setChecked( True )
2018-11-14 23:10:55 +00:00
self._paths.SetData( list( enumerate( flat_media ) ) )
2019-11-14 03:56:30 +00:00
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 )
2018-11-14 23:10:55 +00:00
2019-01-23 22:19:16 +00:00
if not HG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
2019-11-14 03:56:30 +00:00
self._export_symlinks.setVisible( False )
2019-01-23 22:19:16 +00:00
2018-11-14 23:10:55 +00:00
#
2019-11-14 03:56:30 +00:00
top_hbox = QP.HBoxLayout()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
QP.AddToLayout( top_hbox, self._tags_box, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( top_hbox, self._paths, CC.FLAGS_EXPAND_BOTH_WAYS )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
hbox = QP.HBoxLayout()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
QP.AddToLayout( hbox, self._directory_picker, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, self._open_location, CC.FLAGS_VCENTER )
2018-11-14 23:10:55 +00:00
self._export_path_box.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
2019-11-14 03:56:30 +00:00
hbox = QP.HBoxLayout()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
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 )
2018-11-14 23:10:55 +00:00
self._filenames_box.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
2019-11-14 03:56:30 +00:00
txt_hbox = QP.HBoxLayout()
2019-09-25 21:34:18 +00:00
2019-11-14 03:56:30 +00:00
QP.AddToLayout( txt_hbox, self._export_tag_txts_services_button, CC.FLAGS_VCENTER )
QP.AddToLayout( txt_hbox, self._export_tag_txts, CC.FLAGS_VCENTER )
2019-09-25 21:34:18 +00:00
2019-11-14 03:56:30 +00:00
vbox = QP.VBoxLayout()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
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 )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self.widget().setLayout( vbox )
2018-11-14 23:10:55 +00:00
self._RefreshTags()
2019-09-25 21:34:18 +00:00
self._UpdateTxtButton()
2020-03-11 21:52:11 +00:00
HG.client_controller.CallAfterQtSafe( self._export, self._export.setFocus, QC.Qt.OtherFocusReason)
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self._paths.itemSelectionChanged.connect( self._RefreshTags )
2018-11-14 23:10:55 +00:00
if do_export_and_then_quit:
2019-11-14 03:56:30 +00:00
QP.CallAfter( self._DoExport, True )
2018-11-14 23:10:55 +00:00
def _ConvertDataToListCtrlTuples( self, data ):
2019-04-10 22:50:53 +00:00
directory = self._directory_picker.GetPath()
2018-11-14 23:10:55 +00:00
( ordering_index, media ) = data
number = ordering_index
mime = media.GetMime()
try:
path = self._GetPath( media )
except Exception as e:
2019-01-09 22:59:03 +00:00
path = str( e )
2018-11-14 23:10:55 +00:00
pretty_number = HydrusData.ToHumanInt( ordering_index + 1 )
pretty_mime = HC.mime_string_lookup[ mime ]
2019-04-10 22:50:53 +00:00
2018-11-14 23:10:55 +00:00
pretty_path = path
2019-04-10 22:50:53 +00:00
if not path.startswith( directory ):
pretty_path = 'INVALID, above destination directory: ' + path
2018-11-14 23:10:55 +00:00
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 ):
2019-11-14 03:56:30 +00:00
delete_afterwards = self._delete_files_after_export.isChecked()
export_symlinks = self._export_symlinks.isChecked() and not delete_afterwards
2018-11-14 23:10:55 +00:00
if quit_afterwards:
message = 'Export as shown?'
if delete_afterwards:
message += os.linesep * 2
message += 'THE FILES WILL BE DELETED FROM THE CLIENT AFTERWARDS'
2019-09-05 00:05:32 +00:00
result = ClientGUIDialogsQuick.GetYesNo( self, message )
2019-11-14 03:56:30 +00:00
if result != QW.QDialog.Accepted:
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self.parentWidget().close()
2019-09-05 00:05:32 +00:00
return
2018-11-14 23:10:55 +00:00
elif delete_afterwards:
message = 'THE FILES WILL BE DELETED FROM THE CLIENT AFTERWARDS'
2019-09-05 00:05:32 +00:00
result = ClientGUIDialogsQuick.GetYesNo( self, message )
2019-11-14 03:56:30 +00:00
if result != QW.QDialog.Accepted:
2018-11-14 23:10:55 +00:00
2019-09-05 00:05:32 +00:00
return
2018-11-14 23:10:55 +00:00
self._RefreshPaths()
2019-11-14 03:56:30 +00:00
export_tag_txts = self._export_tag_txts.isChecked()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
if self._export_tag_txts.isChecked():
2019-09-25 21:34:18 +00:00
neighbouring_txt_tag_service_keys = self._neighbouring_txt_tag_service_keys
else:
neighbouring_txt_tag_service_keys = []
2018-11-14 23:10:55 +00:00
directory = self._directory_picker.GetPath()
HydrusPaths.MakeSureDirectoryExists( directory )
2019-11-14 03:56:30 +00:00
pattern = self._pattern.text()
2018-11-14 23:10:55 +00:00
2019-09-25 21:34:18 +00:00
HG.client_controller.new_options.SetString( 'export_phrase', pattern )
2018-11-14 23:10:55 +00:00
try:
terms = ClientExporting.ParseExportPhrase( pattern )
except Exception as e:
2019-11-14 03:56:30 +00:00
QW.QMessageBox.critical( self, 'Error', str(e) )
2018-11-14 23:10:55 +00:00
return
client_files_manager = HG.client_controller.client_files_manager
2019-11-14 03:56:30 +00:00
self._export.setEnabled( False )
2018-11-14 23:10:55 +00:00
to_do = self._paths.GetData()
num_to_do = len( to_do )
2019-11-14 03:56:30 +00:00
def qt_update_label( text ):
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
if not QP.isValid( self ) or not QP.isValid( self._export ) or not self._export:
2018-11-14 23:10:55 +00:00
return
2019-11-14 03:56:30 +00:00
self._export.setText( text )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
def qt_done( quit_afterwards ):
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
if not QP.isValid( self ) or not QP.isValid( self._export ) or not self._export:
2018-11-14 23:10:55 +00:00
return
2019-11-14 03:56:30 +00:00
self._export.setEnabled( True )
2018-11-14 23:10:55 +00:00
if quit_afterwards:
2019-11-14 03:56:30 +00:00
QP.CallAfter( self.parentWidget().close() )
2018-11-14 23:10:55 +00:00
2019-04-10 22:50:53 +00:00
def do_it( directory, neighbouring_txt_tag_service_keys, delete_afterwards, export_symlinks, quit_afterwards ):
pauser = HydrusData.BigJobPauser()
2018-11-14 23:10:55 +00:00
for ( index, ( ordering_index, media ) ) in enumerate( to_do ):
try:
2019-11-14 03:56:30 +00:00
QP.CallAfter( qt_update_label, HydrusData.ConvertValueRangeToPrettyString(index+1,num_to_do) )
2018-11-14 23:10:55 +00:00
hash = media.GetHash()
mime = media.GetMime()
path = self._GetPath( media )
2019-04-10 22:50:53 +00:00
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 ) )
2018-11-14 23:10:55 +00:00
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:
2019-10-02 23:38:59 +00:00
current_tags = tags_manager.GetCurrent( service_key, ClientTags.TAG_DISPLAY_SIBLINGS_AND_PARENTS )
2018-11-14 23:10:55 +00:00
tags.update( current_tags )
2020-05-13 19:03:16 +00:00
tags = sorted( tags )
2018-11-14 23:10:55 +00:00
txt_path = path + '.txt'
2019-01-16 22:40:53 +00:00
with open( txt_path, 'w', encoding = 'utf-8' ) as f:
2018-11-14 23:10:55 +00:00
2019-01-09 22:59:03 +00:00
f.write( os.linesep.join( tags ) )
2018-11-14 23:10:55 +00:00
source_path = client_files_manager.GetFilePath( hash, mime, check_file_exists = False )
2019-01-23 22:19:16 +00:00
if export_symlinks:
os.symlink( source_path, path )
else:
HydrusPaths.MirrorFile( source_path, path )
2019-04-03 22:45:57 +00:00
HydrusPaths.MakeFileWritable( path )
2019-01-23 22:19:16 +00:00
2018-11-14 23:10:55 +00:00
except:
2019-11-14 03:56:30 +00:00
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() )
2018-11-14 23:10:55 +00:00
break
2019-04-10 22:50:53 +00:00
pauser.Pause()
2018-11-14 23:10:55 +00:00
if delete_afterwards:
2019-11-14 03:56:30 +00:00
QP.CallAfter( qt_update_label, 'deleting' )
2018-11-14 23:10:55 +00:00
deletee_hashes = { media.GetHash() for ( ordering_index, media ) in to_do }
chunks_of_hashes = HydrusData.SplitListIntoChunks( deletee_hashes, 64 )
2019-04-10 22:50:53 +00:00
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 ]
2018-11-14 23:10:55 +00:00
for content_update in content_updates:
HG.client_controller.WriteSynchronous( 'content_updates', { CC.LOCAL_FILE_SERVICE_KEY : [ content_update ] } )
2019-11-14 03:56:30 +00:00
QP.CallAfter( qt_update_label, 'done!' )
2018-11-14 23:10:55 +00:00
time.sleep( 1 )
2019-11-14 03:56:30 +00:00
QP.CallAfter( qt_update_label, 'export' )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
QP.CallAfter( qt_done, quit_afterwards )
2018-11-14 23:10:55 +00:00
2019-09-25 21:34:18 +00:00
HG.client_controller.CallToThread( do_it, directory, neighbouring_txt_tag_service_keys, delete_afterwards, export_symlinks, quit_afterwards )
2018-11-14 23:10:55 +00:00
def _GetPath( self, media ):
if media in self._media_to_paths:
return self._media_to_paths[ media ]
2019-01-09 22:59:03 +00:00
directory = self._directory_picker.GetPath()
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
pattern = self._pattern.text()
2018-11-14 23:10:55 +00:00
terms = ClientExporting.ParseExportPhrase( pattern )
filename = ClientExporting.GenerateExportFilename( directory, media, terms )
i = 1
while filename in self._existing_filenames:
2019-09-05 00:05:32 +00:00
filename = ClientExporting.GenerateExportFilename( directory, media, terms, append_number = i )
2018-11-14 23:10:55 +00:00
i += 1
path = os.path.join( directory, filename )
2019-04-10 22:50:53 +00:00
path = os.path.normpath( path )
2018-11-14 23:10:55 +00:00
self._existing_filenames.add( filename )
self._media_to_paths[ media ] = path
return path
def _RefreshPaths( self ):
2019-11-14 03:56:30 +00:00
pattern = self._pattern.text()
2018-11-14 23:10:55 +00:00
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 )
2019-09-25 21:34:18 +00:00
def _SetTxtServices( self ):
services_manager = HG.client_controller.services_manager
2020-03-11 21:52:11 +00:00
tag_services = services_manager.GetServices( HC.REAL_TAG_SERVICES )
2019-09-25 21:34:18 +00:00
choice_tuples = [ ( service.GetName(), service.GetServiceKey(), service.GetServiceKey() in self._neighbouring_txt_tag_service_keys ) for service in tag_services ]
2020-04-29 21:44:12 +00:00
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'select tag services' ) as dlg:
2019-09-25 21:34:18 +00:00
panel = ClientGUIScrolledPanelsEdit.EditChooseMultiple( dlg, choice_tuples )
dlg.SetPanel( panel )
2019-11-14 03:56:30 +00:00
if dlg.exec() == QW.QDialog.Accepted:
2019-09-25 21:34:18 +00:00
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:
2019-11-14 03:56:30 +00:00
self._export_tag_txts.setChecked( False )
2019-09-25 21:34:18 +00:00
self._UpdateTxtButton()
def _UpdateTxtButton( self ):
2019-11-14 03:56:30 +00:00
if self._export_tag_txts.isChecked():
2019-09-25 21:34:18 +00:00
2019-11-14 03:56:30 +00:00
self._export_tag_txts_services_button.setEnabled( True )
2019-09-25 21:34:18 +00:00
else:
2019-11-14 03:56:30 +00:00
self._export_tag_txts_services_button.setEnabled( False )
2019-09-25 21:34:18 +00:00
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 )
2019-11-14 03:56:30 +00:00
self._export_tag_txts_services_button.setToolTip( tt )
2019-09-25 21:34:18 +00:00
2018-11-14 23:10:55 +00:00
def EventExport( self, event ):
self._DoExport()
2019-11-14 03:56:30 +00:00
def EventDeleteFilesChanged( self ):
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
value = self._delete_files_after_export.isChecked()
2019-01-23 22:19:16 +00:00
HG.client_controller.new_options.SetBoolean( 'delete_files_after_export', value )
if value:
2019-11-14 03:56:30 +00:00
self._export_symlinks.setChecked( False )
2019-01-23 22:19:16 +00:00
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
def EventExportTagTxtsChanged( self ):
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
turning_on = self._export_tag_txts.isChecked()
2019-09-25 21:34:18 +00:00
self._UpdateTxtButton()
if turning_on:
2018-11-14 23:10:55 +00:00
2019-09-25 21:34:18 +00:00
self._SetTxtServices()
2018-11-14 23:10:55 +00:00
else:
2019-09-25 21:34:18 +00:00
HG.client_controller.new_options.SetKeyList( 'default_neighbouring_txt_tag_service_keys', [] )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
def EventOpenLocation( self ):
2018-11-14 23:10:55 +00:00
directory = self._directory_picker.GetPath()
if directory is not None and directory != '':
HydrusPaths.LaunchDirectory( directory )