hydrus/hydrus/client/gui/exporting/ClientGUIExport.py

1005 lines
36 KiB
Python
Raw Normal View History

2022-03-23 20:57:10 +00:00
import collections
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
2020-07-29 20:52:44 +00:00
2020-04-22 21:00:35 +00:00
from hydrus.client import ClientConstants as CC
2022-01-19 21:28:59 +00:00
from hydrus.client import ClientLocation
2020-04-22 21:00:35 +00:00
from hydrus.client import ClientSearch
2021-08-11 21:14:12 +00:00
from hydrus.client import ClientThreading
2022-07-13 21:35:17 +00:00
from hydrus.client.exporting import ClientExportingFiles
2020-04-22 21:00:35 +00:00
from hydrus.client.gui import ClientGUIDialogsQuick
2021-06-23 21:11:38 +00:00
from hydrus.client.gui import ClientGUIFunctions
2020-04-22 21:00:35 +00:00
from hydrus.client.gui import ClientGUIScrolledPanels
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
2022-10-26 20:43:00 +00:00
from hydrus.client.gui.metadata import ClientGUIMetadataMigration
2020-11-25 22:22:47 +00:00
from hydrus.client.gui.search import ClientGUIACDropdown
2021-03-17 21:59:28 +00:00
from hydrus.client.gui.widgets import ClientGUICommon
2022-07-20 19:17:03 +00:00
from hydrus.client.media import ClientMedia
2022-10-26 20:43:00 +00:00
from hydrus.client.metadata import ClientMetadataMigrationExporters
from hydrus.client.metadata import ClientMetadataMigrationImporters
2021-03-17 21:59:28 +00:00
from hydrus.client.metadata import ClientTags
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
2021-05-27 00:09:06 +00:00
2022-03-23 20:57:10 +00:00
default_location_context = HG.client_controller.new_options.GetDefaultLocalLocationContext()
2021-05-27 00:09:06 +00:00
2022-01-19 21:28:59 +00:00
file_search_context = ClientSearch.FileSearchContext( location_context = default_location_context )
2021-05-27 00:09:06 +00:00
2022-10-26 20:43:00 +00:00
metadata_routers = new_options.GetDefaultExportFilesMetadataRouters()
2023-01-25 22:59:39 +00:00
if len( metadata_routers ) > 0:
message = 'You have some default metadata sidecar settings, most likely from a previous file export. They look like this:'
message += os.linesep * 2
message += os.linesep.join( [ router.ToString( pretty = True ) for router in metadata_routers ] )
message += os.linesep * 2
message += 'Do you want these in the new export folder?'
( result, cancelled ) = ClientGUIDialogsQuick.GetYesNo( self, message, no_label = 'no, I want an empty sidecar list', check_for_cancelled = True )
if cancelled:
return
if result != QW.QDialog.DialogCode.Accepted:
metadata_routers = []
2018-11-14 23:10:55 +00:00
period = 15 * 60
2022-10-26 20:43:00 +00:00
export_folder = ClientExportingFiles.ExportFolder(
name,
path,
export_type = export_type,
delete_from_client_after_export = delete_from_client_after_export,
file_search_context = file_search_context,
metadata_routers = metadata_routers,
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, ) )
2022-07-13 21:35:17 +00:00
def _ConvertExportFolderToListCtrlTuples( self, export_folder: ClientExportingFiles.ExportFolder ):
2018-11-14 23:10:55 +00:00
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
2022-09-28 17:15:23 +00:00
pretty_export_type = 'regular'
if export_type == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE:
2018-11-14 23:10:55 +00:00
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
2020-08-27 01:00:42 +00:00
last_error = export_folder.GetLastError()
2018-11-14 23:10:55 +00:00
2020-08-27 01:00:42 +00:00
display_tuple = ( name, path, pretty_export_type, pretty_file_search_context, pretty_paused, pretty_period, pretty_phrase, last_error )
sort_tuple = ( name, path, pretty_export_type, pretty_file_search_context, paused, period, phrase, last_error )
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 )
2022-03-09 22:18:23 +00:00
edited_datas = []
2018-11-14 23:10:55 +00:00
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, ) )
2022-03-09 22:18:23 +00:00
edited_datas.append( edited_export_folder )
2018-11-14 23:10:55 +00:00
else:
return
2022-03-09 22:18:23 +00:00
self._export_folders.SelectDatas( edited_datas )
2018-11-14 23:10:55 +00:00
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 ):
2022-07-13 21:35:17 +00:00
def __init__( self, parent, export_folder: ClientExportingFiles.ExportFolder ):
2018-11-14 23:10:55 +00:00
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._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
#
2022-07-13 21:35:17 +00:00
self._query_box = ClientGUICommon.StaticBox( self, 'query to export' )
self._page_key = b'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 )
#
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 )
#
2022-10-26 20:43:00 +00:00
self._metadata_routers_box = ClientGUICommon.StaticBox( self, 'sidecar exporting' )
2022-07-13 21:35:17 +00:00
2022-10-26 20:43:00 +00:00
metadata_routers = export_folder.GetMetadataRouters()
allowed_importer_classes = [ ClientMetadataMigrationImporters.SingleFileMetadataImporterMediaTags, ClientMetadataMigrationImporters.SingleFileMetadataImporterMediaURLs ]
allowed_exporter_classes = [ ClientMetadataMigrationExporters.SingleFileMetadataExporterTXT, ClientMetadataMigrationExporters.SingleFileMetadataExporterJSON ]
2022-07-13 21:35:17 +00:00
2022-10-26 20:43:00 +00:00
self._metadata_routers_button = ClientGUIMetadataMigration.SingleFileMetadataRoutersButton( self._metadata_routers_box, metadata_routers, allowed_importer_classes, allowed_exporter_classes )
2022-07-13 21:35:17 +00:00
#
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 )
2020-07-29 20:52:44 +00:00
QP.AddToLayout( phrase_hbox, self._examples, CC.FLAGS_CENTER_PERPENDICULAR )
2018-11-14 23:10:55 +00:00
self._phrase_box.Add( phrase_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
2022-10-26 20:43:00 +00:00
self._metadata_routers_box.Add( self._metadata_routers_button, CC.FLAGS_EXPAND_PERPENDICULAR )
2022-07-13 21:35:17 +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, self._path_box, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._type_box, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._period_box, CC.FLAGS_EXPAND_PERPENDICULAR )
2022-07-13 21:35:17 +00:00
QP.AddToLayout( vbox, self._query_box, CC.FLAGS_EXPAND_BOTH_WAYS )
2019-11-14 03:56:30 +00:00
QP.AddToLayout( vbox, self._phrase_box, CC.FLAGS_EXPAND_PERPENDICULAR )
2022-07-13 21:35:17 +00:00
QP.AddToLayout( vbox, self._metadata_routers_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
2022-10-26 20:43:00 +00:00
metadata_routers = self._metadata_routers_button.GetValue()
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:
2022-07-13 21:35:17 +00:00
ClientExportingFiles.ParseExportPhrase( phrase )
2018-11-14 23:10:55 +00:00
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
2020-08-27 01:00:42 +00:00
last_error = self._export_folder.GetLastError()
2022-07-13 21:35:17 +00:00
export_folder = ClientExportingFiles.ExportFolder(
2020-08-27 01:00:42 +00:00
name,
path = path,
export_type = export_type,
delete_from_client_after_export = delete_from_client_after_export,
file_search_context = file_search_context,
2022-10-26 20:43:00 +00:00
metadata_routers = metadata_routers,
2020-08-27 01:00:42 +00:00
run_regularly = run_regularly,
period = period,
phrase = phrase,
last_checked = self._last_checked,
paused = paused,
run_now = run_now,
last_error = last_error
)
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 = {}
2022-09-28 17:15:23 +00:00
self._media_to_number_indices = { media : i + 1 for ( i, media ) in enumerate( flat_media ) }
2018-11-14 23:10:55 +00:00
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
2020-10-21 22:22:10 +00:00
t = ClientGUIListBoxes.ListBoxTagsMedia( self._tags_box, ClientTags.TAG_DISPLAY_ACTUAL, 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
2022-09-28 17:15:23 +00:00
self._paths = ClientGUIListCtrl.BetterListCtrl( self, CGLC.COLUMN_LIST_EXPORT_FILES.ID, 24, self._ConvertDataToListCtrlTuples, delete_key_callback = self._DeletePaths )
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
2022-10-26 20:43:00 +00:00
metadata_routers = new_options.GetDefaultExportFilesMetadataRouters()
allowed_importer_classes = [ ClientMetadataMigrationImporters.SingleFileMetadataImporterMediaTags, ClientMetadataMigrationImporters.SingleFileMetadataImporterMediaURLs ]
allowed_exporter_classes = [ ClientMetadataMigrationExporters.SingleFileMetadataExporterTXT, ClientMetadataMigrationExporters.SingleFileMetadataExporterJSON ]
2018-11-14 23:10:55 +00:00
2022-10-26 20:43:00 +00:00
self._metadata_routers_button = ClientGUIMetadataMigration.SingleFileMetadataRoutersButton( self, metadata_routers, allowed_importer_classes, allowed_exporter_classes )
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
#
2022-07-13 21:35:17 +00:00
export_path = ClientExportingFiles.GetExportPath()
2018-11-14 23:10:55 +00:00
2021-03-17 21:59:28 +00:00
if export_path is not None:
self._directory_picker.SetPath( export_path )
2018-11-14 23:10:55 +00:00
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
2022-09-28 17:15:23 +00:00
self._paths.SetData( flat_media )
2018-11-14 23:10:55 +00:00
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 )
2020-07-29 20:52:44 +00:00
QP.AddToLayout( hbox, self._open_location, CC.FLAGS_CENTER_PERPENDICULAR )
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 )
2020-07-29 20:52:44 +00:00
QP.AddToLayout( hbox, self._update, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( hbox, self._examples, CC.FLAGS_CENTER_PERPENDICULAR )
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
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 )
2020-07-29 20:52:44 +00:00
QP.AddToLayout( vbox, self._delete_files_after_export, CC.FLAGS_ON_RIGHT )
QP.AddToLayout( vbox, self._export_symlinks, CC.FLAGS_ON_RIGHT )
2022-10-26 20:43:00 +00:00
QP.AddToLayout( vbox, self._metadata_routers_button, CC.FLAGS_ON_RIGHT )
2020-07-29 20:52:44 +00:00
QP.AddToLayout( vbox, self._export, CC.FLAGS_ON_RIGHT )
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()
2021-06-23 21:11:38 +00:00
ClientGUIFunctions.SetFocusLater( self._export )
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
self._paths.itemSelectionChanged.connect( self._RefreshTags )
2022-10-26 20:43:00 +00:00
self._metadata_routers_button.valueChanged.connect( self._MetadataRoutersUpdated )
2018-11-14 23:10:55 +00:00
if do_export_and_then_quit:
2021-06-23 21:11:38 +00:00
HG.client_controller.CallAfterQtSafe( self, 'doing export before dialog quit', self._DoExport, True )
2018-11-14 23:10:55 +00:00
2022-09-28 17:15:23 +00:00
def _ConvertDataToListCtrlTuples( self, media ):
2018-11-14 23:10:55 +00:00
2019-04-10 22:50:53 +00:00
directory = self._directory_picker.GetPath()
2022-09-28 17:15:23 +00:00
number = self._media_to_number_indices[ media ]
2018-11-14 23:10:55 +00:00
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
2022-09-28 17:15:23 +00:00
pretty_number = HydrusData.ToHumanInt( number )
2018-11-14 23:10:55 +00:00
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 )
2022-09-28 17:15:23 +00:00
def _DeletePaths( self ):
if not self._paths.HasSelected():
return
result = ClientGUIDialogsQuick.GetYesNo( self, 'Remove all selected?' )
if result == QW.QDialog.Accepted:
self._paths.DeleteSelected()
kept_media = set( self._paths.GetData() )
media_in_correct_order = [ media for ( i, media ) in sorted( ( ( i, media ) for ( media, i ) in self._media_to_number_indices.items() ) ) ]
i = 1
self._media_to_number_indices = {}
for media in media_in_correct_order:
if media in kept_media:
self._media_to_number_indices[ media ] = i
i += 1
self._paths.UpdateDatas()
2018-11-14 23:10:55 +00:00
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()
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:
2022-07-13 21:35:17 +00:00
terms = ClientExportingFiles.ParseExportPhrase( pattern )
2018-11-14 23:10:55 +00:00
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
2022-10-26 20:43:00 +00:00
metadata_routers = self._metadata_routers_button.GetValue()
2018-11-14 23:10:55 +00:00
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
2022-09-28 17:15:23 +00:00
flat_media = self._paths.GetData()
2018-11-14 23:10:55 +00:00
2022-09-28 17:15:23 +00:00
to_do = [ ( media, self._GetPath( media ) ) for media in flat_media ]
2021-08-11 21:14:12 +00:00
2018-11-14 23:10:55 +00:00
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:
2020-09-02 21:10:41 +00:00
QP.CallAfter( self.parentWidget().close )
2018-11-14 23:10:55 +00:00
2022-10-26 20:43:00 +00:00
def do_it( directory, metadata_routers, delete_afterwards, export_symlinks, quit_afterwards ):
2019-04-10 22:50:53 +00:00
2021-08-11 21:14:12 +00:00
job_key = ClientThreading.JobKey( cancellable = True )
job_key.SetStatusTitle( 'file export' )
HG.client_controller.pub( 'message', job_key )
2019-04-10 22:50:53 +00:00
pauser = HydrusData.BigJobPauser()
2018-11-14 23:10:55 +00:00
2022-09-28 17:15:23 +00:00
for ( index, ( media, path ) ) in enumerate( to_do ):
number = self._media_to_number_indices[ media ]
2021-08-11 21:14:12 +00:00
if job_key.IsCancelled():
break
2018-11-14 23:10:55 +00:00
try:
2021-08-11 21:14:12 +00:00
x_of_y = HydrusData.ConvertValueRangeToPrettyString( index + 1, num_to_do )
job_key.SetVariable( 'popup_text_1', 'Done {}'.format( x_of_y ) )
job_key.SetVariable( 'popup_gauge_1', ( index + 1, num_to_do ) )
QP.CallAfter( qt_update_label, x_of_y )
2018-11-14 23:10:55 +00:00
hash = media.GetHash()
mime = media.GetMime()
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 )
2022-10-26 20:43:00 +00:00
for metadata_router in metadata_routers:
2018-11-14 23:10:55 +00:00
2022-07-13 21:35:17 +00:00
metadata_router.Work( media.GetMediaResult(), path )
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 )
2022-01-12 22:14:50 +00:00
HydrusPaths.TryToGiveFileNicePermissionBits( path )
2019-01-23 22:19:16 +00:00
2018-11-14 23:10:55 +00:00
except:
2022-09-28 17:15:23 +00:00
QP.CallAfter( QW.QMessageBox.information, self, 'Information', 'Encountered a problem while attempting to export file with index {}:'.format( HydrusData.ToHumanInt( number + 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
2021-08-11 21:14:12 +00:00
if not job_key.IsCancelled() and delete_afterwards:
2018-11-14 23:10:55 +00:00
2019-11-14 03:56:30 +00:00
QP.CallAfter( qt_update_label, 'deleting' )
2018-11-14 23:10:55 +00:00
2022-09-28 17:15:23 +00:00
possible_deletee_medias = { media for ( media, path ) in to_do }
2021-05-19 21:30:28 +00:00
2022-07-20 19:17:03 +00:00
deletee_medias = ClientMedia.FilterAndReportDeleteLockFailures( possible_deletee_medias )
2018-11-14 23:10:55 +00:00
2022-07-13 21:35:17 +00:00
local_file_service_keys = HG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_FILE_DOMAIN, ) )
2022-03-23 20:57:10 +00:00
chunks_of_deletee_medias = HydrusData.SplitListIntoChunks( list( deletee_medias ), 64 )
2018-11-14 23:10:55 +00:00
2022-03-23 20:57:10 +00:00
for chunk_of_deletee_medias in chunks_of_deletee_medias:
reason = 'Deleted after manual export to "{}".'.format( directory )
service_keys_to_hashes = collections.defaultdict( set )
for media in chunk_of_deletee_medias:
2022-07-13 21:35:17 +00:00
for service_key in media.GetLocationsManager().GetCurrent().intersection( local_file_service_keys ):
2022-03-23 20:57:10 +00:00
2022-03-30 20:28:13 +00:00
service_keys_to_hashes[ service_key ].add( media.GetHash() )
2022-03-23 20:57:10 +00:00
2018-11-14 23:10:55 +00:00
2022-03-23 20:57:10 +00:00
for service_key in ClientLocation.ValidLocalDomainsFilter( service_keys_to_hashes.keys() ):
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, service_keys_to_hashes[ service_key ], reason = reason )
HG.client_controller.WriteSynchronous( 'content_updates', { service_key : [ content_update ] } )
2018-11-14 23:10:55 +00:00
2021-08-11 21:14:12 +00:00
job_key.DeleteVariable( 'popup_gauge_1' )
job_key.SetVariable( 'popup_text_1', 'Done!' )
job_key.Finish()
job_key.Delete( 5 )
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
2022-10-26 20:43:00 +00:00
HG.client_controller.CallToThread( do_it, directory, metadata_routers, 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
2022-07-13 21:35:17 +00:00
terms = ClientExportingFiles.ParseExportPhrase( pattern )
2022-09-28 17:15:23 +00:00
number = self._media_to_number_indices[ media ]
2018-11-14 23:10:55 +00:00
2022-09-28 17:15:23 +00:00
filename = ClientExportingFiles.GenerateExportFilename( directory, media, terms, number, do_not_use_filenames = self._existing_filenames )
2018-11-14 23:10:55 +00:00
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
2022-10-26 20:43:00 +00:00
def _MetadataRoutersUpdated( self ):
metadata_routers = self._metadata_routers_button.GetValue()
HG.client_controller.new_options.SetDefaultExportFilesMetadataRouters( metadata_routers )
2018-11-14 23:10:55 +00:00
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 ):
2022-09-28 17:15:23 +00:00
flat_media = self._paths.GetData( only_selected = True )
2018-11-14 23:10:55 +00:00
2022-09-28 17:15:23 +00:00
if len( flat_media ) == 0:
2018-11-14 23:10:55 +00:00
2022-09-28 17:15:23 +00:00
flat_media = self._paths.GetData()
2018-11-14 23:10:55 +00:00
2022-09-28 17:15:23 +00:00
self._tags_box.SetTagsByMedia( flat_media )
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 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 )