Version 107

This commit is contained in:
Hydrus 2014-03-12 17:08:23 -05:00
parent de15e3e5ef
commit 3339e5e0be
19 changed files with 713 additions and 680 deletions

View File

@ -11,6 +11,12 @@
<p>So, if you want to play a flash game in fullscreen, keep your mouse inside the window.</p>
<h3>exclude deleted files</h3>
<p>In the client's options is a checkbox to exclude deleted files. It recurs pretty much anywhere you can import, under 'advanced import options'. If you select this, any file you ever deleted will be excluded from all future remote searches and import operations. This can stop you from importing/downloading and filtering out the same bad files several times over. The default is off. You may wish to have it set one way most of the time, but switch it the other just for one specific import or search.</p>
<h3>tag censorship</h3>
<p>If you do not like a particular tag or namespace, you can easily hide it with <i>services->manage tag censorship</i>:</p>
<p><img src="tag_censorship.png" /></p>
<p>You can exclude single tags, like as shown above, or entire namespaces (enter the colon, like 'species:'), or all namespaced tags (use ':'), or all unnamespaced tags (''). 'all known tags' will be applied to everything, as well as any repository-specific rules you set.</p>
<p>A blacklist excludes whatever is listed; a whitelist excludes whatever is <i>not</i> listed.</p>
<p>This censorship is local to your client. No one else will experience your changes or know what you have censored.</p>
<h3>importing and adding tags at the same time</h3>
<p><i>Add tags before importing</i> on <i>file->import files</i> lets you give tags to the files you import <i>en masse</i>, and intelligently, using regexes that parse filename:</p>
<p><img src="gunnerkrigg_import.png"/></p>

View File

@ -8,6 +8,27 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 107</h3></li>
<ul>
<li>converted 'namespace blacklists' to more general 'tag censorship'</li>
<li>tag censorship now accepts unnamespaced tag bans</li>
<li>tag censorship now accepts all namespaced tags bans</li>
<li>tag censorship now permits bans for all tag services</li>
<li>fixed several small bugs carried over from the namespace blacklists code</li>
<li>added a bit about tag censorship to help, under advanced.html</li>
<li>fixed youtube downloading</li>
<li>file import dialog now ignores dupe paths</li>
<li>improved update status text in review services window</li>
<li>fixed erroneous capital P on pending menu</li>
<li>fixed shortcut input dialog's ok button</li>
<li>fixed modify tags dialog's admin modify button</li>
<li>added several bits of explanation text to advanced dialogs</li>
<li>added more helpful timestamp to news dialog</li>
<li>neatened export dialog's layout</li>
<li>corrected some double-escaped backslashes in the export filename pattern button menu</li>
<li>fixed modify accounts dialog's http request</li>
<li>improved grammar in db pubsub code</li>
</ul>
<li><h3>version 106</h3></li>
<ul>
<li>download by raw url now sends the correct text to the popup on successful import</li>

View File

@ -12,13 +12,7 @@
<p>You will need to install python 2.7 and several python modules. To see what your system needs, just keep running client.pyw and see what it complains about missing.</p>
<p>I recently got the client working on Ubuntu 13.10, which required:</p>
<p><ul>
<li>sudo pip install beautifulsoup4</li>
<li>sudo pip install pyyaml</li>
<li>sudo pip install python-potr</li>
<li>sudo pip install hsaudiotag</li>
<li>sudo pip install PyPDF2</li>
<li>sudo pip install flvlib</li>
<li>sudo pip install pafy</li>
<li>sudo pip install beautifulsoup4 pyyaml python-potr hsaudiotag PyPDF2 flvlib pafy</li>
<li>sudo apt-get install python-numpy</li>
<li>sudo apt-get install python-opencv</li>
<li>and installing a recent version of wxPython, which is difficult and best done <a href="http://wiki.wxpython.org/CheckInstall">like so</a></li>
@ -30,8 +24,9 @@
<li>brew install opencv</li>
<li>export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH</li>
</ul></p>
<p>For Windows, you'll find <a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/">this</a> page very helpful. I have a fair bit of experience with Windows python, so send me a mail if you need help.</a>
<p>For Windows, you can do the same thing with easy_install and pip and module installers. Since it doesn't natively come with Python, you'll probably need to go through a bigger list. You'll find <a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/">this</a> page very helpful. I have a fair bit of experience with Windows python, so send me a mail if you need help.</a>
<p>Some people have encountered problems with wxPython 3.0, so I use 2.9.x in my official releases. Again, YMMV.</p>
<p>You'll probably want to install Pillow (pip should do it for Linux/OS X, easy_install for Windows) instead of PIL, but go back to PIL if Pillow doesn't work.</p>
<p>Once you have everything set up, client.pyw and server.pyw should look for and run off the database files just like the executables.</p>
<p>I develop hydrus on 64-bit Win 7, so the program is much more stable and reasonable on Windows. I am developing for Linux and OS X, but I do not have as much experience with them, so I would particularly appreciate your Linux/OS X bug reports and any informed suggestions.</p>
<h3>my code</h3>

BIN
help/tag_censorship.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -2127,15 +2127,39 @@ class Service( HC.HydrusYAMLBase ):
def GetUpdateStatus( self ):
if not self._info[ 'account' ].HasPermission( HC.GET_DATA ): return 'updates on hold'
account = self._info[ 'account' ]
now = HC.GetNow()
first_timestamp = self._info[ 'first_timestamp' ]
next_download_timestamp = self._info[ 'next_download_timestamp' ]
next_processing_timestamp = self._info[ 'next_processing_timestamp' ]
if first_timestamp is None:
num_updates = 0
num_updates_downloaded = 0
num_updates_processed = 0
else:
if self.CanDownloadUpdate(): return 'downloading ' + HC.ConvertTimestampToPrettySync( self._info[ 'next_download_timestamp' ] )
elif self.CanProcessUpdate(): return 'processing ' + HC.ConvertTimestampToPrettySync( self._info[ 'next_processing_timestamp' ] )
elif self.HasRecentError(): return 'due to a previous error, update is delayed - next check ' + self.GetRecentErrorPending()
else: return 'fully synchronised - next update ' + HC.ConvertTimestampToPrettyPending( self._info[ 'next_download_timestamp' ] + HC.UPDATE_DURATION + 1800 )
num_updates = ( now - first_timestamp ) / HC.UPDATE_DURATION
num_updates_downloaded = ( next_download_timestamp - first_timestamp ) / HC.UPDATE_DURATION
num_updates_processed = ( next_processing_timestamp - first_timestamp ) / HC.UPDATE_DURATION
downloaded_text = HC.ConvertIntToPrettyString( num_updates_downloaded ) + '/' + HC.ConvertIntToPrettyString( num_updates )
if not self._info[ 'account' ].HasPermission( HC.GET_DATA ): status = 'updates on hold'
else:
if self.CanDownloadUpdate(): status = 'downloaded up to ' + HC.ConvertTimestampToPrettySync( self._info[ 'next_download_timestamp' ] )
elif self.CanProcessUpdate(): status = 'processed up to ' + HC.ConvertTimestampToPrettySync( self._info[ 'next_processing_timestamp' ] )
elif self.HasRecentError(): status = 'due to a previous error, update is delayed - next check ' + self.GetRecentErrorPending()
else: status = 'fully synchronised - next update ' + HC.ConvertTimestampToPrettyPending( self._info[ 'next_download_timestamp' ] + HC.UPDATE_DURATION + 1800 )
return downloaded_text + ' - ' + status
def HasRecentError( self ):

View File

@ -244,7 +244,7 @@ The database will be locked while the backup occurs, which may lock up your gui
self._managers = {}
self._managers[ 'hydrus_sessions' ] = HydrusSessions.HydrusSessionManagerClient()
self._managers[ 'namespace_blacklists' ] = HydrusTags.NamespaceBlacklistsManager()
self._managers[ 'tag_censorship' ] = HydrusTags.TagCensorshipManager()
self._managers[ 'tag_siblings' ] = HydrusTags.TagSiblingsManager()
self._managers[ 'tag_parents' ] = HydrusTags.TagParentsManager()
self._managers[ 'undo' ] = CC.UndoManager()

View File

