Version 222

This commit is contained in:
Hydrus Network Developer 2016-09-07 15:01:05 -05:00
parent e5d0e791d6
commit f478efd48c
23 changed files with 1331 additions and 423 deletions

View File

@ -8,6 +8,31 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 222</h3></li>
<ul>
<li>created a 'raw url' downloader page that just downloads urls and tries to import the result. it has a 'paste urls' button to make mass import of a list of urls easy</li>
<li>fixed an options update bug when updating to v221 from any version before v220</li>
<li>added support for 'ftypqt' quicktime (usually .mov) video</li>
<li>embed button now uses system gui colours</li>
<li>embed button now puts the thumbnail of the media, if one exists, behind the 'play' button</li>
<li>sped up an inefficient existing mapping check that was slowing new pending mappings for popular tags</li>
<li>'last session' will no longer be listed on the gui session delete menu</li>
<li>cleaned up the main gui's initialisation events--a sizing bug often triggered after system reboot may be fixed</li>
<li>popup messages are initialised in a safer way</li>
<li>popup messages are dismissed in a safer way</li>
<li>popup messages will hide/restore themselves more reliably when the main gui window is minimised/restored</li>
<li>the pending popup message queue is now regularly purged of already deleted messages</li>
<li>new popup messages will no longer raise the main gui window to the top</li>
<li>subscription http errors during the gallery sync phase are now caught and handled gracefully, with exact error text written quietly to the log</li>
<li>network timeouts during successful response read are caught and converted to a hydrus network exception that will be caught and handled more reliably up the chain</li>
<li>the client's upnp daemon will now silence upnp mapping errors that are due to the router being too busy or full or any other unknown errors. a simple statement about the error and an instruction to explore the problem with the manual upnp manager will be written to the log</li>
<li>finished flexgrid refactoring</li>
<li>the new automatic flexgrid creation detects subsizers and lines them up more accurately with standard controls</li>
<li>wrapped the different sections of the 'speed and memory' options panel into staticboxes</li>
<li>wraped the misc crap up top the 'tags' options panel into a neater staticbox</li>
<li>taglists with unusual tags will copy them more reliably and present fewer invalid menu options</li>
<li>misc layout fixes</li>
</ul>
<li><h3>version 221</h3></li>
<ul>
<li>fixed the 8chan thread parser for the new sha256 file urls. legacy links should still work!</li>

View File

@ -135,6 +135,7 @@ FLAGS_BUTTON_SIZER = wx.SizerFlags( 0 ).Align( wx.ALIGN_RIGHT )
FLAGS_LONE_BUTTON = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_RIGHT )
FLAGS_VCENTER = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
FLAGS_SIZER_VCENTER = wx.SizerFlags( 0 ).Align( wx.ALIGN_CENTRE_VERTICAL )
FLAGS_VCENTER_EXPAND_DEPTH_ONLY = wx.SizerFlags( 2 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
DAY = 0
@ -203,6 +204,7 @@ else:
media_viewer_capabilities[ HC.APPLICATION_PDF ] = no_support
media_viewer_capabilities[ HC.VIDEO_FLV ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_MOV ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_MP4 ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_MKV ] = animated_full_support
media_viewer_capabilities[ HC.VIDEO_WEBM ] = animated_full_support

View File

@ -944,10 +944,9 @@ class Controller( HydrusController.HydrusController ):
self._app = wx.App()
self._app.locale = wx.Locale( wx.LANGUAGE_DEFAULT ) # Very important
self._app.locale = wx.Locale( wx.LANGUAGE_DEFAULT ) # Very important to init this here and keep it non garbage collected
# I have had this as 'suppress' before
# The default is to create exceptions, and since this stuff is usually pissy locale/missing parent stuff, we don't want to kill the boot
self._app.SetAssertMode( wx.PYAPP_ASSERT_EXCEPTION )
HydrusData.Print( 'booting controller...' )

View File

@ -8174,7 +8174,13 @@ class DB( HydrusDB.HydrusDB ):
for ( namespace_id, tag_id, hash_ids ) in pending_mappings_ids:
existing_current_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM ' + current_mappings_table_name + ' WHERE namespace_id = ? AND tag_id = ?;', ( namespace_id, tag_id ) ) }
self._c.execute( 'CREATE TABLE mem.temp_pending_hash_ids ( hash_id INTEGER );' )
self._c.executemany( 'INSERT INTO temp_pending_hash_ids ( hash_id ) VALUES ( ? );', ( ( hash_id, ) for hash_id in hash_ids ) )
existing_current_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM temp_pending_hash_ids, ' + current_mappings_table_name + ' USING ( hash_id ) WHERE namespace_id = ? AND tag_id = ?;', ( namespace_id, tag_id ) ) }
self._c.execute( 'DROP TABLE mem.temp_pending_hash_ids;' )
valid_hash_ids = set( hash_ids ).difference( existing_current_hash_ids )

View File

@ -405,7 +405,20 @@ def DAEMONUPnP( controller ):
duration = 3600
HydrusNATPunch.AddUPnPMapping( local_ip, internal_port, external_port, protocol, description, duration = duration )
try:
HydrusNATPunch.AddUPnPMapping( local_ip, internal_port, external_port, protocol, description, duration = duration )
except HydrusExceptions.FirewallException:
HydrusData.Print( 'The UPnP Daemon tried to add ' + local_ip + ':' + internal_port + '->external:' + external_port + ' but it failed due to router error. Please try it manually to get a full log of what happened.' )
return
except:
raise

View File

@ -593,6 +593,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'media_view' ][ HC.APPLICATION_PDF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_FLV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MOV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MP4 ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MPEG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MKV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
@ -635,23 +636,26 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( old_serialisable_info )
mimes = loaded_dictionary[ 'media_view' ].keys()
for mime in mimes:
if 'media_view' in loaded_dictionary:
if mime in self._dictionary[ 'media_view' ]:
mimes = loaded_dictionary[ 'media_view' ].keys()
for mime in mimes:
( default_media_show_action, default_preview_show_action, default_zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
( media_show_action, preview_show_action, zoom_in_to_fit, exact_zooms_only, scale_up_quality, scale_down_quality ) = loaded_dictionary[ 'media_view' ][ mime ]
loaded_dictionary[ 'media_view' ][ mime ] = ( media_show_action, preview_show_action, default_zoom_info )
else:
# while devving this, I discovered some u'20' stringified keys had snuck in and hung around. let's nuke them here, in case anyone else got similar
del loaded_dictionary[ 'media_view' ][ mime ]
if mime in self._dictionary[ 'media_view' ]:
( default_media_show_action, default_preview_show_action, default_zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
( media_show_action, preview_show_action, zoom_in_to_fit, exact_zooms_only, scale_up_quality, scale_down_quality ) = loaded_dictionary[ 'media_view' ][ mime ]
loaded_dictionary[ 'media_view' ][ mime ] = ( media_show_action, preview_show_action, default_zoom_info )
else:
# while devving this, I discovered some u'20' stringified keys had snuck in and hung around. let's nuke them here, in case anyone else got similar
del loaded_dictionary[ 'media_view' ][ mime ]

View File

@ -10,7 +10,7 @@ import ClientGUIDialogs
import ClientGUIDialogsManage
import ClientGUIManagement
import ClientGUIPages
import ClientGUIPanels
import ClientGUIScrolledPanels
import ClientGUITopLevelWindows
import ClientDownloading
import ClientMedia
@ -91,8 +91,9 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
self._controller.sub( self, 'NewPageImportBooru', 'new_import_booru' )
self._controller.sub( self, 'NewPageImportGallery', 'new_import_gallery' )
self._controller.sub( self, 'NewPageImportHDD', 'new_hdd_import' )
self._controller.sub( self, 'NewPageImportThreadWatcher', 'new_page_import_thread_watcher' )
self._controller.sub( self, 'NewPageImportPageOfImages', 'new_page_import_page_of_images' )
self._controller.sub( self, 'NewPageImportThreadWatcher', 'new_page_import_thread_watcher' )
self._controller.sub( self, 'NewPageImportURLs', 'new_page_import_urls' )
self._controller.sub( self, 'NewPagePetitions', 'new_page_petitions' )
self._controller.sub( self, 'NewPageQuery', 'new_page_query' )
self._controller.sub( self, 'NewPageThreadDumper', 'new_thread_dumper' )
@ -124,10 +125,6 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
self._RefreshStatusBar()
# as we are in oninit, callafter and calllater( 1ms ) are different
# later waits until the mainloop is running, I think.
# after seems to execute synchronously
default_gui_session = HC.options[ 'default_gui_session' ]
existing_session_names = self._controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION )
@ -158,11 +155,11 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
if load_a_blank_page:
wx.CallLater( 1, self._NewPageQuery, CC.LOCAL_FILE_SERVICE_KEY )
self._NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY )
else:
wx.CallLater( 1, self._LoadGUISession, default_gui_session )
self._LoadGUISession( default_gui_session )
wx.CallLater( 5 * 60 * 1000, self.SaveLastSession )
@ -834,13 +831,16 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
sessions.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'save_gui_session' ), p( 'Save Current' ) )
if len( gui_session_names ) > 0:
if len( gui_session_names ) > 0 and gui_session_names != [ 'last session' ]:
delete = wx.Menu()
for name in gui_session_names:
delete.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'delete_gui_session', name ), name )
if name != 'last session':
delete.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'delete_gui_session', name ), name )
sessions.AppendMenu( CC.ID_NULL, p( 'Delete' ), delete )
@ -895,6 +895,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
download_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'new_import_page_of_images' ), p( '&New Page of Images Download Page' ), p( 'Open a new tab to download files from generic galleries or threads.' ) )
download_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'new_import_thread_watcher' ), p( '&New Thread Watcher Page' ), p( 'Open a new tab to watch a thread.' ) )
download_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'new_import_urls' ), p( '&New URL Download Page' ), p( 'Open a new tab to download some raw urls.' ) )
submenu = wx.Menu()
@ -933,8 +934,6 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
download_popup_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'start_ipfs_download' ), p( '&An IPFS Multihash' ), p( 'Enter an IPFS multihash and attempt to import whatever is returned' ) )
download_popup_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'start_url_download' ), p( '&A Raw URL' ), p( 'Enter a normal URL and attempt to import whatever is returned' ) )
menu.AppendMenu( CC.ID_NULL, p( 'New Download Popup' ), download_popup_menu )
#
@ -1359,7 +1358,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
with ClientGUITopLevelWindows.DialogManage( self, title, frame_key ) as dlg:
panel = ClientGUIPanels.ManageOptionsPanel( dlg )
panel = ClientGUIScrolledPanels.ManageOptionsPanel( dlg )
dlg.SetPanel( panel )
@ -1491,6 +1490,13 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
self._NewPage( page_name, management_controller )
def _NewPageImportPageOfImages( self ):
management_controller = ClientGUIManagement.CreateManagementControllerImportPageOfImages()
self._NewPage( 'download', management_controller )
def _NewPageImportThreadWatcher( self ):
management_controller = ClientGUIManagement.CreateManagementControllerImportThreadWatcher()
@ -1498,11 +1504,11 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
self._NewPage( 'thread watcher', management_controller )
def _NewPageImportPageOfImages( self ):
def _NewPageImportURLs( self ):
management_controller = ClientGUIManagement.CreateManagementControllerImportPageOfImages()
management_controller = ClientGUIManagement.CreateManagementControllerImportURLs()
self._NewPage( 'download', management_controller )
self._NewPage( 'url import', management_controller )
def _NewPagePetitions( self, service_key = None ):
@ -1733,7 +1739,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, self._controller.PrepStringForDisplay( 'Review Services' ), 'review_services' )
panel = ClientGUIPanels.ReviewServices( frame, self._controller )
panel = ClientGUIScrolledPanels.ReviewServices( frame, self._controller )
frame.SetPanel( panel )
@ -1888,27 +1894,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
def _StartURLDownload( self ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter URL.' ) as dlg:
result = dlg.ShowModal()
if result == wx.ID_OK:
url = dlg.GetValue()
url_string = url
job_key = ClientThreading.JobKey( pausable = True, cancellable = True )
self._controller.pub( 'message', job_key )
self._controller.CallToThread( ClientDownloading.THREADDownloadURL, job_key, url, url_string )
def _StartYoutubeDownload( self ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter YouTube URL.' ) as dlg:
@ -2405,8 +2390,9 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._NewPageImportGallery( gallery_identifier )
elif command == 'new_import_thread_watcher': self._NewPageImportThreadWatcher()
elif command == 'new_import_page_of_images': self._NewPageImportPageOfImages()
elif command == 'new_import_thread_watcher': self._NewPageImportThreadWatcher()
elif command == 'new_import_urls': self._NewPageImportURLs()
elif command == 'new_page':
with ClientGUIDialogs.DialogPageChooser( self ) as dlg: dlg.ShowModal()
@ -2455,7 +2441,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
elif command == 'site': webbrowser.open( 'https://hydrusnetwork.github.io/hydrus/' )
elif command == 'start_ipfs_download': self._StartIPFSDownload()
elif command == 'start_url_download': self._StartURLDownload()
elif command == 'start_youtube_download': self._StartYoutubeDownload()
elif command == 'stats': self._Stats( data )
elif command == 'synchronised_wait_switch': self._SetSynchronisedWait()
@ -2638,9 +2623,11 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._NewPage( 'import', management_controller )
def NewPageImportPageOfImages( self ): self._NewPageImportPageOfImages()
def NewPageImportThreadWatcher( self ): self._NewPageImportThreadWatcher()
def NewPageImportPageOfImages( self ): self._NewPageImportPageOfImages()
def NewPageImportURLs( self ): self._NewPageImportURLs()
def NewPagePetitions( self, service_key ): self._NewPagePetitions( service_key )

View File

@ -7,7 +7,7 @@ import ClientGUICommon
import ClientGUIDialogs
import ClientGUIDialogsManage
import ClientGUIHoverFrames
import ClientGUIPanels
import ClientGUIScrolledPanels
import ClientGUITopLevelWindows
import ClientMedia
import ClientRatings
@ -1161,7 +1161,7 @@ class Canvas( wx.Window ):
manage_tags = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIPanels.ManageTagsPanel( manage_tags, self._file_service_key, ( self._current_display_media, ), immediate_commit = True, canvas_key = self._canvas_key )
panel = ClientGUIScrolledPanels.ManageTagsPanel( manage_tags, self._file_service_key, ( self._current_display_media, ), immediate_commit = True, canvas_key = self._canvas_key )
manage_tags.SetPanel( panel )
@ -3589,7 +3589,7 @@ class MediaContainer( wx.Window ):
if do_embed_button and self._show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED ):
self._embed_button = EmbedButton( self, media_initial_size )
self._embed_button = EmbedButton( self, self._media, media_initial_size )
self._embed_button.Bind( wx.EVT_LEFT_DOWN, self.EventEmbedButton )
return
@ -3784,16 +3784,31 @@ class MediaContainer( wx.Window ):
class EmbedButton( wx.Window ):
def __init__( self, parent, size ):
def __init__( self, parent, media, size ):
wx.Window.__init__( self, parent, size = size )
self._media = media
self._dirty = True
( x, y ) = size
self._canvas_bmp = wx.EmptyBitmap( x, y, 24 )
if self._media.GetLocationsManager().HasLocal() and self._media.GetMime() in HC.MIMES_WITH_THUMBNAILS:
hash = self._media.GetHash()
thumbnail_path = HydrusGlobals.client_controller.GetClientFilesManager().GetFullSizeThumbnailPath( hash )
self._thumbnail_bmp = ClientRendering.GenerateHydrusBitmap( thumbnail_path ).GetWxBitmap()
else:
self._thumbnail_bmp = None
self.SetCursor( wx.StockCursor( wx.CURSOR_HAND ) )
self.Bind( wx.EVT_PAINT, self.EventPaint )
@ -3805,41 +3820,63 @@ class EmbedButton( wx.Window ):
( x, y ) = self.GetClientSize()
background_brush = wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) )
dc.SetBackground( background_brush )
dc.Clear()
center_x = x / 2
center_y = y / 2
radius = min( center_x, center_y ) - 5
radius = min( 50, center_x, center_y ) - 5
dc.SetPen( wx.TRANSPARENT_PEN )
if self._thumbnail_bmp is not None:
# animations will have the animation bar space underneath in this case, so colour it in
dc.SetBackground( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
dc.Clear()
( thumb_width, thumb_height ) = self._thumbnail_bmp.GetSize()
scale = x / float( thumb_width )
dc.SetUserScale( scale, scale )
dc.DrawBitmap( self._thumbnail_bmp, 0, 0 )
dc.SetUserScale( 1.0, 1.0 )
else:
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
dc.Clear()
dc.SetBrush( wx.Brush( wx.Colour( 235, 235, 235 ) ) )
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) ) )
dc.DrawCircle( center_x, center_y, radius )
dc.SetBrush( background_brush )
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) ) )
m = ( 2 ** 0.5 ) / 2 # 45 degree angle
# play symbol is a an equilateral triangle
half_radius = radius / 2
triangle_side = radius * 0.8
angle_half_radius = m * half_radius
half_triangle_side = int( triangle_side / 2 )
cos30 = 0.866
triangle_width = triangle_side * 0.866
third_triangle_width = int( triangle_width / 3 )
points = []
points.append( ( center_x - angle_half_radius, center_y - angle_half_radius ) )
points.append( ( center_x + half_radius, center_y ) )
points.append( ( center_x - angle_half_radius, center_y + angle_half_radius ) )
points.append( ( center_x - third_triangle_width, center_y - half_triangle_side ) )
points.append( ( center_x + third_triangle_width * 2, center_y ) )
points.append( ( center_x - third_triangle_width, center_y + half_triangle_side ) )
dc.DrawPolygon( points )
#
dc.SetPen( wx.Pen( wx.Colour( 215, 215, 215 ) ) )
dc.SetPen( wx.Pen( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNSHADOW ) ) )
dc.SetBrush( wx.TRANSPARENT_BRUSH )

