hydrus/hydrus/client/gui/ClientGUIExport.py

1062 lines
36 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
2021-04-07 21:26:45 +00:00
from hydrus.core import HydrusTags
2020-07-29 20:52:44 +00:00
2020-04-22 21:00:35 +00:00
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientExporting
from hydrus.client import ClientSearch
from hydrus.client.gui import ClientGUIDialogsQuick
from hydrus.client.gui import ClientGUIScrolledPanels
2021-02-17 18:22:44 +00:00
from hydrus.client.gui import ClientGUITags
2020-04-22 21:00:35 +00:00
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
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
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
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, ) )
2020-08-27 01:00:42 +00:00
def _ConvertExportFolderToListCtrlTuples( self, export_folder: ClientExporting.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
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
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 )
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 ):
2020-08-27 01:00:42 +00:00
def __init__( self, parent, export_folder: ClientExporting.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._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 )
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 )
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
2020-08-27 01:00:42 +00:00
last_error = self._export_folder.GetLastError()
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,
last_error = last_error
)
2018-11-14 23:10:55 +00:00
return export_folder
2021-02-17 18:22:44 +00:00
class EditSidecarExporterPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, sidecar_exporter: ClientExporting.SidecarExporter ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._service_keys_to_tag_data = dict( sidecar_exporter.GetTagData() )
#
# ok, I guess a multi-column list of services, then tag filter and display type options
# open it, you make a new edit panel type
# add (with test for remaining services), edit, delete
#
# populate that lad
#
vbox = QP.VBoxLayout()
#QP.AddToLayout( vbox, self._tag_data_listctrl, CC.FLAGS_EXPAND_PERPENDICULAR )
self.widget().setLayout( vbox )
def GetValue( self ):
sidecar_exporter = ClientExporting.SidecarExporter( service_keys_to_tag_data = self._service_keys_to_tag_data )
return sidecar_exporter
class EditSidecarExporterTagDataPanel( ClientGUIScrolledPanels.EditPanel ):
2021-04-07 21:26:45 +00:00
def __init__( self, parent, tag_filter: HydrusTags.TagFilter, tag_display_type: int ):
2021-02-17 18:22:44 +00:00
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
#
message = 'Filter the tags you want to export here. Anything that passes this filter is exported.'
self._tag_filter = ClientGUITags.TagFilterButton( self, message, tag_filter )
self._tag_display_type = ClientGUICommon.BetterChoice( self )
self._tag_display_type.addItem( 'with siblings and parents applied', ClientTags.TAG_DISPLAY_ACTUAL )
self._tag_display_type.addItem( 'as the tags are actually stored', ClientTags.TAG_DISPLAY_STORAGE )
#
self._tag_display_type.SetValue( tag_display_type )
#
vbox = QP.VBoxLayout()
rows = []
rows.append( ( 'Tags to export: ', self._tag_filter ) )
rows.append( ( 'Type to export: ', self._tag_display_type ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.widget().setLayout( vbox )
def GetValue( self ):
tag_filter = self._tag_filter.GetValue()
tag_display_type = self._tag_display_type.GetValue()
return ( tag_filter, tag_display_type )
2018-11-14 23:10:55 +00:00
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-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
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()
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
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 )
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
txt_hbox = QP.HBoxLayout()
2019-09-25 21:34:18 +00:00
2020-07-29 20:52:44 +00:00
QP.AddToLayout( txt_hbox, self._export_tag_txts_services_button, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( txt_hbox, self._export_tag_txts, CC.FLAGS_CENTER_PERPENDICULAR )
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 )
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 )
QP.AddToLayout( vbox, txt_hbox, CC.FLAGS_ON_RIGHT )
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()
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:
2020-09-02 21:10:41 +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:
2020-10-21 22:22:10 +00:00
current_tags = tags_manager.GetCurrent( service_key, ClientTags.TAG_DISPLAY_ACTUAL )
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 )
2021-02-17 18:22:44 +00:00
HydrusPaths.MakeFileWriteable( 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-07-29 20:52:44 +00:00
try:
2019-09-25 21:34:18 +00:00
2020-07-29 20:52:44 +00:00
neighbouring_txt_tag_service_keys = ClientGUIDialogsQuick.SelectMultipleFromList( self, 'select tag services', choice_tuples )
2019-09-25 21:34:18 +00:00
2020-07-29 20:52:44 +00:00
except HydrusExceptions.CancelledException:
2019-09-25 21:34:18 +00:00
2020-07-29 20:52:44 +00:00
return
2019-09-25 21:34:18 +00:00
2020-07-29 20:52:44 +00:00
self._neighbouring_txt_tag_service_keys = neighbouring_txt_tag_service_keys
HG.client_controller.new_options.SetKeyList( 'default_neighbouring_txt_tag_service_keys', self._neighbouring_txt_tag_service_keys )
2019-09-25 21:34:18 +00:00
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 )