@ -61,7 +61,7 @@ class FileDB():
hashes = { hash for ( hash, thumbnail ) in thumbnails }
self.pub( 'new_thumbnails', hashes )
self.pub_after_commit( 'new_thumbnails', hashes )
def _CopyFiles( self, c, hashes ):
@ -97,7 +97,7 @@ class FileDB():
except Exception as e: error_messages.add( HC.u( e ) )
self.pub( 'clipboard', 'paths', paths )
self.pub_after_commit( 'clipboard', 'paths', paths )
if len( error_messages ) > 0: raise Exception( 'Some of the file exports failed with the following error message(s):' + os.linesep + os.linesep.join( error_messages ) )
@ -288,7 +288,7 @@ class MessageDB():
message = ClientConstantsMessages.Message( message_key, contact_from, destinations, timestamp, body, attachment_hashes, inbox )
self.pub( 'new_message', conversation_key, message )
self.pub_after_commit( 'new_message', conversation_key, message )
if serverside_message_key is not None:
@ -353,8 +353,8 @@ class MessageDB():
c.execute( 'DELETE FROM message_inbox WHERE message_id IN ' + HC.SplayListForDB( message_ids ) + ';' )
self.pub( 'archive_conversation_data', conversation_key )
self.pub( 'archive_conversation_gui', conversation_key )
self.pub_after_commit( 'archive_conversation_data', conversation_key )
self.pub_after_commit( 'archive_conversation_gui', conversation_key )
self._DoStatusNumInbox( c )
@ -388,8 +388,8 @@ class MessageDB():
c.execute( 'DELETE FROM message_bodies WHERE docid IN ' + splayed_message_ids + ';' )
c.execute( 'DELETE FROM conversation_subjects WHERE docid IN ' + splayed_message_ids + ';' )
self.pub( 'delete_conversation_data', conversation_key )
self.pub( 'delete_conversation_gui', conversation_key )
self.pub_after_commit( 'delete_conversation_data', conversation_key )
self.pub_after_commit( 'delete_conversation_gui', conversation_key )
self._DoStatusNumInbox( c )
@ -402,9 +402,9 @@ class MessageDB():
c.execute( 'DELETE FROM message_bodies WHERE docid = ?;', ( message_id, ) )
c.execute( 'DELETE FROM conversation_subjects WHERE docid = ?;', ( message_id, ) )
self.pub( 'delete_draft_data', draft_key )
self.pub( 'delete_draft_gui', draft_key )
self.pub( 'notify_check_messages' )
self.pub_after_commit( 'delete_draft_data', draft_key )
self.pub_after_commit( 'delete_draft_gui', draft_key )
self.pub_after_commit( 'notify_check_messages' )
def _DoMessageQuery( self, c, query_key, search_context ):
@ -498,7 +498,7 @@ class MessageDB():
conversations = self._GetConversations( c, search_context, query_message_ids )
self.pub( 'message_query_done', query_key, conversations )
self.pub_after_commit( 'message_query_done', query_key, conversations )
def _DoStatusNumInbox( self, c ):
@ -510,7 +510,7 @@ class MessageDB():
if num_inbox == 0: inbox_string = 'message inbox empty'
else: inbox_string = HC.u( num_inbox ) + ' in message inbox'
self.pub( 'inbox_status', inbox_string )
self.pub_after_commit( 'inbox_status', inbox_string )
def _DraftMessage( self, c, draft_message ):
@ -545,7 +545,7 @@ class MessageDB():
c.executemany( 'INSERT INTO message_attachments ( message_id, hash_id ) VALUES ( ?, ? );', [ ( message_id, hash_id ) for hash_id in hash_ids ] )
self.pub( 'draft_saved', draft_key, draft_message )
self.pub_after_commit( 'draft_saved', draft_key, draft_message )
def _FlushMessageStatuses( self, c ):
@ -571,8 +571,8 @@ class MessageDB():
status_updates = [ ( contact_key, self._GetStatus( c, status_id ) ) for ( contact_key, status_id ) in status_infos ]
self.pub( 'message_statuses_data', message_key, status_updates )
self.pub( 'message_statuses_gui', message_key, status_updates )
self.pub_after_commit( 'message_statuses_data', message_key, status_updates )
self.pub_after_commit( 'message_statuses_gui', message_key, status_updates )
@ -1021,8 +1021,8 @@ class MessageDB():
c.executemany( 'INSERT OR IGNORE INTO message_inbox ( message_id ) VALUES ( ? );', inserts )
self.pub( 'inbox_conversation_data', conversation_key )
self.pub( 'inbox_conversation_gui', conversation_key )
self.pub_after_commit( 'inbox_conversation_data', conversation_key )
self.pub_after_commit( 'inbox_conversation_gui', conversation_key )
self._DoStatusNumInbox( c )
@ -1065,7 +1065,7 @@ class MessageDB():
self.pub( 'notify_new_contacts' )
self.pub_after_commit( 'notify_new_contacts' )
def _UpdateMessageStatuses( self, c, message_key, status_updates ):
@ -1084,9 +1084,9 @@ class MessageDB():
c.executemany( 'UPDATE message_destination_map SET status_id = ? WHERE contact_id_to = ? AND message_id = ?;', [ ( status_id, contact_id, message_id ) for ( contact_id, status_id ) in updates ] )
self.pub( 'message_statuses_data', message_key, status_updates )
self.pub( 'message_statuses_gui', message_key, status_updates )
self.pub( 'notify_check_messages' )
self.pub_after_commit( 'message_statuses_data', message_key, status_updates )
self.pub_after_commit( 'message_statuses_gui', message_key, status_updates )
self.pub_after_commit( 'notify_check_messages' )
class RatingDB():
@ -1146,8 +1146,8 @@ class RatingDB():
c.executemany( 'INSERT INTO ratings ( service_id, hash_id, count, rating, score ) VALUES ( ?, ?, ?, ?, ? );', [ ( service_id, self._GetHashId( c, hash ), count, rating, HC.CalculateScoreFromRating( count, rating ) ) for ( hash, count, rating ) in ratings if count > 0 ] )
# these need count and score in
#self.pub( 'content_updates_data', [ HC.ContentUpdate( HC.CONTENT_UPDATE_RATING_REMOTE, service_identifier, ( hash, ), rating ) for ( hash, rating ) in ratings ] )
#self.pub( 'content_updates_gui', [ HC.ContentUpdate( HC.CONTENT_UPDATE_RATING_REMOTE, service_identifier, ( hash, ), rating ) for ( hash, rating ) in ratings ] )
#self.pub_after_commit( 'content_updates_data', [ HC.ContentUpdate( HC.CONTENT_UPDATE_RATING_REMOTE, service_identifier, ( hash, ), rating ) for ( hash, rating ) in ratings ] )
#self.pub_after_commit( 'content_updates_gui', [ HC.ContentUpdate( HC.CONTENT_UPDATE_RATING_REMOTE, service_identifier, ( hash, ), rating ) for ( hash, rating ) in ratings ] )
class TagDB():
@ -1470,7 +1470,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
self._UpdateAutocompleteTagCacheFromFiles( c, service_id, actual_hash_ids_i_can_delete, -1 )
self.pub( 'notify_new_pending' )
self.pub_after_commit( 'notify_new_pending' )
def _DeleteHydrusSessionKey( self, c, service_identifier ):
@ -1564,11 +1564,11 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
c.execute( 'DELETE FROM file_petitions WHERE service_id = ?;', ( service_id, ) )
self.pub( 'notify_new_pending' )
self.pub( 'notify_new_siblings' )
self.pub( 'notify_new_parents' )
self.pub_after_commit( 'notify_new_pending' )
self.pub_after_commit( 'notify_new_siblings' )
self.pub_after_commit( 'notify_new_parents' )
self.pub_service_updates( { service_identifier : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_DELETE_PENDING ) ] } )
self.pub_service_updates_after_commit( { service_identifier : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_DELETE_PENDING ) ] } )
def _DeleteYAMLDump( self, c, dump_type, dump_name = None ):
@ -1739,9 +1739,9 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
tags = { tag for ( tag, num_tags ) in tag_info }
namespace_blacklists_manager = HC.app.GetManager( 'namespace_blacklists' )
tag_censorship_manager = HC.app.GetManager( 'tag_censorship' )
filtered_tags = namespace_blacklists_manager.FilterTags( tag_service_identifier, tags )
filtered_tags = tag_censorship_manager.FilterTags( tag_service_identifier, tags )
predicates = [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', tag ), num_tags ) for ( tag, num_tags ) in tag_info if tag in filtered_tags ]
@ -1904,9 +1904,11 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
if num_tags_zero or num_tags_nonzero or len( tag_predicates ) > 0:
namespace_blacklists_manager = HC.app.GetManager( 'namespace_blacklists' )
tag_censorship_manager = HC.app.GetManager( 'tag_censorship' )
( blacklist, namespaces ) = namespace_blacklists_manager.GetInfo( tag_service_identifier )
( blacklist, tags ) = tag_censorship_manager.GetInfo( tag_service_identifier )
namespaces = [ tag for tag in tags if ':' in tag ]
if len( namespaces ) == 0: namespace_predicate = ''
else:
@ -2145,7 +2147,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def _GetHashIdsFromTag( self, c, file_service_identifier, tag_service_identifier, tag, include_current_tags, include_pending_tags ):
# this does siblings and blacklists too!
# this does siblings and censorship too!
statuses = []
@ -2163,9 +2165,9 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
tags = siblings_manager.GetAllSiblings( tag )
namespace_blacklists_manager = HC.app.GetManager( 'namespace_blacklists' )
tag_censorship_manager = HC.app.GetManager( 'tag_censorship' )
tags = namespace_blacklists_manager.FilterTags( tag_service_identifier, tags )
tags = tag_censorship_manager.FilterTags( tag_service_identifier, tags )
hash_ids = set()
@ -2404,31 +2406,6 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
return mime
def _GetNamespaceBlacklists( self, c, service_identifier = None ):
if service_identifier is None:
result = []
for ( service_id, blacklist, namespaces ) in c.execute( 'SELECT service_id, blacklist, namespaces FROM namespace_blacklists;' ).fetchall():
service_identifier = self._GetServiceIdentifier( c, service_id )
result.append( ( service_identifier, blacklist, namespaces ) )
else:
service_id = self._GetServiceId( c, service_identifier )
result = c.execute( 'SELECT blacklist, namespaces FROM namespace_blacklists WHERE service_id = ?;', ( service_id, ) ).fetchone()
if result is None: result = ( True, [] )
return result
def _GetNews( self, c, service_identifier ):
service_id = self._GetServiceId( c, service_identifier )
@ -2828,6 +2805,31 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
return shutdown_timestamps
def _GetTagCensorship( self, c, service_identifier = None ):
if service_identifier is None:
result = []
for ( service_id, blacklist, tags ) in c.execute( 'SELECT service_id, blacklist, tags FROM tag_censorship;' ).fetchall():
service_identifier = self._GetServiceIdentifier( c, service_id )
result.append( ( service_identifier, blacklist, tags ) )
else:
service_id = self._GetServiceId( c, service_identifier )
result = c.execute( 'SELECT blacklist, tags FROM tag_censorship WHERE service_id = ?;', ( service_id, ) ).fetchone()
if result is None: result = ( True, [] )
return result
def _GetTagParents( self, c, service_identifier = None ):
if service_identifier is None:
@ -3021,7 +3023,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
c.execute( 'DELETE FROM file_inbox WHERE hash_id = ?;', ( hash_id, ) )
self.pub_content_updates( { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, set( ( hash, ) ) ) ] } )
self.pub_content_updates_after_commit( { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, set( ( hash, ) ) ) ] } )
can_add = False
@ -3084,7 +3086,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_ADD, ( hash, size, mime, timestamp, width, height, duration, num_frames, num_words ) )
self.pub_content_updates( { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ content_update ] } )
self.pub_content_updates_after_commit( { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ content_update ] } )
( md5, sha1 ) = HydrusFileHandling.GetMD5AndSHA1FromPath( path )
@ -3343,7 +3345,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
c.execute( 'DROP TABLE temp_operation;' )
self.pub( 'notify_new_pending' )
self.pub_after_commit( 'notify_new_pending' )
else:
@ -3452,7 +3454,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
special_content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( parent_tag, existing_hashes ) )
self.pub_content_updates( { service_identifier : [ special_content_update ] } )
self.pub_content_updates_after_commit( { service_identifier : [ special_content_update ] } )
elif action in ( HC.CONTENT_UPDATE_PENDING, HC.CONTENT_UPDATE_PETITION ):
@ -3484,7 +3486,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
special_content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_PENDING, ( parent_tag, existing_hashes ) )
self.pub_content_updates( { service_identifier : [ special_content_update ] } )
self.pub_content_updates_after_commit( { service_identifier : [ special_content_update ] } )
notify_new_pending = True
@ -3563,17 +3565,17 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
if notify_new_downloads: self.pub( 'notify_new_downloads' )
if notify_new_pending: self.pub( 'notify_new_pending' )
if notify_new_parents: self.pub( 'notify_new_parents' )
if notify_new_downloads: self.pub_after_commit( 'notify_new_downloads' )
if notify_new_pending: self.pub_after_commit( 'notify_new_pending' )
if notify_new_parents: self.pub_after_commit( 'notify_new_parents' )
if notify_new_siblings:
self.pub( 'notify_new_siblings' )
self.pub( 'notify_new_parents' )
self.pub_after_commit( 'notify_new_siblings' )
self.pub_after_commit( 'notify_new_parents' )
if notify_new_thumbnails: self.pub( 'notify_new_thumbnails' )
if notify_new_thumbnails: self.pub_after_commit( 'notify_new_thumbnails' )
self.pub_content_updates( service_identifiers_to_content_updates )
self.pub_content_updates_after_commit( service_identifiers_to_content_updates )
def _ProcessServiceUpdates( self, c, service_identifiers_to_service_updates ):
@ -3627,7 +3629,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
text = service_identifier.GetName() + ' at ' + time.ctime( timestamp ) + ':' + os.linesep + os.linesep + post
self.pub( 'message', HC.Message( HC.MESSAGE_TYPE_TEXT, { 'text' : text } ) )
self.pub_after_commit( 'message', HC.Message( HC.MESSAGE_TYPE_TEXT, { 'text' : text } ) )
@ -3660,7 +3662,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
self.pub_service_updates( service_identifiers_to_service_updates )
self.pub_service_updates_after_commit( service_identifiers_to_service_updates )
for ( service_id, nums_bytes ) in HC.BuildKeyToListDict( requests_made ).items():
@ -3674,7 +3676,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
c.execute( 'UPDATE services SET info = ? WHERE service_id = ?;', ( info, service_id ) )
if do_new_permissions: self.pub( 'notify_new_permissions' )
if do_new_permissions: self.pub_after_commit( 'notify_new_permissions' )
def _RebuildTagServicePrecedenceCache( self, c ):
@ -3740,29 +3742,29 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
info[ 'next_processing_timestamp' ] = 0
self.pub( 'notify_restart_repo_sync_daemon' )
self.pub_after_commit( 'notify_restart_repo_sync_daemon' )
self._AddService( c, service_identifier, info )
self.pub_service_updates( { service_identifier : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_RESET ) ] } )
self.pub( 'notify_new_pending' )
self.pub( 'permissions_are_stale' )
self.pub_service_updates_after_commit( { service_identifier : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_RESET ) ] } )
self.pub_after_commit( 'notify_new_pending' )
self.pub_after_commit( 'permissions_are_stale' )
HC.ShowText( 'reset ' + service_name )
def _SetNamespaceBlacklists( self, c, info ):
def _SetTagCensorship( self, c, info ):
c.execute( 'DELETE FROM namespace_blacklists;' )
c.execute( 'DELETE FROM tag_censorship;' )
for ( service_identifier, blacklist, namespaces ) in info:
for ( service_identifier, blacklist, tags ) in info:
service_id = self._GetServiceId( c, service_identifier )
c.execute( 'INSERT OR IGNORE INTO namespace_blacklists ( service_id, blacklist, namespaces ) VALUES ( ?, ?, ? );', ( service_id, blacklist, namespaces ) )
c.execute( 'INSERT OR IGNORE INTO tag_censorship ( service_id, blacklist, tags ) VALUES ( ?, ?, ? );', ( service_id, blacklist, tags ) )
self.pub( 'notify_new_namespace_blacklists' )
self.pub_after_commit( 'notify_new_tag_censorship' )
def _SetTagServicePrecedence( self, c, service_identifiers ):
@ -3781,7 +3783,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
service_identifiers_to_service_updates = { HC.COMBINED_TAG_SERVICE_IDENTIFIER : [ service_update ] }
self.pub_service_updates( service_identifiers_to_service_updates )
self.pub_service_updates_after_commit( service_identifiers_to_service_updates )
def _SetYAMLDump( self, c, dump_type, dump_name, data ):
@ -4213,7 +4215,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
service_identifiers_to_service_updates = { client_service_identifier : [ service_update ] }
self.pub_service_updates( service_identifiers_to_service_updates )
self.pub_service_updates_after_commit( service_identifiers_to_service_updates )
if service_type in HC.REPOSITORIES:
@ -4261,8 +4263,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
self._RecalcCombinedMappings( c )
self.pub( 'notify_new_pending' )
self.pub( 'notify_new_services' )
self.pub_after_commit( 'notify_new_pending' )
self.pub_after_commit( 'notify_new_services' )
def _UpdateServices( self, c, edit_log ):
@ -4291,7 +4293,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
service_identifiers_to_service_updates = { service_identifier : [ service_update ] }
self.pub_service_updates( service_identifiers_to_service_updates )
self.pub_service_updates_after_commit( service_identifiers_to_service_updates )
service_type = service_identifier.GetType()
@ -4346,8 +4348,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
self._RecalcCombinedMappings( c )
self.pub( 'notify_new_pending' )
self.pub( 'notify_new_services' )
self.pub_after_commit( 'notify_new_pending' )
self.pub_after_commit( 'notify_new_services' )
def _UpdateServiceInfo( self, c, service_id, update ):
@ -4617,8 +4619,6 @@ class DB( ServiceDB ):
c.execute( 'CREATE INDEX messages_contact_id_from_index ON messages ( contact_id_from );' )
c.execute( 'CREATE INDEX messages_timestamp_index ON messages ( timestamp );' )
c.execute( 'CREATE TABLE namespace_blacklists ( service_id INTEGER PRIMARY KEY REFERENCES services ON DELETE CASCADE, blacklist INTEGER_BOOLEAN, namespaces TEXT_YAML );' )
c.execute( 'CREATE TABLE namespaces ( namespace_id INTEGER PRIMARY KEY, namespace TEXT );' )
c.execute( 'CREATE UNIQUE INDEX namespaces_namespace_index ON namespaces ( namespace );' )
@ -4645,6 +4645,8 @@ class DB( ServiceDB ):
c.execute( 'CREATE TABLE statuses ( status_id INTEGER PRIMARY KEY, status TEXT );' )
c.execute( 'CREATE UNIQUE INDEX statuses_status_index ON statuses ( status );' )
c.execute( 'CREATE TABLE tag_censorship ( service_id INTEGER PRIMARY KEY REFERENCES services ON DELETE CASCADE, blacklist INTEGER_BOOLEAN, tags TEXT_YAML );' )
c.execute( 'CREATE TABLE tag_service_precedence ( service_id INTEGER PRIMARY KEY REFERENCES services ON DELETE CASCADE, precedence INTEGER );' )
c.execute( 'CREATE TABLE tag_parents ( service_id INTEGER REFERENCES services ON DELETE CASCADE, child_namespace_id INTEGER, child_tag_id INTEGER, parent_namespace_id INTEGER, parent_tag_id INTEGER, status INTEGER, PRIMARY KEY ( service_id, child_namespace_id, child_tag_id, parent_namespace_id, parent_tag_id, status ) );' )
@ -4728,10 +4730,10 @@ class DB( ServiceDB ):
for path in thumbnail_paths: os.remove( path )
self.pub( 'thumbnail_resize' )
self.pub_after_commit( 'thumbnail_resize' )
self.pub( 'notify_new_options' )
self.pub_after_commit( 'notify_new_options' )
def _SetPassword( self, c, password ):
@ -5058,6 +5060,28 @@ class DB( ServiceDB ):
if version == 106:
c.execute( 'CREATE TABLE tag_censorship ( service_id INTEGER PRIMARY KEY REFERENCES services ON DELETE CASCADE, blacklist INTEGER_BOOLEAN, tags TEXT_YAML );' )
result = c.execute( 'SELECT service_id, blacklist, namespaces FROM namespace_blacklists;' ).fetchall()
for ( service_id, blacklist, namespaces ) in result:
tags = [ namespace + ':' for namespace in namespaces ]
if ':' in tags: # don't want to change ''!
tags.remove( ':' )
tags.append( '' )
c.execute( 'INSERT INTO tag_censorship ( service_id, blacklist, tags ) VALUES ( ?, ?, ? );', ( service_id, blacklist, tags ) )
c.execute( 'DROP TABLE namespace_blacklists;' )
c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
HC.is_db_updated = True
@ -6680,18 +6704,18 @@ class DB( ServiceDB ):
def GetLoopFinished( self ): return self._loop_finished
def pub( self, topic, *args, **kwargs ): self._pubsubs.append( ( topic, args, kwargs ) )
def pub_after_commit( self, topic, *args, **kwargs ): self._pubsubs.append( ( topic, args, kwargs ) )
def pub_content_updates( self, service_identifiers_to_content_updates ):
def pub_content_updates_after_commit( self, service_identifiers_to_content_updates ):
self.pub( 'content_updates_data', service_identifiers_to_content_updates )
self.pub( 'content_updates_gui', service_identifiers_to_content_updates )
self.pub_after_commit( 'content_updates_data', service_identifiers_to_content_updates )
self.pub_after_commit( 'content_updates_gui', service_identifiers_to_content_updates )
def pub_service_updates( self, service_identifiers_to_service_updates ):
def pub_service_updates_after_commit( self, service_identifiers_to_service_updates ):
self.pub( 'service_updates_data', service_identifiers_to_service_updates )
self.pub( 'service_updates_gui', service_identifiers_to_service_updates )
self.pub_after_commit( 'service_updates_data', service_identifiers_to_service_updates )
self.pub_after_commit( 'service_updates_gui', service_identifiers_to_service_updates )
def MainLoop( self ):
@ -6724,7 +6748,6 @@ class DB( ServiceDB ):
elif action == 'message_keys_to_download': result = self._GetMessageKeysToDownload( c, *args, **kwargs )
elif action == 'message_system_predicates': result = self._GetMessageSystemPredicates( c, *args, **kwargs )
elif action == 'messages_to_send': result = self._GetMessagesToSend( c, *args, **kwargs )
elif action == 'namespace_blacklists': result = self._GetNamespaceBlacklists( c, *args, **kwargs )
elif action == 'news': result = self._GetNews( c, *args, **kwargs )
elif action == 'nums_pending': result = self._GetNumsPending( c, *args, **kwargs )
elif action == 'pending': result = self._GetPending( c, *args, **kwargs )
@ -6738,6 +6761,7 @@ class DB( ServiceDB ):
elif action == 'shutdown_timestamps': result = self._GetShutdownTimestamps( c, *args, **kwargs )
elif action == 'status_num_inbox': result = self._DoStatusNumInbox( c, *args, **kwargs )
elif action == 'subscriptions': result = self._GetYAMLDump( c, YAML_DUMP_ID_SUBSCRIPTION, *args, **kwargs )
elif action == 'tag_censorship': result = self._GetTagCensorship( c, *args, **kwargs )
elif action == 'tag_service_precedence': result = self._tag_service_precedence
elif action == 'tag_parents': result = self._GetTagParents( c, *args, **kwargs )
elif action == 'tag_siblings': result = self._GetTagSiblings( c, *args, **kwargs )
@ -6789,7 +6813,6 @@ class DB( ServiceDB ):
elif action == 'message': result = self._AddMessage( c, *args, **kwargs )
elif action == 'message_info_since': result = self._AddMessageInfoSince( c, *args, **kwargs )
elif action == 'message_statuses': result = self._UpdateMessageStatuses( c, *args, **kwargs )
elif action == 'namespace_blacklists': result = self._SetNamespaceBlacklists( c, *args, **kwargs )
elif action == 'pixiv_account': result = self._SetYAMLDump( c, YAML_DUMP_ID_SINGLE, 'pixiv_account', *args, **kwargs )
elif action == 'reset_service': result = self._ResetService( c, *args, **kwargs )
elif action == 'save_options': result = self._SaveOptions( c, *args, **kwargs )
@ -6797,6 +6820,7 @@ class DB( ServiceDB ):
elif action == 'set_password': result = self._SetPassword( c, *args, **kwargs )
elif action == 'set_tag_service_precedence': result = self._SetTagServicePrecedence( c, *args, **kwargs )
elif action == 'subscription': result = self._SetYAMLDump( c, YAML_DUMP_ID_SUBSCRIPTION, *args, **kwargs )
elif action == 'tag_censorship': result = self._SetTagCensorship( c, *args, **kwargs )
elif action == 'tag_parents': result = self._UpdateTagParents( c, *args, **kwargs )
elif action == 'tag_siblings': result = self._UpdateTagSiblings( c, *args, **kwargs )
elif action == 'thumbnails': result = self._AddThumbnails( c, *args, **kwargs )