View File

@ -81,6 +81,7 @@ def WrapInGrid( parent, rows, expand_text = False ):
text_flags = CC.FLAGS_VCENTER_EXPAND_DEPTH_ONLY # Trying to expand both ways nixes the center. This seems to work right.
control_flags = CC.FLAGS_VCENTER
sizer_flags = CC.FLAGS_SIZER_VCENTER
else:
@ -88,12 +89,22 @@ def WrapInGrid( parent, rows, expand_text = False ):
text_flags = CC.FLAGS_VCENTER
control_flags = CC.FLAGS_EXPAND_BOTH_WAYS
sizer_flags = CC.FLAGS_EXPAND_SIZER_BOTH_WAYS
for ( text, control ) in rows:
if isinstance( control, wx.Sizer ):
cflags = sizer_flags
else:
cflags = control_flags
gridbox.AddF( wx.StaticText( parent, label = text ), text_flags )
gridbox.AddF( control, control_flags )
gridbox.AddF( control, cflags )
return gridbox
@ -2398,6 +2409,16 @@ class ListBox( wx.ScrolledWindow ):
def _GetTextColour( self, text ): return ( 0, 111, 250 )
def _HandleClick( self, event ):
hit_index = self._GetIndexUnderMouse( event )
shift = event.ShiftDown()
ctrl = event.CmdDown()
self._Hit( shift, ctrl, hit_index )
def _Hit( self, shift, ctrl, hit_index ):
if hit_index is not None:
@ -2690,12 +2711,7 @@ class ListBox( wx.ScrolledWindow ):
def EventMouseSelect( self, event ):
hit_index = self._GetIndexUnderMouse( event )
shift = event.ShiftDown()
ctrl = event.CmdDown()
self._Hit( shift, ctrl, hit_index )
self._HandleClick( event )
event.Skip()
@ -2761,6 +2777,8 @@ class ListBoxTags( ListBox ):
has_counts = False
can_spawn_new_windows = True
def __init__( self, *args, **kwargs ):
ListBox.__init__( self, *args, **kwargs )
@ -2856,7 +2874,7 @@ class ListBoxTags( ListBox ):
else:
text = term
text = HydrusData.ToUnicode( term )
if command == 'copy_sub_terms' and ':' in text:
@ -2920,27 +2938,20 @@ class ListBoxTags( ListBox ):
def EventMouseMiddleClick( self, event ):
hit_index = self._GetIndexUnderMouse( event )
self._HandleClick( event )
shift = event.ShiftDown()
ctrl = event.CmdDown()
self._Hit( shift, ctrl, hit_index )
self._NewSearchPage()
if self.can_spawn_new_windows:
self._NewSearchPage()
def EventMouseRightClick( self, event ):
hit_index = self._GetIndexUnderMouse( event )
shift = event.ShiftDown()
ctrl = event.CmdDown()
self._Hit( shift, ctrl, hit_index )
self._HandleClick( event )
if len( self._ordered_strings ) > 0:
menu = wx.Menu()
if len( self._selected_terms ) > 0:
@ -2962,7 +2973,7 @@ class ListBoxTags( ListBox ):
else:
selection_string = '"' + term + '"'
selection_string = '"' + HydrusData.ToUnicode( term ) + '"'
else:
@ -3005,9 +3016,15 @@ class ListBoxTags( ListBox ):
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'new_search_page' ), 'open a new search page for ' + selection_string )
if self.can_spawn_new_windows:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'new_search_page' ), 'open a new search page for ' + selection_string )
menu.AppendSeparator()
if menu.GetMenuItemCount() > 0:
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_terms' ), 'copy ' + selection_string )
@ -3037,7 +3054,7 @@ class ListBoxTags( ListBox ):
if self.has_counts: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_all_tags_with_counts' ), 'copy all tags with counts' )
if len( self._selected_terms ) > 0:
if self.can_spawn_new_windows and len( self._selected_terms ) > 0:
term_types = [ type( term ) for term in self._selected_terms ]
@ -3391,6 +3408,8 @@ class ListBoxTagsCensorship( ListBoxTags ):
class ListBoxTagsColourOptions( ListBoxTags ):
can_spawn_new_windows = False
def __init__( self, parent, initial_namespace_colours ):
ListBoxTags.__init__( self, parent )
@ -4381,9 +4400,15 @@ class PopupWindow( wx.Window ):
self.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
def TryToDismiss( self ): self.GetParent().Dismiss( self )
def TryToDismiss( self ):
self.GetParent().Dismiss( self )
def EventDismiss( self, event ): self.TryToDismiss()
def EventDismiss( self, event ):
self.TryToDismiss()
class PopupDismissAll( PopupWindow ):
@ -4406,11 +4431,20 @@ class PopupDismissAll( PopupWindow ):
self.SetSizer( hbox )
def TryToDismiss( self ): pass
def TryToDismiss( self ):
pass
def EventButton( self, event ): self.GetParent().DismissAll()
def EventButton( self, event ):
self.GetParent().DismissAll()
def SetNumMessages( self, num_messages_pending ): self._text.SetLabelText( HydrusData.ConvertIntToPrettyString( num_messages_pending ) + ' more messages' )
def SetNumMessages( self, num_messages_pending ):
self._text.SetLabelText( HydrusData.ConvertIntToPrettyString( num_messages_pending ) + ' more messages' )
class PopupMessage( PopupWindow ):
@ -4641,20 +4675,24 @@ class PopupMessage( PopupWindow ):
return self._job_key
def TryToDismiss( self ):
def IsDeleted( self ):
if self._job_key.IsPausable() or self._job_key.IsCancellable(): return
else: PopupWindow.TryToDismiss( self )
return self._job_key.IsDeleted()
def Update( self ):
def TryToDismiss( self ):
if self._job_key.IsDeleted():
self.TryToDismiss()
if self._job_key.IsPausable() or self._job_key.IsCancellable():
return
else:
PopupWindow.TryToDismiss( self )
def Update( self ):
if self._job_key.HasVariable( 'popup_title' ):
@ -4858,6 +4896,8 @@ class PopupMessageManager( wx.Frame ):
num_messages_displayed = self._message_vbox.GetItemCount()
self._pending_job_keys = [ job_key for job_key in self._pending_job_keys if not job_key.IsDeleted() ]
if len( self._pending_job_keys ) > 0 and num_messages_displayed < self._max_messages_to_display:
job_key = self._pending_job_keys.pop( 0 )
@ -4901,39 +4941,60 @@ class PopupMessageManager( wx.Frame ):
try:
self.Fit()
parent = self.GetParent()
( parent_width, parent_height ) = parent.GetClientSize()
( my_width, my_height ) = self.GetClientSize()
my_x = ( parent_width - my_width ) - 5
my_y = ( parent_height - my_height ) - 15
self.SetPosition( parent.ClientToScreenXY( my_x, my_y ) )
do_restore = False
do_show = False
num_messages_displayed = self._message_vbox.GetItemCount()
if num_messages_displayed > 0:
if self.GetParent().IsIconized():
current_focus = wx.Window.FindFocus()
tlp = wx.GetTopLevelParent( current_focus )
show_happened = self.Show()
if show_happened and tlp is not None:
if not self.IsIconized():
self.Raise()
tlp.Raise()
self.Iconize()
else:
self.Hide()
if self.IsIconized():
self.Restore()
if num_messages_displayed > 0:
if not self.IsShown():
do_show = True
else:
if self.IsShown():
self.Hide()
if do_show or self.IsShown():
self.Fit()
parent = self.GetParent()
( parent_width, parent_height ) = parent.GetClientSize()
( my_width, my_height ) = self.GetClientSize()
my_x = ( parent_width - my_width ) - 5
my_y = ( parent_height - my_height ) - 15
self.SetPosition( parent.ClientToScreenXY( my_x, my_y ) )
if do_show:
self.Show()
except:
@ -5005,7 +5066,8 @@ class PopupMessageManager( wx.Frame ):
self._message_vbox.Detach( window )
window.Destroy()
# OS X segfaults if this is instant
wx.CallAfter( window.Destroy )
self._SizeAndPositionAndShow()
@ -5056,7 +5118,16 @@ class PopupMessageManager( wx.Frame ):
message_window = sizer_item.GetWindow()
message_window.Update()
if message_window.IsDeleted():
message_window.TryToDismiss()
break
else:
message_window.Update()
self._SizeAndPositionAndShow()