View File

@ -824,7 +824,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
show = total_num_pending > 0
return ( menu, '&Pending (' + HC.ConvertIntToPrettyString( total_num_pending ) + ')', show )
return ( menu, p( '&Pending (' + HC.ConvertIntToPrettyString( total_num_pending ) + ')' ), show )
def services():
@ -857,9 +857,9 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'review_services' ), p( '&Review Services' ), p( 'Review your services.' ) )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_services' ), p( '&Add, Remove or Edit Services' ), p( 'Edit your services.' ) )
menu.AppendSeparator()
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_censorship' ), p( '&Manage Tag Censorship' ), p( 'Set which tags you want to see from which services.' ) )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_siblings' ), p( '&Manage Tag Siblings' ), p( 'Set certain tags to be automatically replaced with other tags.' ) )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_parents' ), p( '&Manage Tag Parents' ), p( 'Set certain tags to be automatically added with other tags.' ) )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_namespace_blacklists' ), p( '&Manage Namespace Blacklists' ), p( 'Set which kinds of tags you want to see from which services.' ) )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_service_precedence' ), p( '&Manage Tag Service Precedence' ), p( 'Change the order in which tag repositories\' taxonomies will be added to the database.' ) )
menu.AppendSeparator()
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_boorus' ), p( 'Manage &Boorus' ), p( 'Change the html parsing information for boorus to download from.' ) )
@ -1136,11 +1136,6 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
with ClientGUIDialogsManage.DialogManageImportFolders( self ) as dlg: dlg.ShowModal()
def _ManageNamespaceBlacklists( self ):
with ClientGUIDialogsManage.DialogManageNamespaceBlacklists( self ) as dlg: dlg.ShowModal()
def _ManageOptions( self ):
with ClientGUIDialogsManage.DialogManageOptions( self ) as dlg: dlg.ShowModal()
@ -1182,6 +1177,11 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
finally: HC.options[ 'pause_subs_sync' ] = original_pause_status
def _ManageTagCensorship( self ):
with ClientGUIDialogsManage.DialogManageTagCensorship( self ) as dlg: dlg.ShowModal()
def _ManageTagParents( self ):
with ClientGUIDialogsManage.DialogManageTagParents( self ) as dlg: dlg.ShowModal()
@ -1800,11 +1800,11 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
elif command == 'manage_export_folders': self._ManageExportFolders()
elif command == 'manage_imageboards': self._ManageImageboards()
elif command == 'manage_import_folders': self._ManageImportFolders()
elif command == 'manage_namespace_blacklists': self._ManageNamespaceBlacklists()
elif command == 'manage_pixiv_account': self._ManagePixivAccount()
elif command == 'manage_server_services': self._ManageServer( data )
elif command == 'manage_services': self._ManageServices()
elif command == 'manage_subscriptions': self._ManageSubscriptions()
elif command == 'manage_tag_censorship': self._ManageTagCensorship()
elif command == 'manage_tag_parents': self._ManageTagParents()
elif command == 'manage_tag_service_precedence': self._ManageTagServicePrecedence()
elif command == 'manage_tag_siblings': self._ManageTagSiblings()
@ -2496,7 +2496,7 @@ class FrameReviewServices( ClientGUICommon.Frame ):
self._updates.SetValue( num_updates_downloaded )
self._updates_text.SetLabel( HC.ConvertIntToPrettyString( num_updates_downloaded ) + '/' + HC.ConvertIntToPrettyString( num_updates ) + ' - ' + self._service.GetUpdateStatus() )
self._updates_text.SetLabel( self._service.GetUpdateStatus() )
( max_num_bytes, max_num_requests ) = account_type.GetMaxMonthlyData()
( used_bytes, used_requests ) = account.GetUsedData()
@ -2682,25 +2682,7 @@ class FrameReviewServices( ClientGUICommon.Frame ):
def TIMEREventUpdates( self, event ):
now = HC.GetNow()
( first_timestamp, next_download_timestamp, next_processing_timestamp ) = self._service.GetTimestamps()
if first_timestamp is None:
num_updates = 0
num_updates_downloaded = 0
else:
num_updates = ( now - first_timestamp ) / HC.UPDATE_DURATION
num_updates_downloaded = ( next_download_timestamp - first_timestamp ) / HC.UPDATE_DURATION
self._updates_text.SetLabel( HC.ConvertIntToPrettyString( num_updates_downloaded ) + '/' + HC.ConvertIntToPrettyString( num_updates ) + ' - ' + self._service.GetUpdateStatus() )
def TIMEREventUpdates( self, event ): self._updates_text.SetLabel( self._service.GetUpdateStatus() )
class FrameSplash( ClientGUICommon.Frame ):

View File

@ -756,9 +756,9 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
( operator, tag ) = predicate.GetValue()
namespace_blacklists_manager = HC.app.GetManager( 'namespace_blacklists' )
tag_censorship_manager = HC.app.GetManager( 'tag_censorship' )
result = namespace_blacklists_manager.FilterTags( self._tag_service_identifier, ( tag, ) )
result = tag_censorship_manager.FilterTags( self._tag_service_identifier, ( tag, ) )
if len( result ) > 0:
@ -766,7 +766,7 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
parents = tag_parents_manager.GetParents( self._tag_service_identifier, tag )
parents = namespace_blacklists_manager.FilterTags( self._tag_service_identifier, parents )
parents = tag_censorship_manager.FilterTags( self._tag_service_identifier, parents )
self._chosen_tag_callable( tag, parents )
@ -1162,69 +1162,17 @@ class ExportPatternButton( wx.Button ):
self.Bind( wx.EVT_MENU, self.EventMenu )
def EventButton( self, event ):
menu = wx.Menu()
menu.Append( -1, 'click on a phrase to copy to clipboard' )
menu.AppendSeparator()
menu.Append( self.ID_REGEX_WHITESPACE, r'whitespace character - \s' )
menu.Append( self.ID_REGEX_NUMBER, r'number character - \d' )
menu.Append( self.ID_REGEX_ALPHANUMERIC, r'alphanumeric or backspace character - \w' )
menu.Append( self.ID_REGEX_ANY, r'any character - .' )
menu.Append( self.ID_REGEX_BACKSPACE, r'backspace character - \\' )
menu.Append( self.ID_REGEX_BEGINNING, r'beginning of line - ^' )
menu.Append( self.ID_REGEX_END, r'end of line - $' )
menu.Append( self.ID_REGEX_SET, r'any of these - [...]' )
menu.Append( self.ID_REGEX_NOT_SET, r'anything other than these - [^...]' )
menu.AppendSeparator()
menu.Append( self.ID_REGEX_0_OR_MORE_GREEDY, r'0 or more matches, consuming as many as possible - *' )
menu.Append( self.ID_REGEX_1_OR_MORE_GREEDY, r'1 or more matches, consuming as many as possible - +' )
menu.Append( self.ID_REGEX_0_OR_1_GREEDY, r'0 or 1 matches, preferring 1 - ?' )
menu.Append( self.ID_REGEX_0_OR_MORE_MINIMAL, r'0 or more matches, consuming as few as possible - *?' )
menu.Append( self.ID_REGEX_1_OR_MORE_MINIMAL, r'1 or more matches, consuming as few as possible - +?' )
menu.Append( self.ID_REGEX_0_OR_1_MINIMAL, r'0 or 1 matches, preferring 0 - *' )
menu.Append( self.ID_REGEX_EXACTLY_M, r'exactly m matches - {m}' )
menu.Append( self.ID_REGEX_M_TO_N_GREEDY, r'm to n matches, consuming as many as possible - {m,n}' )
menu.Append( self.ID_REGEX_M_TO_N_MINIMAL, r'm to n matches, consuming as few as possible - {m,n}?' )
menu.AppendSeparator()
menu.Append( self.ID_REGEX_LOOKAHEAD, r'the next characters are: (non-consuming) - (?=...)' )
menu.Append( self.ID_REGEX_NEGATIVE_LOOKAHEAD, r'the next characters are not: (non-consuming) - (?!...)' )
menu.Append( self.ID_REGEX_LOOKBEHIND, r'the previous characters are: (non-consuming) - (?<=...)' )
menu.Append( self.ID_REGEX_NEGATIVE_LOOKBEHIND, r'the previous characters are not: (non-consuming) - (?<!...)' )
menu.AppendSeparator()
menu.Append( self.ID_REGEX_FILENAME, r'filename - (?<=' + os.path.sep.encode( 'string_escape' ) + r')[\w\s]*?(?=\..*$)' )
menu.AppendSeparator()
menu.Append( self.ID_REGEX_NUMBER_WITHOUT_ZEROES, r'0074 -> 74 - [1-9]+\d*' )
menu.Append( self.ID_REGEX_NUMBER_EXT, r'...0074.jpg -> 74 - [1-9]+\d*(?=.{4}$)' )
menu.Append( self.ID_REGEX_AUTHOR, r'E:\my collection\author name - v4c1p0074.jpg -> author name - [^\\][\w\s]*(?=\s-)' )
self.PopupMenu( menu )
wx.CallAfter( menu.Destroy )
def EventMenu( self, event ):
id = event.GetId()
phrase = None
if id == self.ID_HASH: phrase = r'{hash}'
if id == self.ID_TAGS: phrase = r'{tags}'
if id == self.ID_NN_TAGS: phrase = r'{nn tags}'
if id == self.ID_NAMESPACE: phrase = r'[...]'
if id == self.ID_TAG: phrase = r'(...)'
if id == self.ID_HASH: phrase = '{hash}'
if id == self.ID_TAGS: phrase = '{tags}'
if id == self.ID_NN_TAGS: phrase = '{nn tags}'
if id == self.ID_NAMESPACE: phrase = '[...]'
if id == self.ID_TAG: phrase = '(...)'
else: event.Skip()
if phrase is not None:
@ -1249,17 +1197,17 @@ class ExportPatternButton( wx.Button ):
menu.AppendSeparator()
menu.Append( self.ID_HASH, r'the file\'s hash - {hash}' )
menu.Append( self.ID_TAGS, r'all the file\'s tags - {tags}' )
menu.Append( self.ID_NN_TAGS, r'all the file\'s non-namespaced tags - {nn tags}' )
menu.Append( self.ID_HASH, 'the file\'s hash - {hash}' )
menu.Append( self.ID_TAGS, 'all the file\'s tags - {tags}' )
menu.Append( self.ID_NN_TAGS, 'all the file\'s non-namespaced tags - {nn tags}' )
menu.AppendSeparator()
menu.Append( self.ID_NAMESPACE, r'all instances of a particular namespace - [...]' )
menu.Append( self.ID_NAMESPACE, 'all instances of a particular namespace - [...]' )
menu.AppendSeparator()
menu.Append( self.ID_TAG, r'a particular tag, if the file has it - (...)' )
menu.Append( self.ID_TAG, 'a particular tag, if the file has it - (...)' )
self.PopupMenu( menu )
@ -3968,6 +3916,12 @@ class TagsBox( ListBox ):
event.Skip()
def GetSelectedTag( self ):
if self._current_selected_index is not None: return self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ]
else: return None
class TagsBoxActiveOnly( TagsBox ):
has_counts = True
@ -4069,6 +4023,42 @@ class TagsBoxActiveOnly( TagsBox ):
class TagsBoxCensorship( TagsBox ):
def _Activate( self, s, term ): self.RemoveTag( term )
def _GetTagString( self, tag ):
if tag == '': return 'unnamespaced'
elif tag == ':': return 'namespaced'
else: return tag
def AddTag( self, tag ):
tag_string = self._GetTagString( tag )
if tag_string in self._strings_to_terms: self.RemoveTag( tag )
else:
self._ordered_strings.append( tag_string )
self._strings_to_terms[ tag_string ] = tag
self._TextsHaveChanged()
def RemoveTag( self, tag ):
tag_string = self._GetTagString( tag )
self._ordered_strings.remove( tag_string )
del self._strings_to_terms[ tag_string ]
self._TextsHaveChanged()
class TagsBoxColourOptions( TagsBox ):
def __init__( self, parent, initial_namespace_colours ):
@ -4559,6 +4549,8 @@ class TagsBoxManageWithShowDeleted( StaticBox ):
else: self._tags_box.HideDeleted()
def GetSelectedTag( self ): return self._tags_box.GetSelectedTag()
def PetitionTag( self, tag ): self._tags_box.PetitionTag( tag )
def PendTag( self, tag ): self._tags_box.PendTag( tag )
@ -4567,37 +4559,6 @@ class TagsBoxManageWithShowDeleted( StaticBox ):
def RescindPend( self, tag ): self._tags_box.RescindPend( tag )
class TagsBoxNamespaces( TagsBox ):
def _Activate( self, s, term ): self.RemoveNamespace( term )
def AddNamespace( self, namespace ):
if namespace == '': namespace_string = 'unnamespaced'
else: namespace_string = namespace + ':'
if namespace_string in self._strings_to_terms: self.RemoveNamespace( namespace )
else:
self._ordered_strings.append( namespace_string )
self._strings_to_terms[ namespace_string ] = namespace
self._TextsHaveChanged()
def RemoveNamespace( self, namespace ):
if namespace == '': namespace_string = 'unnamespaced'
else: namespace_string = namespace + ':'
self._ordered_strings.remove( namespace_string )
del self._strings_to_terms[ namespace_string ]
self._TextsHaveChanged()
class TagsBoxPredicates( TagsBox ):
has_counts = True