View File

@ -2828,8 +2828,9 @@ class DialogPageChooser( Dialog ):
button.SetLabelText( text )
elif entry_type == 'page_import_thread_watcher': button.SetLabelText( 'thread watcher' )
elif entry_type == 'page_import_page_of_images': button.SetLabelText( 'page of images' )
elif entry_type == 'page_import_thread_watcher': button.SetLabelText( 'thread watcher' )
elif entry_type == 'page_import_urls': button.SetLabelText( 'raw urls' )
button.Show()
@ -2867,9 +2868,10 @@ class DialogPageChooser( Dialog ):
elif menu_keyword == 'download':
entries.append( ( 'page_import_page_of_images', None ) )
entries.append( ( 'page_import_urls', None ) )
entries.append( ( 'page_import_thread_watcher', None ) )
entries.append( ( 'menu', 'gallery' ) )
entries.append( ( 'page_import_page_of_images', None ) )
elif menu_keyword == 'gallery':
@ -2957,13 +2959,17 @@ class DialogPageChooser( Dialog ):
HydrusGlobals.client_controller.pub( 'new_import_gallery', site_type )
elif entry_type == 'page_import_page_of_images':
HydrusGlobals.client_controller.pub( 'new_page_import_page_of_images' )
elif entry_type == 'page_import_thread_watcher':
HydrusGlobals.client_controller.pub( 'new_page_import_thread_watcher' )
elif entry_type == 'page_import_page_of_images':
elif entry_type == 'page_import_urls':
HydrusGlobals.client_controller.pub( 'new_page_import_page_of_images' )
HydrusGlobals.client_controller.pub( 'new_page_import_urls' )
elif entry_type == 'page_petitions':

View File

@ -10,7 +10,7 @@ import ClientGUIDialogs
import ClientDownloading
import ClientGUIOptionsPanels
import ClientGUIPredicates
import ClientGUIPanels
import ClientGUIScrolledPanels
import ClientGUITopLevelWindows
import ClientImporting
import ClientMedia
@ -3062,7 +3062,7 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
with ClientGUITopLevelWindows.DialogEdit( self, 'file import status' ) as dlg:
panel = ClientGUIPanels.EditSeedCachePanel( dlg, HydrusGlobals.client_controller, dupe_seed_cache )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( dlg, HydrusGlobals.client_controller, dupe_seed_cache )
dlg.SetPanel( panel )
@ -5560,7 +5560,7 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
with ClientGUITopLevelWindows.DialogEdit( self, 'file import status' ) as dlg:
panel = ClientGUIPanels.EditSeedCachePanel( dlg, HydrusGlobals.client_controller, dupe_seed_cache )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( dlg, HydrusGlobals.client_controller, dupe_seed_cache )
dlg.SetPanel( panel )

View File

@ -14,7 +14,7 @@ import ClientGUICollapsible
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIMedia
import ClientGUIPanels
import ClientGUIScrolledPanels
import ClientGUITopLevelWindows
import ClientImporting
import ClientMedia
@ -48,6 +48,7 @@ MANAGEMENT_TYPE_IMPORT_HDD = 3
MANAGEMENT_TYPE_IMPORT_THREAD_WATCHER = 4
MANAGEMENT_TYPE_PETITIONS = 5
MANAGEMENT_TYPE_QUERY = 6
MANAGEMENT_TYPE_IMPORT_URLS = 7
management_panel_types_to_classes = {}
@ -108,6 +109,16 @@ def CreateManagementControllerImportThreadWatcher():
return management_controller
def CreateManagementControllerImportURLs():
management_controller = CreateManagementController( MANAGEMENT_TYPE_IMPORT_URLS )
urls_import = ClientImporting.URLsImport()
management_controller.SetVariable( 'urls_import', urls_import )
return management_controller
def CreateManagementControllerPetitions( petition_service_key ):
petition_service = HydrusGlobals.client_controller.GetServicesManager().GetService( petition_service_key )
@ -1834,7 +1845,7 @@ class ManagementPanelGalleryImport( ManagementPanel ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )
@ -1982,7 +1993,7 @@ class ManagementPanelHDDImport( ManagementPanel ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )
@ -2191,6 +2202,16 @@ class ManagementPanelPageOfImagesImport( ManagementPanel ):
current_action = ''
if self._parser_status.GetLabelText() != parser_status:
self._parser_status.SetLabelText( parser_status )
if self._current_action.GetLabelText() != current_action:
self._current_action.SetLabelText( current_action )
if paused:
if self._pause_button.GetBitmap() != CC.GlobalBMPs.play:
@ -2206,16 +2227,6 @@ class ManagementPanelPageOfImagesImport( ManagementPanel ):
if self._parser_status.GetLabelText() != parser_status:
self._parser_status.SetLabelText( parser_status )
if self._current_action.GetLabelText() != current_action:
self._current_action.SetLabelText( current_action )
def EventAdvance( self, event ):
@ -2359,7 +2370,7 @@ class ManagementPanelPageOfImagesImport( ManagementPanel ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )
@ -3158,7 +3169,7 @@ class ManagementPanelThreadWatcherImport( ManagementPanel ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )
@ -3206,3 +3217,252 @@ class ManagementPanelThreadWatcherImport( ManagementPanel ):
management_panel_types_to_classes[ MANAGEMENT_TYPE_IMPORT_THREAD_WATCHER ] = ManagementPanelThreadWatcherImport
class ManagementPanelURLsImport( ManagementPanel ):
def __init__( self, parent, page, controller, management_controller ):
ManagementPanel.__init__( self, parent, page, controller, management_controller )
self._url_panel = ClientGUICommon.StaticBox( self, 'raw url downloader' )
self._overall_status = wx.StaticText( self._url_panel )
self._current_action = wx.StaticText( self._url_panel )
self._file_gauge = ClientGUICommon.Gauge( self._url_panel )
self._overall_gauge = ClientGUICommon.Gauge( self._url_panel )
self._pause_button = wx.BitmapButton( self._url_panel, bitmap = CC.GlobalBMPs.pause )
self._pause_button.Bind( wx.EVT_BUTTON, self.EventPause )
self._waiting_politely_indicator = ClientGUICommon.GetWaitingPolitelyControl( self._url_panel, self._page_key )
self._seed_cache_button = wx.BitmapButton( self._url_panel, bitmap = CC.GlobalBMPs.seed_cache )
self._seed_cache_button.Bind( wx.EVT_BUTTON, self.EventSeedCache )
self._seed_cache_button.SetToolTipString( 'open detailed file import status' )
self._url_input = wx.TextCtrl( self._url_panel, style = wx.TE_PROCESS_ENTER )
self._url_input.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown )
self._url_paste = wx.Button( self._url_panel, label = 'paste urls' )
self._url_paste.Bind( wx.EVT_BUTTON, self.EventPaste )
self._import_file_options = ClientGUICollapsible.CollapsibleOptionsImportFiles( self._url_panel )
#
button_sizer = wx.BoxSizer( wx.HORIZONTAL )
button_sizer.AddF( self._waiting_politely_indicator, CC.FLAGS_VCENTER )
button_sizer.AddF( self._seed_cache_button, CC.FLAGS_VCENTER )
button_sizer.AddF( self._pause_button, CC.FLAGS_VCENTER )
input_hbox = wx.BoxSizer( wx.HORIZONTAL )
input_hbox.AddF( self._url_input, CC.FLAGS_EXPAND_BOTH_WAYS )
input_hbox.AddF( self._url_paste, CC.FLAGS_VCENTER )
self._url_panel.AddF( self._overall_status, CC.FLAGS_EXPAND_PERPENDICULAR )
self._url_panel.AddF( self._current_action, CC.FLAGS_EXPAND_PERPENDICULAR )
self._url_panel.AddF( self._file_gauge, CC.FLAGS_EXPAND_PERPENDICULAR )
self._url_panel.AddF( self._overall_gauge, CC.FLAGS_EXPAND_PERPENDICULAR )
self._url_panel.AddF( button_sizer, CC.FLAGS_BUTTON_SIZER )
self._url_panel.AddF( input_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._url_panel.AddF( self._import_file_options, CC.FLAGS_EXPAND_PERPENDICULAR )
#
vbox = wx.BoxSizer( wx.VERTICAL )
self._MakeSort( vbox )
vbox.AddF( self._url_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self._MakeCurrentSelectionTagsBox( vbox )
self.SetSizer( vbox )
#
self.Bind( wx.EVT_MENU, self.EventMenu )
self._controller.sub( self, 'UpdateStatus', 'update_status' )
self._urls_import = self._management_controller.GetVariable( 'urls_import' )
def file_download_hook( gauge_range, gauge_value ):
self._file_gauge.SetRange( gauge_range )
self._file_gauge.SetValue( gauge_value )
self._urls_import.SetDownloadHook( file_download_hook )
import_file_options = self._urls_import.GetOptions()
self._import_file_options.SetOptions( import_file_options )
self._Update()
self._urls_import.Start( self._page_key )
def _Update( self ):
( ( overall_status, ( overall_value, overall_range ) ), paused ) = self._urls_import.GetStatus()
if self._overall_status.GetLabelText() != overall_status:
self._overall_status.SetLabelText( overall_status )
self._overall_gauge.SetRange( overall_range )
self._overall_gauge.SetValue( overall_value )
if overall_value < overall_range:
if paused:
current_action = 'paused at ' + HydrusData.ConvertValueRangeToPrettyString( overall_value + 1, overall_range )
else:
current_action = 'processing ' + HydrusData.ConvertValueRangeToPrettyString( overall_value + 1, overall_range )
else:
current_action = ''
if self._current_action.GetLabelText() != current_action:
self._current_action.SetLabelText( current_action )
if paused:
if self._pause_button.GetBitmap() != CC.GlobalBMPs.play:
self._pause_button.SetBitmap( CC.GlobalBMPs.play )
else:
if self._pause_button.GetBitmap() != CC.GlobalBMPs.pause:
self._pause_button.SetBitmap( CC.GlobalBMPs.pause )
def EventKeyDown( self, event ):
if event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
url = self._url_input.GetValue()
if url != '':
self._urls_import.PendURLs( ( url, ) )
self._url_input.SetValue( '' )
self._Update()
else:
event.Skip()
def EventMenu( self, event ):
action = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
if action is not None:
( command, data ) = action
if command == 'import_file_options_changed':
import_file_options = self._import_file_options.GetOptions()
self._urls_import.SetImportFileOptions( import_file_options )
else:
event.Skip()
def EventPaste( self, event ):
if wx.TheClipboard.Open():
data = wx.TextDataObject()
wx.TheClipboard.GetData( data )
wx.TheClipboard.Close()
raw_text = data.GetText()
try:
urls = HydrusData.SplitByLinesep( raw_text )
if len( urls ) > 0:
self._urls_import.PendURLs( urls )
self._Update()
except:
wx.MessageBox( 'I could not understand what was in the clipboard' )
else:
wx.MessageBox( 'I could not get permission to access the clipboard.' )
def EventPause( self, event ):
self._urls_import.PausePlay()
self._Update()
def EventSeedCache( self, event ):
seed_cache = self._urls_import.GetSeedCache()
title = 'file import status'
frame_key = 'file_import_status'
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )
def SetSearchFocus( self, page_key ):
if page_key == self._page_key: self._url_input.SetFocus()
def UpdateStatus( self, page_key ):
if page_key == self._page_key:
self._Update()
management_panel_types_to_classes[ MANAGEMENT_TYPE_IMPORT_URLS ] = ManagementPanelURLsImport

View File

@ -7,7 +7,7 @@ import ClientGUICommon
import ClientGUIDialogs
import ClientGUIDialogsManage
import ClientGUICanvas
import ClientGUIPanels
import ClientGUIScrolledPanels
import ClientGUITopLevelWindows
import ClientMedia
import collections
@ -799,7 +799,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
with ClientGUITopLevelWindows.DialogManage( self, title, frame_key ) as dlg:
panel = ClientGUIPanels.ManageTagsPanel( dlg, self._file_service_key, self._selected_media )
panel = ClientGUIScrolledPanels.ManageTagsPanel( dlg, self._file_service_key, self._selected_media )
dlg.SetPanel( panel )

View File

@ -14,6 +14,7 @@ import HydrusConstants as HC
import HydrusData
import HydrusExceptions
import HydrusGlobals
import HydrusHTMLParsing
import HydrusNATPunch
import HydrusPaths
import HydrusSerialisable
@ -134,6 +135,264 @@ class EditFrameLocationPanel( EditPanel ):
return ( name, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen )
class EditHTMLFormulaPanel( EditPanel ):
def __init__( self, parent, info ):
EditPanel.__init__( self, parent )
self._original_info = info
self._do_testing_automatically = False
formula_panel = ClientGUICommon.StaticBox( self, 'formula' )
self._tag_rules = wx.ListBox( formula_panel, style = wx.LB_SINGLE )
self._tag_rules.Bind( wx.EVT_LEFT_DCLICK, self.EventEdit )
self._add_rule = wx.Button( formula_panel, label = 'add' )
self._add_rule.Bind( wx.EVT_BUTTON, self.EventAdd )
self._edit_rule = wx.Button( formula_panel, label = 'edit' )
self._edit_rule.Bind( wx.EVT_BUTTON, self.EventEdit )
self._move_rule_up = wx.Button( formula_panel, label = u'\u2191' )
self._move_rule_up.Bind( wx.EVT_BUTTON, self.EventMoveUp )
self._delete_rule = wx.Button( formula_panel, label = 'X' )
self._delete_rule.Bind( wx.EVT_BUTTON, self.EventDelete )
self._move_rule_down = wx.Button( formula_panel, label = u'\u2193' )
self._move_rule_down.Bind( wx.EVT_BUTTON, self.EventMoveDown )
self._content_rule = wx.TextCtrl( formula_panel )
testing_panel = ClientGUICommon.StaticBox( self, 'testing' )
self._test_html = wx.TextCtrl( testing_panel, style = wx.TE_MULTILINE )
self._fetch_from_url = wx.Button( testing_panel, label = 'fetch html from url' )
self._fetch_from_url.Bind( wx.EVT_BUTTON, self.EventFetchFromURL )
self._run_test = wx.Button( testing_panel, label = 'run test' )
self._run_test.Bind( wx.EVT_BUTTON, self.EventRunTest )
self._results = wx.TextCtrl( testing_panel, style = wx.TE_MULTILINE )
#
( tag_rules, content_rule ) = self._original_info.ToTuple()
for rule in tag_rules:
pretty_rule = HydrusHTMLParsing.RenderTagRule( rule )
self._tag_rules.Append( pretty_rule, rule )
self._content_rule.SetValue( content_rule )
self._test_html.SetValue( 'Enter html here to test it against the above formula.' )
self._results.SetValue( 'Successfully parsed results will be printed here.' )
#
udd_button_vbox = wx.BoxSizer( wx.VERTICAL )
udd_button_vbox.AddF( self._move_rule_up, CC.FLAGS_VCENTER )
udd_button_vbox.AddF( self._delete_rule, CC.FLAGS_VCENTER )
udd_button_vbox.AddF( self._move_rule_down, CC.FLAGS_VCENTER )
tag_rules_hbox = wx.BoxSizer( wx.HORIZONTAL )
tag_rules_hbox.AddF( self._tag_rules, CC.FLAGS_EXPAND_BOTH_WAYS )
tag_rules_hbox.AddF( udd_button_vbox, CC.FLAGS_VCENTER )
ae_button_hbox = wx.BoxSizer( wx.HORIZONTAL )
ae_button_hbox.AddF( self._add_rule, CC.FLAGS_VCENTER )
ae_button_hbox.AddF( self._edit_rule, CC.FLAGS_VCENTER )
formula_panel.AddF( tag_rules_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
formula_panel.AddF( ae_button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
formula_panel.AddF( ClientGUICommon.WrapInText( self._content_rule, formula_panel, 'attribute: ' ), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
testing_panel.AddF( self._test_html, CC.FLAGS_EXPAND_PERPENDICULAR )
testing_panel.AddF( self._fetch_from_url, CC.FLAGS_EXPAND_PERPENDICULAR )
testing_panel.AddF( self._run_test, CC.FLAGS_EXPAND_PERPENDICULAR )
testing_panel.AddF( self._results, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
message = 'The html will be searched recursively by each rule in turn and then the attribute of the final tags will be returned.'
message += os.linesep * 2
message += 'So, to find the \'src\' of the first <img> tag beneath all <span> tags with the class \'content\', use:'
message += os.linesep * 2
message += 'all span tags with class=content'
message += '1st img tag'
message += 'attribute: src'
message += os.linesep * 2
message += 'Leave the attribute blank to represent the string of the tag (i.e. <p>This part</p>).'
vbox.AddF( wx.StaticText( self, label = message ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( formula_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( testing_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
def _RunTest( self ):
formula = self.GetValue()
html = self._test_html.GetValue()
try:
results = formula.Parse( html )
# do the begin/end to better display '' results and any other whitespace weirdness
results = [ '*** RESULTS BEGIN ***' ] + results + [ '*** RESULTS END ***' ]
results_text = os.linesep.join( results )
self._results.SetValue( results_text )
self._do_testing_automatically = True
except Exception as e:
message = 'Could not parse! Full error written to log!'
message += os.linesep * 2
message += HydrusData.ToUnicode( e )
wx.MessageBox( message )
self._do_testing_automatically = False
def EventAdd( self, event ):
# spawn dialog, add it and run test
if self._do_testing_automatically:
self._RunTest()
def EventDelete( self, event ):
selection = self._tag_rules.GetSelection()
if selection != wx.NOT_FOUND:
if self._tag_rules.GetCount() == 1:
wx.MessageBox( 'A parsing formula needs at least one tag rule!' )
else:
self._tag_rules.Delete( selection )
if self._do_testing_automatically:
self._RunTest()
def EventEdit( self, event ):
selection = self._tag_rules.GetSelection()
if selection != wx.NOT_FOUND:
( name, attrs, index ) = self._tag_rules.GetClientData( selection )
# spawn dialog, then if ok, set it and run test
if self._do_testing_automatically:
self._RunTest()
def EventFetchFromURL( self, event ):
# ask user for url with textdlg
# get it with requests
# handle errors with a messagebox
# try to parse it with bs4 to check it is good html and then splat it to the textctrl, otherwise just messagebox the error
if self._do_testing_automatically:
self._RunTest()
def EventMoveDown( self, event ):
selection = self._tag_rules.GetSelection()
if selection != wx.NOT_FOUND and selection + 1 < self._tag_rules.GetCount():
pretty_rule = self._tag_rules.GetString( selection )
rule = self._tag_rules.GetClientData( selection )
self._tag_rules.Delete( selection )
self._tag_rules.Insert( selection + 1, pretty_rule, rule )
if self._do_testing_automatically:
self._RunTest()
def EventMoveUp( self, event ):
selection = self._tag_rules.GetSelection()
if selection != wx.NOT_FOUND and selection > 0:
pretty_rule = self._tag_rules.GetString( selection )
rule = self._tag_rules.GetClientData( selection )
self._tag_rules.Delete( selection )
self._tag_rules.Insert( selection - 1, pretty_rule, rule )
if self._do_testing_automatically:
self._RunTest()
def EventRunTest( self, event ):
self._RunTest()
def GetValue( self ):
tags_rules = [ self._tag_rules.GetClientData( i ) for i in range( self._tag_rules.GetCount() ) ]
content_rule = self._content_rule.GetValue()
if content_rule == '':
content_rule = None
formula = HydrusHTMLParsing.ParseFormula( tags_rules, content_rule )
return formula
class EditMediaViewOptionsPanel( EditPanel ):
def __init__( self, parent, info ):
@ -998,35 +1257,25 @@ class ManageOptionsPanel( ManagePanel ):
#
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Run maintenance jobs when the client is idle and the system is not otherwise busy: ', self._idle_normal ) )
rows.append( ( 'Assume the client is idle if no general browsing activity has occured in the past: ', self._idle_period ) )
rows.append( ( 'Assume the client is idle if the mouse has not been moved in the past: ', self._idle_mouse_period ) )
rows.append( ( 'Assume the system is busy if any CPU core has recent average usage above: ', self._idle_cpu_max ) )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Run maintenance jobs when the client is idle and the system is not otherwise busy?: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._idle_normal, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Assume the client is idle if no general browsing activity has occured in the past: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._idle_period, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Assume the client is idle if the mouse has not been moved in the past: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._idle_mouse_period, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Assume the system is busy if any CPU core has recent average usage above: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._idle_cpu_max, CC.FLAGS_VCENTER )
gridbox = ClientGUICommon.WrapInGrid( self._idle_panel, rows )
self._idle_panel.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
#
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Run jobs on shutdown: ', self._idle_shutdown ) )
rows.append( ( 'Max number of minutes to run shutdown jobs: ', self._idle_shutdown_max_minutes ) )
gridbox.AddF( wx.StaticText( self._shutdown_panel, label = 'Run jobs on shutdown?: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._idle_shutdown, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self._shutdown_panel, label = 'Max number of minutes to run shutdown jobs: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._idle_shutdown_max_minutes, CC.FLAGS_VCENTER )
gridbox = ClientGUICommon.WrapInGrid( self._shutdown_panel, rows )
self._shutdown_panel.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -1050,23 +1299,21 @@ class ManageOptionsPanel( ManagePanel ):
#
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Number of days to wait between vacuums: ', self._maintenance_vacuum_period ) )
gridbox.AddF( wx.StaticText( self._maintenance_panel, label = 'Number of days to wait between vacuums: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._maintenance_vacuum_period, CC.FLAGS_VCENTER )
gridbox = ClientGUICommon.WrapInGrid( self._maintenance_panel, rows )
self._maintenance_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Delay repository update processing by (s): ', self._processing_phase ) )
gridbox.AddF( wx.StaticText( self._processing_panel, label = 'Delay repository update processing by (s): ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._processing_phase, CC.FLAGS_VCENTER )
gridbox = ClientGUICommon.WrapInGrid( self._processing_panel, rows )
self._processing_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -1388,30 +1635,17 @@ class ManageOptionsPanel( ManagePanel ):
vbox = wx.BoxSizer( wx.VERTICAL )
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Default export directory: ', self._export_location ) )
rows.append( ( 'When deleting files or folders, send them to the OS\'s recycle bin: ', self._delete_to_recycle_bin ) )
rows.append( ( 'By default, do not reimport files that have been previously deleted: ', self._exclude_deleted_files ) )
rows.append( ( 'Remove files from view when they are filtered: ', self._remove_filtered_files ) )
rows.append( ( 'Remove files from view when they are sent to the trash: ', self._remove_trashed_files ) )
rows.append( ( 'Number of hours a file can be in the trash before being deleted: ', self._trash_max_age ) )
rows.append( ( 'Maximum size of trash (MB): ', self._trash_max_size ) )
gridbox.AddF( wx.StaticText( self, label = 'Default export directory: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._export_location, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'When deleting files or folders, send them to the OS\'s recycle bin: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._delete_to_recycle_bin, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'By default, do not reimport files that have been previously deleted: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._exclude_deleted_files, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Remove files from view when they are filtered: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._remove_filtered_files, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Remove files from view when they are sent to the trash: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._remove_trashed_files, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Number of hours a file can be in the trash before being deleted: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._trash_max_age, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Maximum size of trash (MB): ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._trash_max_size, CC.FLAGS_VCENTER )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
text = 'If you set the default export directory blank, the client will use \'hydrus_export\' under the current user\'s home directory.'
@ -1508,36 +1742,19 @@ class ManageOptionsPanel( ManagePanel ):
#
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Default session on startup: ', self._default_gui_session ) )
rows.append( ( 'Confirm client exit: ', self._confirm_client_exit ) )
rows.append( ( 'Confirm sending files to trash: ', self._confirm_trash ) )
rows.append( ( 'Confirm sending more than one file to archive or inbox: ', self._confirm_archive ) )
rows.append( ( 'Always embed autocomplete dropdown results window: ', self._always_embed_autocompletes ) )
rows.append( ( 'Capitalise gui: ', self._gui_capitalisation ) )
rows.append( ( 'Hide the preview window: ', self._hide_preview ) )
rows.append( ( 'Show \'title\' banner on thumbnails: ', self._show_thumbnail_title_banner ) )
rows.append( ( 'Show volume/chapter/page number on thumbnails: ', self._show_thumbnail_page ) )
gridbox.AddF( wx.StaticText( self, label = 'Default session on startup:' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._default_gui_session, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Confirm client exit:' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._confirm_client_exit, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Confirm sending files to trash:' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._confirm_trash, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Confirm sending more than one file to archive or inbox:' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._confirm_archive, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Always embed autocomplete dropdown results window:' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._always_embed_autocompletes, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Capitalise gui: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._gui_capitalisation, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Hide the preview window: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._hide_preview, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Show \'title\' banner on thumbnails: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._show_thumbnail_title_banner, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Show volume/chapter/page number on thumbnails: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._show_thumbnail_page, CC.FLAGS_VCENTER )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
text = 'Here you can override the current and default values for many frame and dialog sizing and positioning variables.'
text += os.linesep
@ -1651,7 +1868,7 @@ class ManageOptionsPanel( ManagePanel ):
self._media_zooms.SetValue( ','.join( ( str( media_zoom ) for media_zoom in media_zooms ) ) )
mimes_in_correct_order = ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_GIF, HC.APPLICATION_FLASH, HC.APPLICATION_PDF, HC.VIDEO_FLV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_MPEG, HC.VIDEO_WEBM, HC.VIDEO_WMV, HC.AUDIO_MP3, HC.AUDIO_OGG, HC.AUDIO_FLAC, HC.AUDIO_WMA )
mimes_in_correct_order = ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_GIF, HC.APPLICATION_FLASH, HC.APPLICATION_PDF, HC.VIDEO_FLV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_MPEG, HC.VIDEO_WEBM, HC.VIDEO_WMV, HC.AUDIO_MP3, HC.AUDIO_OGG, HC.AUDIO_FLAC, HC.AUDIO_WMA )
for mime in mimes_in_correct_order:
@ -1669,18 +1886,13 @@ class ManageOptionsPanel( ManagePanel ):
vbox = wx.BoxSizer( wx.VERTICAL )
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Start animations this % in: ', self._animation_start_position ) )
rows.append( ( 'Disable OpenCV for gifs: ', self._disable_cv_for_gifs ) )
rows.append( ( 'Media zooms: ', self._media_zooms ) )
gridbox.AddF( wx.StaticText( self, label = 'Start animations this % in: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._animation_start_position, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Disable OpenCV for gifs: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._disable_cv_for_gifs, CC.FLAGS_VCENTER )
gridbox.AddF( wx.StaticText( self, label = 'Media zooms: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._media_zooms, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -1999,16 +2211,13 @@ class ManageOptionsPanel( ManagePanel ):
#
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Default sort: ', self._default_sort ) )
rows.append( ( 'Secondary sort (when primary gives two equal values): ', self._sort_fallback ) )
rows.append( ( 'Default collect: ', self._default_collect ) )
gridbox.AddF( wx.StaticText( self, label = 'default sort: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._default_sort, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'secondary sort (when primary gives two equal values): ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._sort_fallback, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'default collect: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._default_collect, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox = wx.BoxSizer( wx.VERTICAL )
@ -2106,52 +2315,70 @@ class ManageOptionsPanel( ManagePanel ):
self._new_options = new_options
self._disk_cache_init_period = ClientGUICommon.NoneableSpinCtrl( self, 'max disk cache init period', none_phrase = 'do not run', min = 1, max = 120 )
disk_panel = ClientGUICommon.StaticBox( self, 'disk cache' )
self._disk_cache_init_period = ClientGUICommon.NoneableSpinCtrl( disk_panel, 'max disk cache init period', none_phrase = 'do not run', min = 1, max = 120 )
self._disk_cache_init_period.SetToolTipString( 'When the client boots, it can speed up operation by reading the front of the database into memory. This sets the max number of seconds it can spend doing that.' )
self._disk_cache_maintenance_mb = ClientGUICommon.NoneableSpinCtrl( self, 'disk cache maintenance (MB)', none_phrase = 'do not keep db cached', min = 32, max = 65536 )
self._disk_cache_maintenance_mb = ClientGUICommon.NoneableSpinCtrl( disk_panel, 'disk cache maintenance (MB)', none_phrase = 'do not keep db cached', min = 32, max = 65536 )
self._disk_cache_maintenance_mb.SetToolTipString( 'The client can regularly check the front of its database is cached in memory. This represents how many megabytes it will ensure are cached.' )
self._thumbnail_width = wx.SpinCtrl( self, min = 20, max = 200 )
#
media_panel = ClientGUICommon.StaticBox( self, 'thumbnail size and media cache' )
self._thumbnail_width = wx.SpinCtrl( media_panel, min = 20, max = 200 )
self._thumbnail_width.Bind( wx.EVT_SPINCTRL, self.EventThumbnailsUpdate )
self._thumbnail_height = wx.SpinCtrl( self, min = 20, max = 200 )
self._thumbnail_height = wx.SpinCtrl( media_panel, min = 20, max = 200 )
self._thumbnail_height.Bind( wx.EVT_SPINCTRL, self.EventThumbnailsUpdate )
self._thumbnail_cache_size = wx.SpinCtrl( self, min = 5, max = 3000 )
self._thumbnail_cache_size = wx.SpinCtrl( media_panel, min = 5, max = 3000 )
self._thumbnail_cache_size.Bind( wx.EVT_SPINCTRL, self.EventThumbnailsUpdate )
self._estimated_number_thumbnails = wx.StaticText( self, label = '' )
self._estimated_number_thumbnails = wx.StaticText( media_panel, label = '' )
self._fullscreen_cache_size = wx.SpinCtrl( self, min = 25, max = 3000 )
self._fullscreen_cache_size = wx.SpinCtrl( media_panel, min = 25, max = 3000 )
self._fullscreen_cache_size.Bind( wx.EVT_SPINCTRL, self.EventFullscreensUpdate )
self._estimated_number_fullscreens = wx.StaticText( self, label = '' )
self._estimated_number_fullscreens = wx.StaticText( media_panel, label = '' )
self._video_buffer_size_mb = wx.SpinCtrl( self, min = 48, max = 16 * 1024 )
#
buffer_panel = ClientGUICommon.StaticBox( self, 'video buffer' )
self._video_buffer_size_mb = wx.SpinCtrl( buffer_panel, min = 48, max = 16 * 1024 )
self._video_buffer_size_mb.Bind( wx.EVT_SPINCTRL, self.EventVideoBufferUpdate )
self._estimated_number_video_frames = wx.StaticText( self, label = '' )
self._estimated_number_video_frames = wx.StaticText( buffer_panel, label = '' )
self._forced_search_limit = ClientGUICommon.NoneableSpinCtrl( self, '', min = 1, max = 100000 )
#
self._num_autocomplete_chars = wx.SpinCtrl( self, min = 1, max = 100 )
ac_panel = ClientGUICommon.StaticBox( self, 'tag autocomplete' )
self._num_autocomplete_chars = wx.SpinCtrl( ac_panel, min = 1, max = 100 )
self._num_autocomplete_chars.SetToolTipString( 'how many characters you enter before the gui fetches autocomplete results from the db. (otherwise, it will only fetch exact matches)' + os.linesep + 'increase this if you find autocomplete results are slow' )
self._fetch_ac_results_automatically = wx.CheckBox( self )
self._fetch_ac_results_automatically = wx.CheckBox( ac_panel )
self._fetch_ac_results_automatically.Bind( wx.EVT_CHECKBOX, self.EventFetchAuto )
self._autocomplete_long_wait = wx.SpinCtrl( self, min = 0, max = 10000 )
self._autocomplete_long_wait = wx.SpinCtrl( ac_panel, min = 0, max = 10000 )
self._autocomplete_long_wait.SetToolTipString( 'how long the gui will typically wait, after you enter a character, before it queries the db with what you have entered so far' )
self._autocomplete_short_wait_chars = wx.SpinCtrl( self, min = 1, max = 100 )
self._autocomplete_short_wait_chars = wx.SpinCtrl( ac_panel, min = 1, max = 100 )
self._autocomplete_short_wait_chars.SetToolTipString( 'how many characters you enter before the gui starts waiting the short time before querying the db' )
self._autocomplete_short_wait = wx.SpinCtrl( self, min = 0, max = 10000 )
self._autocomplete_short_wait = wx.SpinCtrl( ac_panel, min = 0, max = 10000 )
self._autocomplete_short_wait.SetToolTipString( 'how long the gui will typically wait, after you enter a lot of characters, before it queries the db with what you have entered so far' )
#
misc_panel = ClientGUICommon.StaticBox( self, 'misc' )
self._forced_search_limit = ClientGUICommon.NoneableSpinCtrl( misc_panel, '', min = 1, max = 100000 )
#
self._disk_cache_init_period.SetValue( self._new_options.GetNoneableInteger( 'disk_cache_init_period' ) )
self._disk_cache_maintenance_mb.SetValue( self._new_options.GetNoneableInteger( 'disk_cache_maintenance_mb' ) )
@ -2167,8 +2394,6 @@ class ManageOptionsPanel( ManagePanel ):
self._video_buffer_size_mb.SetValue( self._new_options.GetInteger( 'video_buffer_size_mb' ) )
self._forced_search_limit.SetValue( self._new_options.GetNoneableInteger( 'forced_search_limit' ) )
self._num_autocomplete_chars.SetValue( HC.options[ 'num_autocomplete_chars' ] )
self._fetch_ac_results_automatically.SetValue( HC.options[ 'fetch_ac_results_automatically' ] )
@ -2181,6 +2406,17 @@ class ManageOptionsPanel( ManagePanel ):
self._autocomplete_short_wait.SetValue( short_wait )
self._forced_search_limit.SetValue( self._new_options.GetNoneableInteger( 'forced_search_limit' ) )
#
vbox = wx.BoxSizer( wx.VERTICAL )
disk_panel.AddF( self._disk_cache_init_period, CC.FLAGS_EXPAND_PERPENDICULAR )
disk_panel.AddF( self._disk_cache_maintenance_mb, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( disk_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
#
thumbnails_sizer = wx.BoxSizer( wx.HORIZONTAL )
@ -2198,34 +2434,20 @@ class ManageOptionsPanel( ManagePanel ):
video_buffer_sizer.AddF( self._video_buffer_size_mb, CC.FLAGS_VCENTER )
video_buffer_sizer.AddF( self._estimated_number_video_frames, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
rows = []
vbox.AddF( self._disk_cache_init_period, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._disk_cache_maintenance_mb, CC.FLAGS_EXPAND_PERPENDICULAR )
rows.append( ( 'Thumbnail width: ', self._thumbnail_width ) )
rows.append( ( 'Thumbnail height: ', self._thumbnail_height ) )
rows.append( ( 'MB memory reserved for thumbnail cache: ', thumbnails_sizer ) )
rows.append( ( 'MB memory reserved for media viewer cache: ', fullscreens_sizer ) )
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox = ClientGUICommon.WrapInGrid( media_panel, rows )
gridbox.AddGrowableCol( 1, 1 )
media_panel.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox.AddF( wx.StaticText( self, label = 'Thumbnail width: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._thumbnail_width, CC.FLAGS_VCENTER )
vbox.AddF( media_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox.AddF( wx.StaticText( self, label = 'Thumbnail height: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._thumbnail_height, CC.FLAGS_VCENTER )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self, label = 'MB memory reserved for thumbnail cache: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( thumbnails_sizer, CC.FLAGS_NONE )
gridbox.AddF( wx.StaticText( self, label = 'MB memory reserved for media viewer cache: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( fullscreens_sizer, CC.FLAGS_NONE )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
#
text = 'Hydrus video rendering is CPU intensive.'
text += os.linesep
@ -2233,50 +2455,51 @@ class ManageOptionsPanel( ManagePanel ):
text += os.linesep
text += 'If the video buffer can hold an entire video, it only needs to be rendered once and will loop smoothly.'
vbox.AddF( wx.StaticText( self, label = text ), CC.FLAGS_VCENTER )
buffer_panel.AddF( wx.StaticText( buffer_panel, label = text ), CC.FLAGS_VCENTER )
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'MB memory for video buffer: ', video_buffer_sizer ) )
gridbox.AddF( wx.StaticText( self, label = 'MB memory for video buffer: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( video_buffer_sizer, CC.FLAGS_NONE )
gridbox = ClientGUICommon.WrapInGrid( buffer_panel, rows )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
buffer_panel.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox = wx.FlexGridSizer( 0, 2 )
vbox.AddF( buffer_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self, label = 'Forced system:limit for all searches: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._forced_search_limit, CC.FLAGS_NONE )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
#
text = 'If you disable automatic autocomplete results fetching, use Ctrl+Space to fetch results manually.'
vbox.AddF( wx.StaticText( self, label = text ), CC.FLAGS_EXPAND_PERPENDICULAR )
ac_panel.AddF( wx.StaticText( ac_panel, label = text ), CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Automatically fetch autocomplete results after a short delay: ', self._fetch_ac_results_automatically ) )
rows.append( ( 'Autocomplete long wait character threshold: ', self._num_autocomplete_chars ) )
rows.append( ( 'Autocomplete long wait (ms): ', self._autocomplete_long_wait ) )
rows.append( ( 'Autocomplete short wait character threshold: ', self._autocomplete_short_wait_chars ) )
rows.append( ( 'Autocomplete short wait (ms): ', self._autocomplete_short_wait ) )
gridbox.AddF( wx.StaticText( self, label = 'Automatically fetch autocomplete results after a short delay: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._fetch_ac_results_automatically, CC.FLAGS_VCENTER )
gridbox = ClientGUICommon.WrapInGrid( ac_panel, rows )
gridbox.AddF( wx.StaticText( self, label = 'Autocomplete long wait character threshold: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._num_autocomplete_chars, CC.FLAGS_VCENTER )
ac_panel.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox.AddF( wx.StaticText( self, label = 'Autocomplete long wait (ms): ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._autocomplete_long_wait, CC.FLAGS_VCENTER )
vbox.AddF( ac_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox.AddF( wx.StaticText( self, label = 'Autocomplete short wait character threshold: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._autocomplete_short_wait_chars, CC.FLAGS_VCENTER )
#
gridbox.AddF( wx.StaticText( self, label = 'Autocomplete short wait (ms): ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._autocomplete_short_wait, CC.FLAGS_VCENTER )
rows = []
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
rows.append( ( 'Forced system:limit for all searches: ', self._forced_search_limit ) )
gridbox = ClientGUICommon.WrapInGrid( misc_panel, rows )
misc_panel.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( misc_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
#
self.SetSizer( vbox )
@ -2367,7 +2590,9 @@ class ManageOptionsPanel( ManagePanel ):
self._new_options = new_options
self._default_tag_sort = wx.Choice( self )
general_panel = ClientGUICommon.StaticBox( self, 'general tag options' )
self._default_tag_sort = wx.Choice( general_panel )
self._default_tag_sort.Append( 'lexicographic (a-z)', CC.SORT_BY_LEXICOGRAPHIC_ASC )
self._default_tag_sort.Append( 'lexicographic (z-a)', CC.SORT_BY_LEXICOGRAPHIC_DESC )
@ -2376,11 +2601,11 @@ class ManageOptionsPanel( ManagePanel ):
self._default_tag_sort.Append( 'incidence (desc)', CC.SORT_BY_INCIDENCE_DESC )
self._default_tag_sort.Append( 'incidence (asc)', CC.SORT_BY_INCIDENCE_ASC )
self._default_tag_repository = ClientGUICommon.BetterChoice( self )
self._default_tag_repository = ClientGUICommon.BetterChoice( general_panel )
self._show_all_tags_in_autocomplete = wx.CheckBox( self )
self._show_all_tags_in_autocomplete = wx.CheckBox( general_panel )
self._apply_all_parents_to_all_services = wx.CheckBox( self )
self._apply_all_parents_to_all_services = wx.CheckBox( general_panel )
suggested_tags_panel = ClientGUICommon.StaticBox( self, 'suggested tags' )
@ -2462,59 +2687,50 @@ class ManageOptionsPanel( ManagePanel ):
#
gridbox = wx.FlexGridSizer( 0, 2 )
vbox = wx.BoxSizer( wx.VERTICAL )
gridbox.AddGrowableCol( 1, 1 )
rows = []
gridbox.AddF( wx.StaticText( self, label = 'Default tag service in manage tag dialogs:' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._default_tag_repository, CC.FLAGS_VCENTER )
rows.append( ( 'Default tag service in manage tag dialogs: ', self._default_tag_repository ) )
rows.append( ( 'Default tag sort: ', self._default_tag_sort ) )
rows.append( ( 'By default, search non-local tags in write-autocomplete: ', self._show_all_tags_in_autocomplete ) )
rows.append( ( 'Suggest all parents for all services: ', self._apply_all_parents_to_all_services ) )
gridbox.AddF( wx.StaticText( self, label = 'Default tag sort:' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._default_tag_sort, CC.FLAGS_VCENTER )
gridbox = ClientGUICommon.WrapInGrid( general_panel, rows )
gridbox.AddF( wx.StaticText( self, label = 'By default, search non-local tags in write-autocomplete: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._show_all_tags_in_autocomplete, CC.FLAGS_VCENTER )
general_panel.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox.AddF( wx.StaticText( self, label = 'Apply all parents to all services: ' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._apply_all_parents_to_all_services, CC.FLAGS_VCENTER )
vbox.AddF( general_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
#
suggested_tags_favourites_panel.AddF( self._suggested_favourites_services, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_favourites_panel.AddF( self._suggested_favourites, CC.FLAGS_EXPAND_BOTH_WAYS )
suggested_tags_favourites_panel.AddF( self._suggested_favourites_input, CC.FLAGS_EXPAND_PERPENDICULAR )
related_gridbox = wx.FlexGridSizer( 0, 2 )
rows = []
related_gridbox.AddGrowableCol( 1, 1 )
rows.append( ( 'Show related tags on single-file manage tags windows: ', self._show_related_tags ) )
rows.append( ( 'Width of related tags list: ', self._related_tags_width ) )
rows.append( ( 'Initial search duration (ms): ', self._related_tags_search_1_duration_ms ) )
rows.append( ( 'Medium search duration (ms): ', self._related_tags_search_2_duration_ms ) )
rows.append( ( 'Thorough search duration (ms): ', self._related_tags_search_3_duration_ms ) )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'show related tags on single-file manage tags windows' ), CC.FLAGS_VCENTER )
related_gridbox.AddF( self._show_related_tags, CC.FLAGS_VCENTER )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'width of related tags list' ), CC.FLAGS_VCENTER )
related_gridbox.AddF( self._related_tags_width, CC.FLAGS_VCENTER )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'initial search duration (ms)' ), CC.FLAGS_VCENTER )
related_gridbox.AddF( self._related_tags_search_1_duration_ms, CC.FLAGS_VCENTER )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'medium search duration (ms)' ), CC.FLAGS_VCENTER )
related_gridbox.AddF( self._related_tags_search_2_duration_ms, CC.FLAGS_VCENTER )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'thorough search duration (ms)' ), CC.FLAGS_VCENTER )
related_gridbox.AddF( self._related_tags_search_3_duration_ms, CC.FLAGS_VCENTER )
related_gridbox = ClientGUICommon.WrapInGrid( suggested_tags_related_panel, rows )
suggested_tags_related_panel.AddF( related_gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_recent_panel.AddF( self._num_recent_tags, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.AddF( self._suggested_tags_width, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.AddF( suggested_tags_favourites_panel, CC.FLAGS_EXPAND_SIZER_DEPTH_ONLY )
suggested_tags_panel.AddF( suggested_tags_favourites_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
suggested_tags_panel.AddF( suggested_tags_related_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.AddF( suggested_tags_recent_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( suggested_tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
#
self.SetSizer( vbox )
#

View File

@ -197,7 +197,6 @@ def SetTLWSizeAndPosition( tlw, frame_key ):
wx.CallAfter( tlw.ShowFullScreen, True, wx.FULLSCREEN_ALL )
class NewDialog( wx.Dialog ):
def __init__( self, parent, title ):

View File

@ -2494,6 +2494,17 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
self._WorkOnFiles( job_key )
except HydrusExceptions.NetworkException as e:
HydrusData.Print( 'The subscription ' + self._name + ' encountered an exception when trying to sync:' )
HydrusData.PrintException( e )
job_key.SetVariable( 'popup_text_1', 'Encountered a network error, will retry again later' )
self._last_error = HydrusData.GetNow()
time.sleep( 5 )
except Exception as e:
HydrusData.ShowText( 'The subscription ' + self._name + ' encountered an exception when trying to sync:' )
@ -2987,3 +2998,243 @@ class ThreadWatcherImport( HydrusSerialisable.SerialisableBase ):
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_THREAD_WATCHER_IMPORT ] = ThreadWatcherImport
class URLsImport( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_URLS_IMPORT
SERIALISABLE_VERSION = 1
def __init__( self ):
HydrusSerialisable.SerialisableBase.__init__( self )
import_file_options = ClientDefaults.GetDefaultImportFileOptions()
self._urls_cache = SeedCache()
self._import_file_options = import_file_options
self._paused = False
self._seed_cache_status = ( 'initialising', ( 0, 1 ) )
self._file_download_hook = None
self._lock = threading.Lock()
def _GetSerialisableInfo( self ):
serialisable_url_cache = self._urls_cache.GetSerialisableTuple()
serialisable_file_options = self._import_file_options.GetSerialisableTuple()
return ( serialisable_url_cache, serialisable_file_options, self._paused )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( serialisable_url_cache, serialisable_file_options, self._paused ) = serialisable_info
self._urls_cache = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_url_cache )
self._import_file_options = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_file_options )
def _RegenerateSeedCacheStatus( self, page_key ):
new_seed_cache_status = self._urls_cache.GetStatus()
if self._seed_cache_status != new_seed_cache_status:
self._seed_cache_status = new_seed_cache_status
HydrusGlobals.client_controller.pub( 'update_status', page_key )
def _WorkOnFiles( self, page_key ):
do_wait = True
file_url = self._urls_cache.GetNextSeed( CC.STATUS_UNKNOWN )
if file_url is None:
return
try:
( status, hash ) = HydrusGlobals.client_controller.Read( 'url_status', file_url )
if status == CC.STATUS_DELETED:
if not self._import_file_options.GetExcludeDeleted():
status = CC.STATUS_NEW
if status == CC.STATUS_NEW:
( os_file_handle, temp_path ) = HydrusPaths.GetTempPath()
try:
report_hooks = []
with self._lock:
if self._file_download_hook is not None:
report_hooks.append( self._file_download_hook )
HydrusGlobals.client_controller.DoHTTP( HC.GET, file_url, report_hooks = report_hooks, temp_path = temp_path )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
( status, hash ) = client_files_manager.ImportFile( temp_path, import_file_options = self._import_file_options, url = file_url )
finally:
HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
else:
do_wait = False
self._urls_cache.UpdateSeedStatus( file_url, status )
if status in ( CC.STATUS_SUCCESSFUL, CC.STATUS_REDUNDANT ):
( media_result, ) = HydrusGlobals.client_controller.Read( 'media_results', ( hash, ) )
HydrusGlobals.client_controller.pub( 'add_media_results', page_key, ( media_result, ) )
except HydrusExceptions.MimeException as e:
status = CC.STATUS_UNINTERESTING_MIME
self._urls_cache.UpdateSeedStatus( file_url, status )
except Exception as e:
status = CC.STATUS_FAILED
self._urls_cache.UpdateSeedStatus( file_url, status, exception = e )
with self._lock:
self._RegenerateSeedCacheStatus( page_key )
HydrusGlobals.client_controller.pub( 'update_status', page_key )
if do_wait:
ClientData.WaitPolitely( page_key )
def _THREADWork( self, page_key ):
with self._lock:
self._RegenerateSeedCacheStatus( page_key )
HydrusGlobals.client_controller.pub( 'update_status', page_key )
while not ( HydrusGlobals.view_shutdown or HydrusGlobals.client_controller.PageDeleted( page_key ) ):
if self._paused or HydrusGlobals.client_controller.PageHidden( page_key ):
time.sleep( 0.1 )
else:
try:
self._WorkOnFiles( page_key )
time.sleep( 0.1 )
HydrusGlobals.client_controller.WaitUntilPubSubsEmpty()
except Exception as e:
HydrusData.ShowException( e )
return
def GetSeedCache( self ):
return self._urls_cache
def GetOptions( self ):
with self._lock:
return self._import_file_options
def GetStatus( self ):
with self._lock:
return ( self._seed_cache_status, self._paused )
def PausePlay( self ):
with self._lock:
self._paused = not self._paused
def PendURLs( self, urls ):
with self._lock:
for url in urls:
if not self._urls_cache.HasSeed( url ):
self._urls_cache.AddSeed( url )
def SetDownloadHook( self, hook ):
with self._lock:
self._file_download_hook = hook
def SetImportFileOptions( self, import_file_options ):
with self._lock:
self._import_file_options = import_file_options
def Start( self, page_key ):
threading.Thread( target = self._THREADWork, args = ( page_key, ) ).start()
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_URLS_IMPORT ] = URLsImport

View File

@ -427,6 +427,29 @@ class HTTPConnection( object ):
def _ReadResponse( self, response, report_hooks, temp_path = None ):
try:
if response.status == 200 and temp_path is not None:
size_of_response = self._WriteResponseToPath( response, temp_path, report_hooks )
parsed_response = 'response written to temporary file'
else:
( parsed_response, size_of_response ) = self._ParseResponse( response, report_hooks )
except socket.timeout as e:
raise HydrusExceptions.NetworkException( 'Connection timed out during response read.' )
return ( parsed_response, size_of_response )
def _ParseCookies( self, raw_cookies_string ):
cookies = {}
@ -455,7 +478,7 @@ class HTTPConnection( object ):
def _ParseResponse( self, response, report_hooks ):
server_header = response.getheader( 'Server' )
if server_header is not None and 'hydrus' in server_header:
@ -636,16 +659,7 @@ class HTTPConnection( object ):
response = self._GetResponse( method_string, path_and_query, request_headers, body )
if response.status == 200 and temp_path is not None:
size_of_response = self._WriteResponseToPath( response, temp_path, report_hooks )
parsed_response = 'response written to temporary file'
else:
( parsed_response, size_of_response ) = self._ParseResponse( response, report_hooks )
( parsed_response, size_of_response ) = self._ReadResponse( response, report_hooks, temp_path )
response_headers = { k : v for ( k, v ) in response.getheaders() if k != 'set-cookie' }

View File

@ -48,7 +48,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 221
SOFTWARE_VERSION = 222
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -275,19 +275,20 @@ APPLICATION_JSON = 22
VIDEO_APNG = 23
UNDETERMINED_PNG = 24
VIDEO_MPEG = 25
VIDEO_MOV = 26
APPLICATION_OCTET_STREAM = 100
APPLICATION_UNKNOWN = 101
ALLOWED_MIMES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP, APPLICATION_FLASH, VIDEO_FLV, VIDEO_MP4, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG, APPLICATION_PDF, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, VIDEO_WMV )
SEARCHABLE_MIMES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, APPLICATION_FLASH, VIDEO_FLV, VIDEO_MP4, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG, APPLICATION_PDF, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, VIDEO_WMV )
ALLOWED_MIMES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP, APPLICATION_FLASH, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG, APPLICATION_PDF, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, VIDEO_WMV )
SEARCHABLE_MIMES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, APPLICATION_FLASH, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG, APPLICATION_PDF, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, VIDEO_WMV )
IMAGES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP )
AUDIO = ( AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA )
VIDEO = ( VIDEO_FLV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
VIDEO = ( VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
NATIVE_VIDEO = ( VIDEO_FLV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
NATIVE_VIDEO = ( VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
APPLICATIONS = ( APPLICATION_FLASH, APPLICATION_PDF, APPLICATION_ZIP )
@ -295,7 +296,7 @@ NOISY_MIMES = tuple( [ APPLICATION_FLASH ] + list( AUDIO ) + list( VIDEO ) )
ARCHIVES = ( APPLICATION_ZIP, APPLICATION_HYDRUS_ENCRYPTED_ZIP )
MIMES_WITH_THUMBNAILS = ( APPLICATION_FLASH, IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP, VIDEO_FLV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
MIMES_WITH_THUMBNAILS = ( APPLICATION_FLASH, IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
# mp3 header is complicated
@ -327,6 +328,7 @@ mime_enum_lookup[ 'audio/x-ms-wma' ] = AUDIO_WMA
mime_enum_lookup[ 'text/html' ] = TEXT_HTML
mime_enum_lookup[ 'video/png' ] = VIDEO_APNG
mime_enum_lookup[ 'video/x-flv' ] = VIDEO_FLV
mime_enum_lookup[ 'video/quicktime' ] = VIDEO_MOV
mime_enum_lookup[ 'video/mp4' ] = VIDEO_MP4
mime_enum_lookup[ 'video/mpeg' ] = VIDEO_MPEG
mime_enum_lookup[ 'video/x-ms-wmv' ] = VIDEO_WMV
@ -360,6 +362,7 @@ mime_string_lookup[ AUDIO ] = 'audio'
mime_string_lookup[ TEXT_HTML ] = 'text/html'
mime_string_lookup[ VIDEO_APNG ] = 'video/png'
mime_string_lookup[ VIDEO_FLV ] = 'video/x-flv'
mime_string_lookup[ VIDEO_MOV ] = 'video/quicktime'
mime_string_lookup[ VIDEO_MP4 ] = 'video/mp4'
mime_string_lookup[ VIDEO_MPEG ] = 'video/mpeg'
mime_string_lookup[ VIDEO_WMV ] = 'video/x-ms-wmv'
@ -391,6 +394,7 @@ mime_ext_lookup[ AUDIO_WMA ] = '.wma'
mime_ext_lookup[ TEXT_HTML ] = '.html'
mime_ext_lookup[ VIDEO_APNG ] = '.png'
mime_ext_lookup[ VIDEO_FLV ] = '.flv'
mime_ext_lookup[ VIDEO_MOV ] = '.mov'
mime_ext_lookup[ VIDEO_MP4 ] = '.mp4'
mime_ext_lookup[ VIDEO_MPEG ] = '.mpeg'
mime_ext_lookup[ VIDEO_WMV ] = '.wmv'

View File

@ -174,6 +174,11 @@ def ConvertIntToBytes( size ):
return '%.0f' % size + suffixes[ suffix_index ] + 'B'
def ConvertIntToFirst( n ):
# straight from stack, wew
return "%d%s" % (n,"tsnrhtdd"[(n/10%10!=1)*(n%10<4)*n%10::4])
def ConvertIntToPixels( i ):
if i == 1: return 'pixels'

View File

@ -37,6 +37,7 @@ header_and_mime = [
( 4, 'ftypmp4', HC.VIDEO_MP4 ),
( 4, 'ftypisom', HC.VIDEO_MP4 ),
( 4, 'ftypM4V', HC.VIDEO_MP4 ),
( 4, 'ftypqt', HC.VIDEO_MOV ),
( 0, 'fLaC', HC.AUDIO_FLAC ),
( 0, '\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C', HC.UNDETERMINED_WM )
]
@ -168,7 +169,7 @@ def GetFileInfo( path ):
( ( width, height ), duration, num_frames ) = HydrusFlashHandling.GetFlashProperties( path )
elif mime in ( HC.VIDEO_FLV, HC.VIDEO_WMV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_WEBM, HC.VIDEO_MPEG ):
elif mime in ( HC.VIDEO_FLV, HC.VIDEO_WMV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_WEBM, HC.VIDEO_MPEG ):
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetFFMPEGVideoProperties( path )

View File

@ -1,16 +1,45 @@
import bs4
import HydrusData
import HydrusSerialisable
def RenderTagRule( ( name, attrs, index ) ):
if index is None:
result = 'all ' + name + ' tags'
else:
result = HydrusData.ConvertIntToFirst( index + 1 ) + name + ' tag'
if len( attrs ) > 0:
result += ' with ' + ' and '.join( [ key + ' = ' + value for ( key, value ) in attrs.items() ] )
return result
class ParseFormula( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_HTML_PARSE_FORMULA
SERIALISABLE_VERSION = 1
def __init__( self ):
def __init__( self, tag_rules = None, content_rule = None ):
self._tag_rules = []
if tag_rules is None:
tag_rules = [ ( 'a', {}, None ) ]
self._content_rule = None
if content_rule is None:
content_rule = 'src'
self._tag_rules = tag_rules
self._content_rule = content_rule
def _GetSerialisableInfo( self ):
@ -58,35 +87,6 @@ class ParseFormula( HydrusSerialisable.SerialisableBase ):
return results
def IsValid( self ):
return len( self._tag_rules ) > 0 and self._content_rule is not None
def PopTagsRule( self ):
self._tag_rules.pop()
def PushTagsRule( self, name = None, attrs = {}, index = None ):
self._tag_rules.append( ( name, attrs, index ) )
def Duplicate( self ):
new_formula = ParseFormula()
for ( name, attrs, index ) in self._tag_rules:
new_formula.PushTagsRule( name, dict( attrs ), index )
new_formula.SetContentRule( self._content_rule )
return new_formula
def Parse( self, html ):
root = bs4.BeautifulSoup( html, 'lxml' )
@ -110,9 +110,9 @@ class ParseFormula( HydrusSerialisable.SerialisableBase ):
return contents
def SetContentRule( self, attr = None ):
def ToTuple( self ):
self._content_rule = attr
return ( self._tag_rules, self._content_rule )
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_HTML_PARSE_FORMULA ] = ParseFormula

View File

@ -81,12 +81,19 @@ def AddUPnPMapping( internal_client, internal_port, external_port, protocol, des
if 'x.x.x.x:' + str( external_port ) + ' TCP is redirected to internal ' + internal_client + ':' + str( internal_port ) in output:
raise Exception( 'The UPnP mapping of ' + internal_client + ':' + internal_port + '->external:' + external_port + ' already exists as a port forward. If this UPnP mapping is automatic, please disable it.' )
raise HydrusExceptions.FirewallException( 'The UPnP mapping of ' + internal_client + ':' + internal_port + '->external:' + external_port + ' already exists as a port forward. If this UPnP mapping is automatic, please disable it.' )
if output is not None and 'failed with code' in output:
raise Exception( 'Problem while trying to add UPnP mapping:' + os.linesep * 2 + HydrusData.ToUnicode( output ) )
if 'UnknownError' in output:
raise HydrusExceptions.FirewallException( 'Problem while trying to add UPnP mapping:' + os.linesep * 2 + HydrusData.ToUnicode( output ) )
else:
raise Exception( 'Problem while trying to add UPnP mapping:' + os.linesep * 2 + HydrusData.ToUnicode( output ) )
if error is not None and len( error ) > 0:

View File

@ -29,6 +29,7 @@ SERIALISABLE_TYPE_SERVER_TO_CLIENT_PETITION = 24
SERIALISABLE_TYPE_ACCOUNT_IDENTIFIER = 25
SERIALISABLE_TYPE_LIST = 26
SERIALISABLE_TYPE_HTML_PARSE_FORMULA = 27
SERIALISABLE_TYPE_URLS_IMPORT = 28
SERIALISABLE_TYPES_TO_OBJECT_TYPES = {}