View File

@ -88,10 +88,6 @@ def SelectServiceIdentifier( permission = None, service_types = HC.ALL_SERVICES,
def ShowMessage( parent, message ):
with DialogMessage( parent, message ) as dlg: dlg.ShowModal()
class Dialog( wx.Dialog ):
def __init__( self, parent, title, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, position = 'topleft' ):
@ -182,7 +178,7 @@ class DialogAdvancedContentUpdate( Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
message = 'These advanced operations are powerful, so think before you click. They can lock up your client for a _long_ time. They are not currently undoable. You may need to refresh your existing searches to see their effect.'
message = 'These advanced operations are powerful, so think before you click. They can lock up your client for a _long_ time, and are not undoable. You may need to refresh your existing searches to see their effect.'
st = wx.StaticText( self, label = message )
@ -202,7 +198,7 @@ class DialogAdvancedContentUpdate( Dialog ):
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( wx.StaticText( self, label = 'set tag: ' ), FLAGS_MIXED )
hbox.AddF( wx.StaticText( self, label = 'set specific tag or namespace: ' ), FLAGS_MIXED )
hbox.AddF( self._tag_input, FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( self._specific_tag, FLAGS_EXPAND_BOTH_WAYS )
@ -293,12 +289,12 @@ class DialogChooseNewServiceMethod( Dialog ):
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
register_message = 'I want to set up a new account. I have a registration key (a key starting with \'r\').'
register_message = 'I want to initialise a new account with the server. I have a registration key (a key starting with \'r\').'
self._register = wx.Button( self, label = register_message )
self._register.Bind( wx.EVT_BUTTON, self.EventRegister )
setup_message = 'The account is already set up; I just want to add it to this client. I have a normal access key.'
setup_message = 'The account is already initialised; I just want to add it to this client. I have a normal access key.'
self._setup = wx.Button( self, id = wx.ID_OK, label = setup_message )
@ -521,7 +517,7 @@ class DialogGenerateNewAccounts( Dialog ):
def InitialiseControls():
self._num = wx.SpinCtrl( self, min=1, max=10000 )
self._num = wx.SpinCtrl( self, min = 1, max = 10000, size = ( 80, -1 ) )
self._account_types = wx.Choice( self, size = ( 400, -1 ) )
@ -555,9 +551,12 @@ class DialogGenerateNewAccounts( Dialog ):
def ArrangeControls():
ctrl_box = wx.BoxSizer( wx.HORIZONTAL )
ctrl_box.AddF( self._num, FLAGS_SMALL_INDENT )
ctrl_box.AddF( self._account_types, FLAGS_SMALL_INDENT )
ctrl_box.AddF( self._lifetime, FLAGS_SMALL_INDENT )
ctrl_box.AddF( wx.StaticText( self, label = 'generate' ), FLAGS_MIXED )
ctrl_box.AddF( self._num, FLAGS_MIXED )
ctrl_box.AddF( self._account_types, FLAGS_MIXED )
ctrl_box.AddF( wx.StaticText( self, label = 'accounts, to expire in' ), FLAGS_MIXED )
ctrl_box.AddF( self._lifetime, FLAGS_MIXED )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.AddF( self._ok, FLAGS_MIXED )
@ -2018,6 +2017,8 @@ class DialogInputLocalFiles( Dialog ):
self._processing_queue = []
self._currently_parsing = False
self._current_paths = set()
self._job_key = HC.JobKey()
if len( paths ) > 0: self._AddPathsToList( paths )
@ -2081,7 +2082,12 @@ class DialogInputLocalFiles( Dialog ):
pretty_path = zip_path + os.path.sep + name
self._paths_list.Append( ( pretty_path, HC.mime_string_lookup[ mime ], pretty_size ), ( ( path_type, path_info ), mime, size ) )
if ( path_type, path_info ) not in self._current_paths:
self._current_paths.add( ( path_type, path_info ) )
self._paths_list.Append( ( pretty_path, HC.mime_string_lookup[ mime ], pretty_size ), ( ( path_type, path_info ), mime, size ) )
def DoneParsing( self ):
@ -2177,7 +2183,12 @@ class DialogInputLocalFiles( Dialog ):
def EventRemovePaths( self, event ): self._paths_list.RemoveAllSelected()
def EventRemovePaths( self, event ):
self._paths_list.RemoveAllSelected()
self._current_paths = set( self._GetPathsInfo() )
def EventTags( self, event ):
@ -2727,6 +2738,9 @@ class DialogInputNamespaceRegex( Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Put the namespace (e.g. page) on the left.' + os.linesep + 'Put the regex (e.g. [1-9]+\d*(?=.{4}$)) on the right.' + os.linesep + 'All files will be tagged with "namespace:regex".'
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( control_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( self._shortcuts, FLAGS_LONE_BUTTON )
vbox.AddF( self._regex_link, FLAGS_LONE_BUTTON )
@ -3011,7 +3025,6 @@ class DialogInputShortcut( Dialog ):
self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', 'ratings_filter', 'frame_back', 'frame_next', 'manage_ratings', 'manage_tags', 'new_page', 'refresh', 'set_search_focus', 'show_hide_splitters', 'synchronised_wait_switch', 'previous', 'next', 'first', 'last', 'undo', 'redo' ] )
self._ok = wx.Button( self, id= wx.ID_OK, label = 'Ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
@ -3152,52 +3165,6 @@ class DialogInputUPnPMapping( Dialog ):
return ( external_port, protocol_type, internal_port, description )
class DialogMessage( Dialog ):
def __init__( self, parent, message, ok_label = 'ok' ):
def InitialiseControls():
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
self._ok = wx.Button( self, id = wx.ID_OK, label = ok_label )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
def PopulateControls():
pass
def ArrangeControls():
vbox = wx.BoxSizer( wx.VERTICAL )
text = wx.StaticText( self, label = HC.u( message ) )
text.Wrap( 480 )
vbox.AddF( text, FLAGS_BIG_INDENT )
vbox.AddF( self._ok, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
Dialog.__init__( self, parent, 'message', position = 'center' )
InitialiseControls()
PopulateControls()
ArrangeControls()
wx.CallAfter( self._ok.SetFocus )
class DialogModifyAccounts( Dialog ):
def __init__( self, parent, service_identifier, subject_identifiers ):
@ -3340,7 +3307,7 @@ class DialogModifyAccounts( Dialog ):
Dialog.__init__( self, parent, 'modify account' )
self._service = HC.app.Read( 'service', service_identifier )
self._subject_identifiers = set( subject_identifiers )
self._subject_identifiers = list( subject_identifiers )
InitialiseControls()
@ -3479,7 +3446,7 @@ class DialogNews( Dialog ):
( news, timestamp ) = self._newslist[ self._current_news_position - 1 ]
self._news.SetValue( time.ctime( timestamp ) + ':' + os.linesep + os.linesep + news )
self._news.SetValue( time.ctime( timestamp ) + ' (' + HC.ConvertTimestampToPrettyAgo( timestamp ) + '):' + os.linesep + os.linesep + news )
self._news_position.SetValue( HC.ConvertIntToPrettyString( self._current_news_position ) + ' / ' + HC.ConvertIntToPrettyString( len( self._newslist ) ) )
@ -4575,6 +4542,7 @@ class DialogSelectYoutubeURL( Dialog ):
def InitialiseControls():
self._urls = ClientGUICommon.SaneListCtrl( self, 360, [ ( 'format', 150 ), ( 'resolution', 150 ) ] )
self._urls.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.EventOK )
self._urls.SetMinSize( ( 360, 200 ) )
@ -4975,29 +4943,38 @@ class DialogSetupExport( Dialog ):
self._tags_box = ClientGUICommon.TagsBoxCPPWithSorter( self, self._page_key )
self._tags_box.SetMinSize( ( 220, 300 ) )
self._paths = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'number', 60 ), ( 'mime', 70 ), ( 'path', -1 ) ] )
self._paths = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'number', 60 ), ( 'mime', 70 ), ( 'expected path', -1 ) ] )
self._paths.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventSelectPath )
self._paths.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventSelectPath )
self._paths.SetMinSize( ( 740, 360 ) )
self._directory_picker = wx.DirPickerCtrl( self )
self._export_path_box = ClientGUICommon.StaticBox( self, 'export path' )
self._directory_picker = wx.DirPickerCtrl( self._export_path_box )
self._directory_picker.Bind( wx.EVT_DIRPICKER_CHANGED, self.EventRecalcPaths )
self._open_location = wx.Button( self, label = 'open this location' )
self._open_location = wx.Button( self._export_path_box, label = 'open this location' )
self._open_location.Bind( wx.EVT_BUTTON, self.EventOpenLocation )
self._zip_name = wx.TextCtrl( self )
self._zip_box = ClientGUICommon.StaticBox( self, 'zip' )
self._export_to_zip = wx.CheckBox( self, label = 'export to zip' )
self._export_to_zip = wx.CheckBox( self._zip_box, label = 'export to zip' )
self._export_to_zip.Bind( wx.EVT_CHECKBOX, self.EventExportToZipCheckbox )
self._export_encrypted = wx.CheckBox( self, label = 'encrypt zip' )
self._zip_name = wx.TextCtrl( self._zip_box )
self._zip_name.Disable()
self._pattern = wx.TextCtrl( self )
self._export_encrypted = wx.CheckBox( self._zip_box, label = 'encrypt zip' )
self._export_encrypted.Disable()
self._update = wx.Button( self, label = 'update' )
self._filenames_box = ClientGUICommon.StaticBox( self, 'filenames' )
self._pattern = wx.TextCtrl( self._filenames_box )
self._update = wx.Button( self._filenames_box, label = 'update' )
self._update.Bind( wx.EVT_BUTTON, self.EventRecalcPaths )
self._examples = ClientGUICommon.ExportPatternButton( self )
self._examples = ClientGUICommon.ExportPatternButton( self._filenames_box )
self._export = wx.Button( self, label = 'export' )
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
@ -5036,32 +5013,36 @@ class DialogSetupExport( Dialog ):
top_hbox.AddF( self._tags_box, FLAGS_EXPAND_PERPENDICULAR )
top_hbox.AddF( self._paths, FLAGS_EXPAND_BOTH_WAYS )
destination_hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox = wx.BoxSizer( wx.HORIZONTAL )
destination_hbox.AddF( self._directory_picker, FLAGS_EXPAND_BOTH_WAYS )
destination_hbox.AddF( self._open_location, FLAGS_MIXED )
hbox.AddF( self._directory_picker, FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( self._open_location, FLAGS_MIXED )
zip_hbox = wx.BoxSizer( wx.HORIZONTAL )
self._export_path_box.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
zip_hbox.AddF( self._zip_name, FLAGS_EXPAND_BOTH_WAYS )
zip_hbox.AddF( self._export_to_zip, FLAGS_MIXED )
zip_hbox.AddF( self._export_encrypted, FLAGS_MIXED )
hbox = wx.BoxSizer( wx.HORIZONTAL )
pattern_hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._pattern, FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( self._update, FLAGS_MIXED )
hbox.AddF( self._examples, FLAGS_MIXED )
pattern_hbox.AddF( self._pattern, FLAGS_EXPAND_BOTH_WAYS )
pattern_hbox.AddF( self._update, FLAGS_MIXED )
pattern_hbox.AddF( self._examples, FLAGS_MIXED )
pattern_hbox.AddF( self._export, FLAGS_MIXED )
self._filenames_box.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
button_hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._export_to_zip, FLAGS_MIXED )
hbox.AddF( self._zip_name, FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( self._export_encrypted, FLAGS_MIXED )
self._zip_box.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( top_hbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
vbox.AddF( destination_hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( zip_hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( pattern_hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( self._export_path_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._filenames_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._zip_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._export, FLAGS_LONE_BUTTON )
vbox.AddF( self._cancel, FLAGS_LONE_BUTTON )
self.SetSizer( vbox )
@ -5194,6 +5175,20 @@ class DialogSetupExport( Dialog ):
def EventExportToZipCheckbox( self, event ):
if self._export_to_zip.GetValue() == True:
self._zip_name.Enable()
self._export_encrypted.Enable()
else:
self._zip_name.Disable()
self._export_encrypted.Disable()
def EventOpenLocation( self, event ):
directory = self._directory_picker.GetPath()

View File

@ -1472,6 +1472,9 @@ class DialogManageExportFolders( ClientGUIDialogs.Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Here you can set the client to regularly export a certain query to a particular location.'
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._export_folders, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( file_buttons, FLAGS_BUTTON_SIZERS )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
@ -1603,19 +1606,35 @@ class DialogManageExportFoldersEdit( ClientGUIDialogs.Dialog ):
def InitialiseControls():
self._path = wx.DirPickerCtrl( self, style = wx.DIRP_USE_TEXTCTRL )
self._path_box = ClientGUICommon.StaticBox( self, 'export path' )
self._path = wx.DirPickerCtrl( self._path_box, style = wx.DIRP_USE_TEXTCTRL )
#
self._query_box = ClientGUICommon.StaticBox( self, 'query to export' )
self._page_key = os.urandom( 32 )
self._predicates_box = ClientGUICommon.TagsBoxPredicates( self, self._page_key, predicates )
self._predicates_box = ClientGUICommon.TagsBoxPredicates( self._query_box, self._page_key, predicates )
self._searchbox = ClientGUICommon.AutoCompleteDropdownTagsRead( self, self._page_key, HC.LOCAL_FILE_SERVICE_IDENTIFIER, HC.COMBINED_TAG_SERVICE_IDENTIFIER )
self._searchbox = ClientGUICommon.AutoCompleteDropdownTagsRead( self._query_box, self._page_key, HC.LOCAL_FILE_SERVICE_IDENTIFIER, HC.COMBINED_TAG_SERVICE_IDENTIFIER )
self._period = wx.SpinCtrl( self )
#
self._pattern = wx.TextCtrl( self )
self._period_box = ClientGUICommon.StaticBox( self, 'export period (minutes)' )
self._examples = ClientGUICommon.ExportPatternButton( self )
self._period = wx.SpinCtrl( self._period_box )
#
self._phrase_box = ClientGUICommon.StaticBox( self, 'filenames' )
self._pattern = wx.TextCtrl( self._phrase_box )
self._examples = ClientGUICommon.ExportPatternButton( self._phrase_box )
#
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
@ -1638,28 +1657,19 @@ class DialogManageExportFoldersEdit( ClientGUIDialogs.Dialog ):
def ArrangeControls():
gridbox = wx.FlexGridSizer( 0, 2 )
self._path_box.AddF( self._path, FLAGS_EXPAND_PERPENDICULAR )
gridbox.AddGrowableCol( 1, 1 )
self._query_box.AddF( self._predicates_box, FLAGS_EXPAND_BOTH_WAYS )
self._query_box.AddF( self._searchbox, FLAGS_EXPAND_PERPENDICULAR )
predicates_vbox = wx.BoxSizer( wx.VERTICAL )
predicates_vbox.AddF( self._predicates_box, FLAGS_EXPAND_BOTH_WAYS )
predicates_vbox.AddF( self._searchbox, FLAGS_EXPAND_PERPENDICULAR )
self._period_box.AddF( self._period, FLAGS_EXPAND_PERPENDICULAR )
phrase_hbox = wx.BoxSizer( wx.HORIZONTAL )
phrase_hbox.AddF( self._pattern, FLAGS_EXPAND_BOTH_WAYS )
phrase_hbox.AddF( self._examples, FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'path:' ), FLAGS_MIXED )
gridbox.AddF( self._path, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'query:' ), FLAGS_MIXED )
gridbox.AddF( predicates_vbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'check period (minutes):' ), FLAGS_MIXED )
gridbox.AddF( self._period, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'export phrase:' ), FLAGS_MIXED )
gridbox.AddF( phrase_hbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
self._phrase_box.AddF( phrase_hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
buttons = wx.BoxSizer( wx.HORIZONTAL )
@ -1668,14 +1678,17 @@ class DialogManageExportFoldersEdit( ClientGUIDialogs.Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( gridbox, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._path_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._query_box, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._period_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._phrase_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 640, y ) )
self.SetInitialSize( ( 480, y ) )
ClientGUIDialogs.Dialog.__init__( self, parent, 'edit export folder' )
@ -2508,6 +2521,9 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Here you can set the client to regularly check certain folders for new files to import.'
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._import_folders, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( file_buttons, FLAGS_BUTTON_SIZERS )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
@ -2651,9 +2667,15 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
def InitialiseControls():
self._path = wx.DirPickerCtrl( self, style = wx.DIRP_USE_TEXTCTRL )
self._path_box = ClientGUICommon.StaticBox( self, 'import path' )
self._type = ClientGUICommon.BetterChoice( self )
self._path = wx.DirPickerCtrl( self._path_box, style = wx.DIRP_USE_TEXTCTRL )
#
self._type_box = ClientGUICommon.StaticBox( self, 'type of import folder' )
self._type = ClientGUICommon.BetterChoice( self._type_box )
self._type.Append( 'delete', HC.IMPORT_FOLDER_TYPE_DELETE )
self._type.Append( 'synchronise', HC.IMPORT_FOLDER_TYPE_SYNCHRONISE )
message = '''delete - try to import all files in folder and delete them if they succeed
@ -2661,11 +2683,21 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
synchronise - try to import all new files in folder'''
self._type.SetToolTipString( message )
self._check_period = wx.SpinCtrl( self )
#
self._local_tag = wx.TextCtrl( self )
self._period_box = ClientGUICommon.StaticBox( self, 'check period (minutes)' )
self._check_period = wx.SpinCtrl( self._period_box )
#
self._local_tag_box = ClientGUICommon.StaticBox( self, 'local tag to give to all imports' )
self._local_tag = wx.TextCtrl( self._local_tag_box )
self._local_tag.SetToolTipString( 'add this tag on the local tag service to anything imported from the folder' )
#
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
@ -2688,18 +2720,13 @@ synchronise - try to import all new files in folder'''
def ArrangeControls():
gridbox = wx.FlexGridSizer( 0, 2 )
self._path_box.AddF( self._path, FLAGS_EXPAND_PERPENDICULAR )
gridbox.AddGrowableCol( 1, 1 )
self._type_box.AddF( self._type, FLAGS_EXPAND_PERPENDICULAR )
gridbox.AddF( wx.StaticText( self, label = 'path:' ), FLAGS_MIXED )
gridbox.AddF( self._path, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'type:' ), FLAGS_MIXED )
gridbox.AddF( self._type, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'check period (minutes):' ), FLAGS_MIXED )
gridbox.AddF( self._check_period, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'local tag:' ), FLAGS_MIXED )
gridbox.AddF( self._local_tag, FLAGS_EXPAND_BOTH_WAYS )
self._period_box.AddF( self._check_period, FLAGS_EXPAND_PERPENDICULAR )
self._local_tag_box.AddF( self._local_tag, FLAGS_EXPAND_PERPENDICULAR )
buttons = wx.BoxSizer( wx.HORIZONTAL )
@ -2708,7 +2735,10 @@ synchronise - try to import all new files in folder'''
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( gridbox, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._path_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._type_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._period_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._local_tag_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
@ -2742,180 +2772,6 @@ synchronise - try to import all new files in folder'''
return ( path, type, check_period, local_tag )
class DialogManageNamespaceBlacklists( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
def InitialiseControls():
self._tag_services = ClientGUICommon.ListBook( self )
self._tag_services.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
service_identifiers = HC.app.Read( 'service_identifiers', ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
for service_identifier in service_identifiers:
page = self._Panel( self._tag_services, service_identifier )
name = service_identifier.GetName()
self._tag_services.AddPage( page, name )
default_tag_repository = HC.options[ 'default_tag_repository' ]
self._tag_services.Select( default_tag_repository.GetName() )
def ArrangeControls():
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tag_services, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
self.SetInitialSize( ( 550, 680 ) )
ClientGUIDialogs.Dialog.__init__( self, parent, 'namespace blacklists' )
InitialiseControls()
PopulateControls()
ArrangeControls()
interested_actions = [ 'set_search_focus' ]
entries = []
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() if action in interested_actions ] )
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
def _SetSearchFocus( self ):
page = self._tag_services.GetCurrentPage()
page.SetTagBoxFocus()
def EventOK( self, event ):
try:
info = [ page.GetInfo() for page in self._tag_services.GetNameToPageDict().values() if page.HasInfo() ]
HC.app.Write( 'namespace_blacklists', info )
finally: self.EndModal( wx.ID_OK )
def EventServiceChanged( self, event ):
page = self._tag_services.GetCurrentPage()
wx.CallAfter( page.SetTagBoxFocus )
class _Panel( wx.Panel ):
def __init__( self, parent, service_identifier ):
def InitialiseControls():
choice_pairs = [ ( 'blacklist', True ), ( 'whitelist', False ) ]
self._blacklist = ClientGUICommon.RadioBox( self, 'type', choice_pairs )
self._namespaces = ClientGUICommon.TagsBoxNamespaces( self )
self._namespace_input = wx.TextCtrl( self, style = wx.TE_PROCESS_ENTER )
self._namespace_input.Bind( wx.EVT_KEY_DOWN, self.EventKeyDownNamespace )
def PopulateControls():
( blacklist, namespaces ) = HC.app.Read( 'namespace_blacklists', self._service_identifier )
if blacklist: self._blacklist.SetSelection( 0 )
else: self._blacklist.SetSelection( 1 )
for namespace in namespaces: self._namespaces.AddNamespace( namespace )
def ArrangeControls():
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._blacklist, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._namespaces, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._namespace_input, FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
wx.Panel.__init__( self, parent )
self._service_identifier = service_identifier
InitialiseControls()
PopulateControls()
ArrangeControls()
def EventKeyDownNamespace( self, event ):
if event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
namespace = self._namespace_input.GetValue()
self._namespaces.AddNamespace( namespace )
self._namespace_input.SetValue( '' )
else: event.Skip()
def GetInfo( self ):
blacklist = self._blacklist.GetSelectedClientData()
namespaces = self._namespaces.GetClientData()
return ( self._service_identifier, blacklist, namespaces )
def HasInfo( self ):
( service_identifier, blacklist, namespaces ) = self.GetInfo()
return len( namespaces ) > 0
def SetTagBoxFocus( self ): self._namespace_input.SetFocus()
class DialogManageOptions( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
@ -5646,7 +5502,7 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
now = HC.GetNow()
if info[ 'last_checked' ] < now: last_checked_message = HC.ConvertTimestampToPrettySync( info[ 'last_checked' ] )
if info[ 'last_checked' ] < now: last_checked_message = 'updated to ' + HC.ConvertTimestampToPrettySync( info[ 'last_checked' ] )
else: last_checked_message = 'due to error, update is delayed. next check in ' + HC.ConvertTimestampToPrettyPending( info[ 'last_checked' ] )
@ -5861,6 +5717,187 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
class DialogManageTagCensorship( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
def InitialiseControls():
self._tag_services = ClientGUICommon.ListBook( self )
self._tag_services.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
service_identifiers = HC.app.Read( 'service_identifiers', ( HC.COMBINED_TAG, HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
for service_identifier in service_identifiers:
page = self._Panel( self._tag_services, service_identifier )
name = service_identifier.GetName()
self._tag_services.AddPage( page, name )
default_tag_repository = HC.options[ 'default_tag_repository' ]
self._tag_services.Select( default_tag_repository.GetName() )
def ArrangeControls():
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Here you can set which tags or classes of tags you do not want to see.'
intro += os.linesep
intro += "Input ':' for all namespaced tags, and '' for all unnamespaced tags."
intro += os.linesep
intro += 'You may have to refresh your current queries to see any changes.'
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tag_services, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
self.SetInitialSize( ( 350, 480 ) )
ClientGUIDialogs.Dialog.__init__( self, parent, 'tag censorship' )
InitialiseControls()
PopulateControls()
ArrangeControls()
interested_actions = [ 'set_search_focus' ]
entries = []
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() if action in interested_actions ] )
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
def _SetSearchFocus( self ):
page = self._tag_services.GetCurrentPage()
page.SetTagBoxFocus()
def EventOK( self, event ):
try:
info = [ page.GetInfo() for page in self._tag_services.GetNameToPageDict().values() if page.HasInfo() ]
HC.app.Write( 'tag_censorship', info )
finally: self.EndModal( wx.ID_OK )
def EventServiceChanged( self, event ):
page = self._tag_services.GetCurrentPage()
wx.CallAfter( page.SetTagBoxFocus )
class _Panel( wx.Panel ):
def __init__( self, parent, service_identifier ):
def InitialiseControls():
choice_pairs = [ ( 'blacklist', True ), ( 'whitelist', False ) ]
self._blacklist = ClientGUICommon.RadioBox( self, 'type', choice_pairs )
self._tags = ClientGUICommon.TagsBoxCensorship( self )
self._tag_input = wx.TextCtrl( self, style = wx.TE_PROCESS_ENTER )
self._tag_input.Bind( wx.EVT_KEY_DOWN, self.EventKeyDownTag )
def PopulateControls():
( blacklist, tags ) = HC.app.Read( 'tag_censorship', self._service_identifier )
if blacklist: self._blacklist.SetSelection( 0 )
else: self._blacklist.SetSelection( 1 )
for tag in tags: self._tags.AddTag( tag )
def ArrangeControls():
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._blacklist, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tags, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._tag_input, FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
wx.Panel.__init__( self, parent )
self._service_identifier = service_identifier
InitialiseControls()
PopulateControls()
ArrangeControls()
def EventKeyDownTag( self, event ):
if event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
tag = self._tag_input.GetValue()
self._tags.AddTag( tag )
self._tag_input.SetValue( '' )
else: event.Skip()
def GetInfo( self ):
blacklist = self._blacklist.GetSelectedClientData()
tags = self._tags.GetClientData()
return ( self._service_identifier, blacklist, tags )
def HasInfo( self ):
( service_identifier, blacklist, tags ) = self.GetInfo()
return len( tags ) > 0
def SetTagBoxFocus( self ): self._tag_input.SetFocus()
class DialogManageTagParents( ClientGUIDialogs.Dialog ):
def __init__( self, parent, tag = None ):
@ -6835,9 +6872,10 @@ class DialogManageTagServicePrecedence( ClientGUIDialogs.Dialog ):
def InitialiseControls():
message = 'When services dispute over a file\'s tags,' + os.linesep + 'higher services will overrule those below.'
intro = 'Unless otherwise specified, the lists of tags you see are a merge of all your different repositories\' data. If two services dispute over whether a file should have a tag, the higher here will overrule those below. Changing the precedence may lock up your client for several minutes while the combined tags cache is recalculated.'
self._explain = wx.StaticText( self, label = message )
self._explain = wx.StaticText( self, label = intro )
self._explain.Wrap( 300 )
self._tag_services = wx.ListBox( self )
@ -6894,7 +6932,7 @@ class DialogManageTagServicePrecedence( ClientGUIDialogs.Dialog ):
( x, y ) = self.GetEffectiveMinSize()
if y < 400: y = 400
if y < 300: y = 300
self.SetInitialSize( ( x, y ) )

View File

@ -48,7 +48,7 @@ TEMP_DIR = BASE_DIR + os.path.sep + 'temp'
# Misc
NETWORK_VERSION = 13
SOFTWARE_VERSION = 106
SOFTWARE_VERSION = 107
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -1004,11 +1004,11 @@ def ConvertTimestampToPrettySync( timestamp ):
if years == 1: y = '1 year'
else: y = u( years ) + ' years'
if years > 0: return 'updated to ' + ' '.join( ( y, mo ) ) + ' ago'
elif months > 0: return 'updated to ' + ' '.join( ( mo, d ) ) + ' ago'
elif days > 0: return 'updated to ' + ' '.join( ( d, h ) ) + ' ago'
elif hours > 0: return 'updated to ' + ' '.join( ( h, m ) ) + ' ago'
else: return 'updated to ' + ' '.join( ( m, s ) ) + ' ago'
if years > 0: return ' '.join( ( y, mo ) ) + ' ago'
elif months > 0: return ' '.join( ( mo, d ) ) + ' ago'
elif days > 0: return ' '.join( ( d, h ) ) + ' ago'
elif hours > 0: return ' '.join( ( h, m ) ) + ' ago'
else: return ' '.join( ( m, s ) ) + ' ago'
def ConvertTimestampToPrettyTime( timestamp ): return time.strftime( '%Y/%m/%d %H:%M:%S', time.localtime( timestamp ) )

View File

@ -82,7 +82,7 @@ def ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options ):
def GetYoutubeFormats( youtube_url ):
try: p = pafy.Pafy( youtube_url )
except: raise Exception( 'Could not fetch video info from youtube!' )
except Exception as e: raise Exception( 'Could not fetch video info from youtube!' + os.linesep + HC.u( e ) )
info = { ( s.extension, s.resolution ) : ( s.url, s.title ) for s in p.streams if s.extension in ( 'flv', 'mp4' ) }

View File

@ -329,7 +329,7 @@ class HTTPConnection():
elif self._scheme == 'https': self._connection = httplib.HTTPSConnection( self._host, self._port, timeout = self._timeout )
try: self._connection.connect()
except: raise Exception( 'Could not connect to ' + self._host + '!' )
except: raise Exception( 'Could not connect to ' + HC.u( self._host ) + '!' )
def _WriteResponseToPath( self, response, report_hooks ):

View File

@ -62,6 +62,24 @@ def BuildSimpleChildrenToParents( pairs ):
return simple_children_to_parents
def CensorshipMatch( tag, censorship ):
if ':' in censorship:
if censorship == ':': return ':' in tag # ':' - all namespaced tags
else: return tag.startswith( censorship ) # 'series:' - namespaced tag
else:
if censorship == '': return ':' not in tag # '' - all non namespaced tags
else: # 'table' - normal tag, or namespaced version of same
if ':' in tag: ( namespace, tag ) = tag.split( ':', 1 )
return tag == censorship
def CollapseTagSiblingChains( processed_siblings ):
# now to collapse chains
@ -229,108 +247,13 @@ def MergeTagsManagers( tag_service_precedence, tags_managers ):
return TagsManagerSimple( merged_service_identifiers_to_statuses_to_tags )
class NamespaceBlacklistsManager():
def __init__( self ):
self.RefreshData()
HC.pubsub.sub( self, 'RefreshData', 'notify_new_namespace_blacklists' )
def _GetPredicate( self, service_identifier ):
( blacklist, namespaces ) = self._service_identifiers_to_blacklists[ service_identifier ]
tag_matches = lambda tag: True in ( tag.startswith( namespace ) for namespace in namespaces )
if blacklist: predicate = lambda tag: not tag_matches( tag )
else: predicate = tag_matches
return predicate
def GetInfo( self, service_identifier ):
if service_identifier in self._service_identifiers_to_predicates: return self._service_identifiers_to_predicates[ service_identifier ]
else: return ( True, set() )
def RefreshData( self ):
info = HC.app.Read( 'namespace_blacklists' )
self._service_identifiers_to_predicates = {}
for ( service_identifier, blacklist, namespaces ) in info:
unnamespaced = '' in namespaces
ns = [ namespace for namespace in namespaces if namespace != '' ]
namespaced_match = lambda tag: True in ( tag.startswith( namespace ) for namespace in ns )
if unnamespaced:
unnamespaced_match = lambda tag: ':' not in tag
if len( ns ) > 0: tag_match = lambda tag: unnamespaced_match( tag ) or namespaced_match( tag )
else: tag_match = unnamespaced_match
else:
tag_match = namespaced_match
if blacklist: predicate = lambda tag: not tag_match( tag )
else: predicate = tag_match
self._service_identifiers_to_predicates[ service_identifier ] = predicate
def FilterServiceidentifiersToStatusesToTags( self, service_identifiers_to_statuses_to_tags ):
filtered_service_identifiers_to_statuses_to_tags = collections.defaultdict( HC.default_dict_set )
for ( service_identifier, statuses_to_tags ) in service_identifiers_to_statuses_to_tags.items():
if service_identifier in self._service_identifiers_to_predicates:
predicate = self._service_identifiers_to_predicates[ service_identifier ]
for ( status, tags ) in statuses_to_tags.items():
tags = { tag for tag in tags if predicate( tag ) }
filtered_service_identifiers_to_statuses_to_tags[ service_identifier ][ status ] = tags
else: filtered_service_identifiers_to_statuses_to_tags[ service_identifier ] = statuses_to_tags
return filtered_service_identifiers_to_statuses_to_tags
def FilterTags( self, service_identifier, tags ):
if service_identifier in self._service_identifiers_to_predicates:
predicate = self._service_identifiers_to_predicates[ service_identifier ]
tags = { tag for tag in tags if predicate( tag ) }
return tags
class TagsManagerSimple():
def __init__( self, service_identifiers_to_statuses_to_tags ):
namespace_blacklists_manager = HC.app.GetManager( 'namespace_blacklists' )
tag_censorship_manager = HC.app.GetManager( 'tag_censorship' )
service_identifiers_to_statuses_to_tags = namespace_blacklists_manager.FilterServiceidentifiersToStatusesToTags( service_identifiers_to_statuses_to_tags )
service_identifiers_to_statuses_to_tags = tag_censorship_manager.FilterServiceidentifiersToStatusesToTags( service_identifiers_to_statuses_to_tags )
self._service_identifiers_to_statuses_to_tags = service_identifiers_to_statuses_to_tags
@ -581,6 +504,82 @@ class TagsManager( TagsManagerSimple ):
class TagCensorshipManager():
def __init__( self ):
self.RefreshData()
HC.pubsub.sub( self, 'RefreshData', 'notify_new_tag_censorship' )
def GetInfo( self, service_identifier ):
if service_identifier in self._service_identifiers_to_predicates: return self._service_identifiers_to_predicates[ service_identifier ]
else: return ( True, set() )
def RefreshData( self ):
info = HC.app.Read( 'tag_censorship' )
self._service_identifiers_to_predicates = {}
for ( service_identifier, blacklist, censorships ) in info:
tag_matches = lambda tag: True in ( CensorshipMatch( tag, censorship ) for censorship in censorships )
if blacklist: predicate = lambda tag: not tag_matches( tag )
else: predicate = tag_matches
self._service_identifiers_to_predicates[ service_identifier ] = predicate
def FilterServiceidentifiersToStatusesToTags( self, service_identifiers_to_statuses_to_tags ):
filtered_service_identifiers_to_statuses_to_tags = collections.defaultdict( HC.default_dict_set )
for ( service_identifier, statuses_to_tags ) in service_identifiers_to_statuses_to_tags.items():
for s_i in ( HC.COMBINED_TAG_SERVICE_IDENTIFIER, service_identifier ):
if s_i in self._service_identifiers_to_predicates:
combined_predicate = self._service_identifiers_to_predicates[ s_i ]
tuples = statuses_to_tags.items()
for ( status, tags ) in tuples:
tags = { tag for tag in tags if combined_predicate( tag ) }
statuses_to_tags[ status ] = tags
filtered_service_identifiers_to_statuses_to_tags[ service_identifier ] = statuses_to_tags
return filtered_service_identifiers_to_statuses_to_tags
def FilterTags( self, service_identifier, tags ):
for s_i in ( HC.COMBINED_TAG_SERVICE_IDENTIFIER, service_identifier ):
if s_i in self._service_identifiers_to_predicates:
predicate = self._service_identifiers_to_predicates[ s_i ]
tags = { tag for tag in tags if predicate( tag ) }
return tags
class TagParentsManager():
def __init__( self ):

View File

@ -979,7 +979,7 @@ class ServiceDB( FileDB, MessageDB, TagDB ):
c.execute( 'UPDATE account_map SET used_bytes = ?, used_requests = ?;', ( 0, 0 ) )
self.pub( 'update_all_session_accounts' )
self.pub_after_commit( 'update_all_session_accounts' )
@ -1750,7 +1750,7 @@ class ServiceDB( FileDB, MessageDB, TagDB ):
for ( service_identifier, options ) in modified_services.items(): self.pub( 'restart_service', service_identifier, options )
for ( service_identifier, options ) in modified_services.items(): self.pub_after_commit( 'restart_service', service_identifier, options )
return service_identifiers_to_access_keys
@ -1965,8 +1965,8 @@ class ServiceDB( FileDB, MessageDB, TagDB ):
c.execute( 'UPDATE services SET options = ? WHERE service_id = ?;', ( options, service_id ) )
self.pub( 'restart_service', service_identifier )
self.pub( 'notify_new_options' )
self.pub_after_commit( 'restart_service', service_identifier )
self.pub_after_commit( 'notify_new_options' )
def _UnbanKey( self, c, service_id, account_id ): c.execute( 'DELETE FROM bans WHERE service_id = ? AND account_id = ?;', ( account_id, ) )
@ -2653,7 +2653,7 @@ class DB( ServiceDB ):
def pub( self, topic, *args, **kwargs ): self._pubsubs.append( ( topic, args, kwargs ) )
def pub_after_commit( self, topic, *args, **kwargs ): self._pubsubs.append( ( topic, args, kwargs ) )
def MainLoop( self ):

View File

@ -812,34 +812,34 @@ class TestClientDB( unittest.TestCase ):
self.assertEqual( mr_num_words, None )
def test_namespace_blacklists( self ):
def test_tag_censorship( self ):
result = self._read( 'namespace_blacklists' )
result = self._read( 'tag_censorship' )
self.assertEqual( result, [] )
result = self._read( 'namespace_blacklists', HC.LOCAL_TAG_SERVICE_IDENTIFIER )
result = self._read( 'tag_censorship', HC.LOCAL_TAG_SERVICE_IDENTIFIER )
self.assertEqual( result, ( True, [] ) )
#
namespace_blacklists = []
info = []
namespace_blacklists.append( ( HC.LOCAL_TAG_SERVICE_IDENTIFIER, False, [ '', 'series' ] ) )
namespace_blacklists.append( ( HC.LOCAL_FILE_SERVICE_IDENTIFIER, True, [ '' ] ) ) # bit dodgy, but whatever!
info.append( ( HC.LOCAL_TAG_SERVICE_IDENTIFIER, False, [ ':', 'series:' ] ) )
info.append( ( HC.LOCAL_FILE_SERVICE_IDENTIFIER, True, [ ':' ] ) ) # bit dodgy, but whatever!
self._write( 'namespace_blacklists', namespace_blacklists )
self._write( 'tag_censorship', info )
#
result = self._read( 'namespace_blacklists' )
result = self._read( 'tag_censorship' )
self.assertItemsEqual( result, namespace_blacklists )
self.assertItemsEqual( result, info )
result = self._read( 'namespace_blacklists', HC.LOCAL_TAG_SERVICE_IDENTIFIER )
result = self._read( 'tag_censorship', HC.LOCAL_TAG_SERVICE_IDENTIFIER )
self.assertEqual( result, ( False, [ '', 'series' ] ) )
self.assertEqual( result, ( False, [ ':', 'series:' ] ) )
def test_news( self ):

View File

@ -158,18 +158,6 @@ class TestNonDBDialogs( unittest.TestCase ):
def test_dialog_message( self ):
with ClientGUIDialogs.DialogMessage( None, 'hello' ) as dlg:
HitButton( dlg._ok )
result = dlg.ShowModal()
self.assertEqual( result, wx.ID_OK )
def test_select_from_list_of_strings( self ):
with ClientGUIDialogs.DialogSelectFromListOfStrings( None, 'select from a list of strings', [ 'a', 'b', 'c' ] ) as dlg:

View File

@ -36,7 +36,7 @@ class App( wx.App ):
self._reads[ 'hydrus_sessions' ] = []
self._reads[ 'messaging_sessions' ] = []
self._reads[ 'namespace_blacklists' ] = []
self._reads[ 'tag_censorship' ] = []
self._reads[ 'options' ] = CC.CLIENT_DEFAULT_OPTIONS
self._reads[ 'sessions' ] = []
self._reads[ 'tag_parents' ] = {}
@ -52,7 +52,7 @@ class App( wx.App ):
self._managers = {}
self._managers[ 'hydrus_sessions' ] = HydrusSessions.HydrusSessionManagerClient()
self._managers[ 'namespace_blacklists' ] = HydrusTags.NamespaceBlacklistsManager()
self._managers[ 'tag_censorship' ] = HydrusTags.TagCensorshipManager()
self._managers[ 'tag_siblings' ] = HydrusTags.TagSiblingsManager()
self._managers[ 'tag_parents' ] = HydrusTags.TagParentsManager()
self._managers[ 'undo' ] = CC.UndoManager()