Version 194

This commit is contained in:
Hydrus Network Developer 2016-02-24 15:42:54 -06:00
parent 8b151cb589
commit 07c7b59ccd
28 changed files with 985 additions and 559 deletions

View File

@ -8,7 +8,10 @@ This github repository is currently a weekly sync with my home dev environment,
The program can do quite a lot! Please check out the help inside the release or [here](http://hydrusnetwork.github.io/hydrus/help).
* [Homepage](http://hydrusnetwork.github.io/hydrus/)
* [homepage](http://hydrusnetwork.github.io/hydrus/)
* [8chan board](https://8ch.net/hydrus/index.html)
* [twitter](https://twitter.com/hydrusnetwork)
* [tumblr](http://hydrus.tumblr.com/)
## Attribution

View File

@ -74,14 +74,14 @@
<h3>setting a password</h3>
<p>the client offers a very simple password system, enough to keep out noobs. You can set it at <i>database->set a password</i>. It will thereafter ask for the password every time you start the program, and will not open without it. However none of the database is encrypted, and someone with enough enthusiasm or a tool and access to your computer can still very easily see what files you have. The password is mainly to stop idle snoops checking your images if you are away from your machine.</p>
<h3>the client's server</h3>
<p>The client runs a very simple http server. I want to do much more with it in future.</p>
<p>When you boot the client, it will try to host a service on port 45865, which will respond to /file and /thumbnail requests just like a file repository, but without needing an access key, and only to localhost (127.0.0.1).</p>
<p>The client can run a very simple http server. It does not run by default, but you can start it by giving it a port at <i>file->options->local server</i>. Once you ok that dialog, the client will try to launch it. You may get a firewall warning.</p>
<p>The server responds to /file and /thumbnail requests just like a file repository, but without needing an access key, and only if the request comes from localhost (127.0.0.1).</p>
<p>For instance, the following image (6c0ae65894c7a5ffd686f54cc052326b8ea188a691a1895b2f88b7c60a07f13f.jpg, in the help dir) is served here from disk:</p>
<p><img src="6c0ae65894c7a5ffd686f54cc052326b8ea188a691a1895b2f88b7c60a07f13f.jpg" /></p>
<p>And here it will attempt to load from the client:</p>
<p>And here it will attempt to load from the client at port 45865:</p>
<p><img src="http://127.0.0.1:45865/file?hash=6c0ae65894c7a5ffd686f54cc052326b8ea188a691a1895b2f88b7c60a07f13f" /></p>
<p>For more information, check the image's two urls. It will of course only display in the second case if you import it to the client and have it running when you load this page. You can copy the second image's url and replace the hash with that of any other file in your collection and it should work.</p>
<p>If you run multiple copies of the client, their local ports will clash. You can change the port number in options.</p>
<p>For more information, check the image's two urls. It will of course only display in the second case if you import the file to your client and have the server running on 45865 when you load this page. You can copy the second image's url and replace the hash with that of any other file in your collection and it should work.</p>
<p>I want to do much more with this in future.</p>
</div>
</body>
</html>

View File

@ -8,6 +8,19 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 194</h3></li>
<ul>
<li>ipfs pins and unpins can now be queued up like file repository pending and petitioned, through the regular thumbnail right-click menu, which also reports some/all ipfs pinned selection status</li>
<li>this ipfs action queue is similarly summarised and commited at the normal service 'pending' menu</li>
<li>ipfs's 'pinned', 'to pin', and 'to unpin' statuses are displayed on thumbnails with ipfs-specific icons</li>
<li>you can copy the focussed file's ipfs multihash or all the selected files' ipfs multihashes from the thumbnail menu's share->copy->ipfs multihash</li>
<li>added a .txt tag parser to the 'path tagging' import dialog--it will parse the same sort of txt files the export dialog produces</li>
<li>the client's new 'requests' network code is harmonised, generally improved, and now produces hydrus-compatible exceptions</li>
<li>updated help re the local server and boorus now defaulting to off</li>
<li>db can now remember service-specific filenames (e.g. ipfs multihashes)</li>
<li>cleaned up some overly complicated and confused thumbnail menu code</li>
<li>the pending menu now specifies what it is about to do more plainly</li>
</ul>
<li><h3>version 193</h3></li>
<ul>
<li>the client's local server and local booru can be turned off from their respective management panels, and from now on, the client will initialise with them this way.</li>

View File

@ -10,7 +10,10 @@
<p>The hydrus client has a simple booru to help you share your files with others over the internet.</p>
<p>First of all, this is <b>hosted from your client</b>, which means other people will be connecting to your computer and fetching files you choose to share from your hard drive. If you close your client or shut your computer down, the local booru will no longer work.</p>
<h3>how to do it</h3>
<p>Right click some files you want to share and select <i>share->local booru</i>. This will throw up a small dialog, like so:</p>
<p>First of all, turn the local booru server on by going to <i>services->manage services</i> and giving it a port:</p>
<p><img src="local_booru_services.png" /></p>
<p>It doesn't matter what you pick, but make it something fairly high. When you ok that dialog, the client should start the booru. You may get a firewall warning.</p>
<p>Then right click some files you want to share and select <i>share->local booru</i>. This will throw up a small dialog, like so:</p>
<p><img src="local_booru_dialog.png" /></p>
<p>This lets you enter an optional <i>name</i>, which titles the share and helps you keep track of it, an optional <i>text</i>, which lets you say some words or html to the people you are sharing with, and an <i>expiry</i>, which lets you determine if and when the share will no longer work.</p>
<p>You can also copy either the internal or external link to your clipboard. The internal link (usually starting something like http://127.0.0.1:45866/) works inside your network and is great just for testing, while the external link (starting http://[your external ip address]:[external port]/) will work for anyone around the world, <b>as long as your booru's port is being forwarded correctly</b>.</p>
@ -18,10 +21,8 @@
<p class="warning">Note that anyone with the external link will be able to see your share, so make sure you only share links with people you trust.</p>
<h3>forwarding your port</h3>
<p>Your home router acts as a barrier between the computers inside the network and the internet. Those inside can see out, but outsiders can only see what you tell the router to permit. Since you want to let people connect to your computer, you need to tell the router to forward all requests of a certain kind to your computer, and thus your client.</p>
<p>If you have never done this before, it can be a headache, especially doing it manually. Luckily, a technology called UPnP makes it a ton easier, and this is how your Skype or Bittorrent clients do it automatically. Since I'm insane about privacy, the hydrus client will not open any external ports without your permission. You just need to go to <i>services->manage services</i> and uncheck a box:</p>
<p><img src="local_booru_upnp.png" /></p>
<p>Unless you know what you are doing and have a good reason to make them different, you might as well keep the internal and external ports the same.</p>
<p>Once you have it set up, the client should make sure your router keeps that port open for your client. You should see the new mapping appear in your <i>services->manage local upnp</i> dialog, which lists all your router's current port mappings.</p>
<p>If you have never done this before, it can be a headache, especially doing it manually. Luckily, a technology called UPnP makes it a ton easier, and this is how your Skype or Bittorrent clients do it automatically. Not all routers support it, but most do. You can have hydrus try to open a port this way back on <i>services->manage services</i>. Unless you know what you are doing and have a good reason to make them different, you might as well keep the internal and external ports the same.</p>
<p>Once you have it set up, the client will try to make sure your router keeps that port open for your client. If it all works, you should see the new mapping appear in your <i>services->manage local upnp</i> dialog, which lists all your router's current port mappings.</p>
<p>If you want to test that the port forward is set up correctly, going to http://[external ip]:[external port]/ should give a little html just saying hello. Your ISP might not allow you to talk to yourself, though, so ask a friend to try if you are having trouble.</p>
<p>If you still do not understand what is going on here, <a href="http://www.howtogeek.com/66214/how-to-forward-ports-on-your-router/">this</a> is a good article explaining everything.</p>
<p>If you do not like UPnP or your router does not support it, you can set the port forward up manually, but I encourage you to keep the internal and external port the same, because absent a 'upnp port' option, the 'copy external share link' button will use the internal port.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -362,6 +362,9 @@ class GlobalBMPs( object ):
GlobalBMPs.file_repository = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'file_repository_small.png' ) )
GlobalBMPs.file_repository_pending = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'file_repository_pending_small.png' ) )
GlobalBMPs.file_repository_petitioned = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'file_repository_petitioned_small.png' ) )
GlobalBMPs.ipfs = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'ipfs_small.png' ) )
GlobalBMPs.ipfs_pending = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'ipfs_pending_small.png' ) )
GlobalBMPs.ipfs_petitioned = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'ipfs_petitioned_small.png' ) )
GlobalBMPs.collection = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'collection.png' ) )
GlobalBMPs.inbox = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'inbox.png' ) )

View File

@ -1622,6 +1622,8 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'CREATE INDEX remote_ratings_rating_index ON remote_ratings ( rating );' )
self._c.execute( 'CREATE INDEX remote_ratings_score_index ON remote_ratings ( score );' )
self._c.execute( 'CREATE TABLE service_filenames ( service_id INTEGER REFERENCES services ON DELETE CASCADE, hash_id INTEGER, filename TEXT, PRIMARY KEY( service_id, hash_id ) );' )
self._c.execute( 'CREATE TABLE service_info ( service_id INTEGER REFERENCES services ON DELETE CASCADE, info_type INTEGER, info INTEGER, PRIMARY KEY ( service_id, info_type ) );' )
self._c.execute( 'CREATE TABLE shutdown_timestamps ( shutdown_type INTEGER PRIMARY KEY, timestamp INTEGER );' )
@ -1925,7 +1927,7 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'DELETE FROM tag_sibling_petitions WHERE service_id = ?;', ( service_id, ) )
self._c.execute( 'DELETE FROM tag_parent_petitions WHERE service_id = ?;', ( service_id, ) )
elif service.GetServiceType() == HC.FILE_REPOSITORY:
elif service.GetServiceType() in ( HC.FILE_REPOSITORY, HC.IPFS ):
self._c.execute( 'DELETE FROM file_transfers WHERE service_id = ?;', ( service_id, ) )
self._c.execute( 'DELETE FROM file_petitions WHERE service_id = ?;', ( service_id, ) )
@ -3503,7 +3505,7 @@ class DB( HydrusDB.HydrusDB ):
def _GetNumsPending( self ):
services = self._GetServices( ( HC.TAG_REPOSITORY, HC.FILE_REPOSITORY ) )
services = self._GetServices( ( HC.TAG_REPOSITORY, HC.FILE_REPOSITORY, HC.IPFS ) )
pendings = {}
@ -3514,7 +3516,7 @@ class DB( HydrusDB.HydrusDB ):
service_id = self._GetServiceId( service_key )
if service_type == HC.FILE_REPOSITORY: info_types = { HC.SERVICE_INFO_NUM_PENDING_FILES, HC.SERVICE_INFO_NUM_PETITIONED_FILES }
if service_type in ( HC.FILE_REPOSITORY, HC.IPFS ): info_types = { HC.SERVICE_INFO_NUM_PENDING_FILES, HC.SERVICE_INFO_NUM_PETITIONED_FILES }
elif service_type == HC.TAG_REPOSITORY: info_types = { HC.SERVICE_INFO_NUM_PENDING_MAPPINGS, HC.SERVICE_INFO_NUM_PETITIONED_MAPPINGS, HC.SERVICE_INFO_NUM_PENDING_TAG_SIBLINGS, HC.SERVICE_INFO_NUM_PETITIONED_TAG_SIBLINGS, HC.SERVICE_INFO_NUM_PENDING_TAG_PARENTS, HC.SERVICE_INFO_NUM_PETITIONED_TAG_PARENTS }
pendings[ service_key ] = self._GetServiceInfoSpecific( service_id, service_type, info_types )
@ -3648,6 +3650,32 @@ class DB( HydrusDB.HydrusDB ):
content_data_dict[ HC.CONTENT_TYPE_FILES ][ HC.CONTENT_UPDATE_PETITION ] = petitioned
elif service_type == HC.IPFS:
result = self._c.execute( 'SELECT hash_id FROM file_transfers WHERE service_id = ?;', ( service_id, ) ).fetchone()
if result is not None:
( hash_id, ) = result
( media_result, ) = self._GetMediaResults( CC.LOCAL_FILE_SERVICE_KEY, ( hash_id, ) )
return media_result
result = self._c.execute( 'SELECT hash_id FROM file_petitions WHERE service_id = ?;', ( service_id, ) ).fetchone()
if result is not None:
( hash_id, ) = result
hash = self._GetHash( hash_id )
multihash = self._GetServiceFilename( service_id, hash_id )
return ( hash, multihash )
if len( content_data_dict ) > 0:
@ -3729,6 +3757,32 @@ class DB( HydrusDB.HydrusDB ):
return service
def _GetServiceFilename( self, service_id, hash_id ):
result = self._c.execute( 'SELECT filename FROM service_filenames WHERE service_id = ? AND hash_id = ?;', ( service_id, hash_id ) ).fetchone()
if result is None:
raise HydrusExceptions.DataMissing( 'Service filename not found!' )
( filename, ) = result
return filename
def _GetServiceFilenames( self, service_key, hashes ):
service_id = self._GetServiceId( service_key )
hash_ids = self._GetHashIds( hashes )
result = [ filename for ( filename, ) in self._c.execute( 'SELECT filename FROM service_filenames WHERE service_id = ? AND hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ';', ( service_id, ) ) ]
result.sort()
return result
def _GetServices( self, limited_types = HC.ALL_SERVICES ):
service_ids = [ service_id for ( service_id, ) in self._c.execute( 'SELECT service_id FROM services WHERE service_type IN ' + HydrusData.SplayListForDB( limited_types ) + ';' ) ]
@ -3770,6 +3824,10 @@ class DB( HydrusDB.HydrusDB ):
info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_TOTAL_SIZE, HC.SERVICE_INFO_NUM_DELETED_FILES, HC.SERVICE_INFO_NUM_THUMBNAILS, HC.SERVICE_INFO_NUM_THUMBNAILS_LOCAL }
elif service_type == HC.IPFS:
info_types = { HC.SERVICE_INFO_NUM_FILES }
elif service_type == HC.LOCAL_TAG:
info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_NUM_NAMESPACES, HC.SERVICE_INFO_NUM_TAGS, HC.SERVICE_INFO_NUM_MAPPINGS }
@ -3830,7 +3888,7 @@ class DB( HydrusDB.HydrusDB ):
save_it = True
if service_type in ( HC.LOCAL_FILE, HC.FILE_REPOSITORY ):
if service_type in ( HC.LOCAL_FILE, HC.FILE_REPOSITORY, HC.IPFS ):
if info_type in ( HC.SERVICE_INFO_NUM_PENDING_FILES, HC.SERVICE_INFO_NUM_PETITIONED_FILES ): save_it = False
@ -4522,7 +4580,7 @@ class DB( HydrusDB.HydrusDB ):
( data_type, action, row ) = content_update.ToTuple()
if service_type in ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ):
if service_type in ( HC.FILE_REPOSITORY, HC.LOCAL_FILE, HC.IPFS ):
if data_type == HC.CONTENT_TYPE_FILES:
@ -4539,11 +4597,24 @@ class DB( HydrusDB.HydrusDB ):
elif action == HC.CONTENT_UPDATE_ADD:
( hash, size, mime, timestamp, width, height, duration, num_frames, num_words ) = row
hash_id = self._GetHashId( hash )
self._AddFilesInfo( [ ( hash_id, size, mime, width, height, duration, num_frames, num_words ) ] )
if service_type in ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ):
( hash, size, mime, timestamp, width, height, duration, num_frames, num_words ) = row
hash_id = self._GetHashId( hash )
self._AddFilesInfo( [ ( hash_id, size, mime, width, height, duration, num_frames, num_words ) ] )
elif service_type == HC.IPFS:
( hash, multihash ) = row
hash_id = self._GetHashId( hash )
self._SetServiceFilename( service_id, hash_id, multihash )
timestamp = HydrusData.GetNow()
self._AddFiles( service_id, [ ( hash_id, timestamp ) ] )
@ -5129,6 +5200,7 @@ class DB( HydrusDB.HydrusDB ):
elif action == 'serialisable': result = self._GetJSONDump( *args, **kwargs )
elif action == 'serialisable_named': result = self._GetJSONDumpNamed( *args, **kwargs )
elif action == 'serialisable_names': result = self._GetJSONDumpNames( *args, **kwargs )
elif action == 'service_filenames': result = self._GetServiceFilenames( *args, **kwargs )
elif action == 'local_booru_share_keys': result = self._GetYAMLDumpNames( YAML_DUMP_ID_LOCAL_BOORU )
elif action == 'local_booru_share': result = self._GetYAMLDump( YAML_DUMP_ID_LOCAL_BOORU, *args, **kwargs )
elif action == 'local_booru_shares': result = self._GetYAMLDump( YAML_DUMP_ID_LOCAL_BOORU )
@ -5354,6 +5426,11 @@ class DB( HydrusDB.HydrusDB ):
self._SaveOptions( options )
def _SetServiceFilename( self, service_id, hash_id, filename ):
self._c.execute( 'REPLACE INTO service_filenames ( service_id, hash_id, filename ) VALUES ( ?, ?, ? );', ( service_id, hash_id, filename ) )
def _SetTagCensorship( self, info ):
self._c.execute( 'DELETE FROM tag_censorship;' )
@ -6383,6 +6460,11 @@ class DB( HydrusDB.HydrusDB ):
if version == 193:
self._c.execute( 'CREATE TABLE service_filenames ( service_id INTEGER REFERENCES services ON DELETE CASCADE, hash_id INTEGER, filename TEXT, PRIMARY KEY( service_id, hash_id ) );' )
self._controller.pub( 'splash_set_title_text', 'updating db to v' + str( version + 1 ) )
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )

View File

@ -1837,18 +1837,11 @@ class ServiceIPFS( ServiceRemote ):
url = 'http://' + host + ':' + str( port ) + path
response = requests.get( url )
response = ClientNetworking.RequestsGet( url )
if response.ok:
j = response.json()
return j[ 'Version' ]
else:
raise Exception( response.content )
j = response.json()
return j[ 'Version' ]
def ImportFile( self, multihash ):
@ -1868,9 +1861,7 @@ class ServiceIPFS( ServiceRemote ):
HydrusGlobals.client_controller.CallToThread( ClientDownloading.THREADDownloadURL, job_key, url, url_string )
def PinFile( self, path ):
mime = HydrusFileHandling.GetMime( path )
def PinFile( self, hash, mime ):
mime_string = HC.mime_string_lookup[ mime ]
@ -1880,51 +1871,30 @@ class ServiceIPFS( ServiceRemote ):
url = 'http://' + host + ':' + str( port ) + '/api/v0/add'
files = { 'path' : ( path, open( path, 'rb' ), mime_string ) }
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
response = requests.put( url, files = files )
path = client_files_manager.GetFilePath( hash, mime )
if response.ok:
# responds with some json with name and key (ipfs hash)
# parse that multihash, wrap it into the content update
pass # spin off a content update
else:
raise Exception( response.content )
files = { 'path' : ( hash.encode( 'hex' ), open( path, 'rb' ), mime_string ) }
def SyncPinned( self ):
response = ClientNetworking.RequestsPost( url, files = files )
# query pin ls and update private cache with what we have
# add a button for this on review services, I think
j = response.json()
pass
multihash = j[ 'Hash' ]
return multihash
def UnpinFile( self, multihash ):
# will have to get multihash from db
credentials = self.GetCredentials()
( host, port ) = credentials.GetAddress()
url = 'http://' + host + ':' + str( port ) + '/api/v0/pin/rm/' + multihash
response = requests.get( url )
if response.ok:
pass # spin off a content update
else:
raise Exception( response.content )
ClientNetworking.RequestsGet( url )
class Shortcuts( HydrusSerialisable.SerialisableBaseNamed ):

View File

@ -230,51 +230,48 @@ def THREADDownloadURL( job_key, url, url_string ):
try:
response = requests.get( url, stream = True )
response = ClientNetworking.RequestsGet( url, stream = True )
if response.ok:
if 'content-length' in response.headers:
if 'content-length' in response.headers:
gauge_range = int( response.headers[ 'content-length' ] )
else:
gauge_range = None
gauge_value = 0
with open( temp_path, 'wb' ) as f:
for chunk in response.iter_content( chunk_size = 65536 ):
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
return
f.write( chunk )
gauge_value += len( chunk )
hook( gauge_value, gauge_range )
job_key.DeleteVariable( 'popup_gauge_1' )
job_key.SetVariable( 'popup_text_1', 'importing ' + url_string )
( result, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', temp_path )
gauge_range = int( response.headers[ 'content-length' ] )
else:
job_key.Cancel()
gauge_range = None
raise HydrusExceptions.NetworkException( response.content )
gauge_value = 0
with open( temp_path, 'wb' ) as f:
for chunk in response.iter_content( chunk_size = 65536 ):
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
return
f.write( chunk )
gauge_value += len( chunk )
hook( gauge_value, gauge_range )
job_key.DeleteVariable( 'popup_gauge_1' )
job_key.SetVariable( 'popup_text_1', 'importing ' + url_string )
( result, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', temp_path )
except HydrusExceptions.NetworkException:
job_key.Cancel()
raise
finally:

View File

@ -439,7 +439,7 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
if HydrusData.TimeHasPassed( self._last_checked + self._period ):
folder_path = self._name
folder_path = HydrusData.ToUnicode( self._name )
if os.path.exists( folder_path ) and os.path.isdir( folder_path ):
@ -477,7 +477,7 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
terms = ParseExportPhrase( self._phrase )
previous_filenames = set( os.listdir( HydrusData.ToUnicode( folder_path ) ) )
previous_filenames = set( os.listdir( folder_path ) )
sync_filenames = set()

View File

@ -886,12 +886,28 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
service_type = service.GetServiceType()
name = service.GetName()
if service_type == HC.TAG_REPOSITORY:
pending_phrase = 'mappings to upload'
petitioned_phrase = 'mappings to petition'
elif service_type == HC.FILE_REPOSITORY:
pending_phrase = 'files to upload'
petitioned_phrase = 'files to petition'
elif service_type == HC.IPFS:
pending_phrase = 'files to pin'
petitioned_phrase = 'files to unpin'
if service_type == HC.TAG_REPOSITORY:
num_pending = info[ HC.SERVICE_INFO_NUM_PENDING_MAPPINGS ] + info[ HC.SERVICE_INFO_NUM_PENDING_TAG_SIBLINGS ] + info[ HC.SERVICE_INFO_NUM_PENDING_TAG_PARENTS ]
num_petitioned = info[ HC.SERVICE_INFO_NUM_PETITIONED_MAPPINGS ] + info[ HC.SERVICE_INFO_NUM_PETITIONED_TAG_SIBLINGS ] + info[ HC.SERVICE_INFO_NUM_PETITIONED_TAG_PARENTS ]
elif service_type == HC.FILE_REPOSITORY:
elif service_type in ( HC.FILE_REPOSITORY, HC.IPFS ):
num_pending = info[ HC.SERVICE_INFO_NUM_PENDING_FILES ]
num_petitioned = info[ HC.SERVICE_INFO_NUM_PETITIONED_FILES ]
@ -901,10 +917,24 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
submenu = wx.Menu()
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'upload_pending', service_key ), p( '&Upload' ), p( 'Upload ' + name + '\'s Pending and Petitions.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'delete_pending', service_key ), p( '&Forget' ), p( 'Clear ' + name + '\'s Pending and Petitions.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'upload_pending', service_key ), p( '&Commit' ), p( 'Upload ' + name + '\'s pending content.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'delete_pending', service_key ), p( '&Forget' ), p( 'Clear ' + name + '\'s pending content.' ) )
menu.AppendMenu( CC.ID_NULL, p( name + ' Pending (' + HydrusData.ConvertValueRangeToPrettyString( num_pending, num_petitioned ) + ')' ), submenu )
submessages = []
if num_pending > 0:
submessages.append( HydrusData.ConvertIntToPrettyString( num_pending ) + ' ' + pending_phrase )
if num_petitioned > 0:
submessages.append( HydrusData.ConvertIntToPrettyString( num_petitioned ) + ' ' + petitioned_phrase )
message = name + ': ' + ', '.join( submessages )
menu.AppendMenu( CC.ID_NULL, p( message ), submenu )
total_num_pending += num_pending + num_petitioned
@ -2019,36 +2049,63 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
try:
if isinstance( result, ClientMedia.MediaResult ):
if service_type in HC.REPOSITORIES:
media_result = result
if isinstance( result, ClientMedia.MediaResult ):
media_result = result
client_files_manager = self._controller.GetClientFilesManager()
hash = media_result.GetHash()
mime = media_result.GetMime()
path = client_files_manager.GetFilePath( hash, mime )
with open( path, 'rb' ) as f: file = f.read()
service.Request( HC.POST, 'file', { 'file' : file } )
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tags_manager, locations_manager, local_ratings, remote_ratings ) = media_result.ToTuple()
timestamp = HydrusData.GetNow()
content_update_row = ( hash, size, mime, timestamp, width, height, duration, num_frames, num_words )
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ADD, content_update_row ) ]
else:
content_update_package = result
service.Request( HC.POST, 'content_update_package', { 'update' : content_update_package } )
content_updates = content_update_package.GetContentUpdates( for_client = True )
client_files_manager = self._controller.GetClientFilesManager()
elif service_type == HC.IPFS:
hash = media_result.GetHash()
mime = media_result.GetMime()
path = client_files_manager.GetFilePath( hash, mime )
with open( path, 'rb' ) as f: file = f.read()
service.Request( HC.POST, 'file', { 'file' : file } )
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tags_manager, locations_manager, local_ratings, remote_ratings ) = media_result.ToTuple()
timestamp = HydrusData.GetNow()
content_update_row = ( hash, size, mime, timestamp, width, height, duration, num_frames, num_words )
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ADD, content_update_row ) ]
else:
content_update_package = result
service.Request( HC.POST, 'content_update_package', { 'update' : content_update_package } )
content_updates = content_update_package.GetContentUpdates( for_client = True )
if isinstance( result, ClientMedia.MediaResult ):
media_result = result
hash = media_result.GetHash()
mime = media_result.GetMime()
multihash = service.PinFile( hash, mime )
content_update_row = ( hash, multihash )
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ADD, content_update_row ) ]
else:
( hash, multihash ) = result
service.UnpinFile( multihash )
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, { hash } ) ]
self._controller.WriteSynchronous( 'content_updates', { service_key : content_updates } )

View File

@ -1388,13 +1388,13 @@ class CanvasWithDetails( Canvas ):
# repo strings
file_repo_strings = self._current_media.GetLocationsManager().GetFileRepositoryStrings()
remote_strings = self._current_media.GetLocationsManager().GetRemoteLocationStrings()
for file_repo_string in file_repo_strings:
for remote_string in remote_strings:
( text_width, text_height ) = dc.GetTextExtent( file_repo_string )
( text_width, text_height ) = dc.GetTextExtent( remote_string )
dc.DrawText( file_repo_string, client_width - text_width - 3, current_y )
dc.DrawText( remote_string, client_width - text_width - 3, current_y )
current_y += text_height + 4

View File

@ -2087,56 +2087,45 @@ class DialogInputShortcut( Dialog ):
def __init__( self, parent, modifier = wx.ACCEL_NORMAL, key = wx.WXK_F7, action = 'new_page' ):
self._action = action
def InitialiseControls():
self._shortcut = ClientGUICommon.Shortcut( self, modifier, key )
self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', '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', 'open_externally' ] )
self._ok = wx.Button( self, id= wx.ID_OK, label = 'Ok' )
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():
self._actions.SetSelection( self._actions.FindString( action ) )
def ArrangeControls():
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._shortcut, CC.FLAGS_MIXED )
hbox.AddF( self._actions, CC.FLAGS_EXPAND_PERPENDICULAR )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.AddF( self._ok, CC.FLAGS_MIXED )
b_box.AddF( self._cancel, CC.FLAGS_MIXED )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
Dialog.__init__( self, parent, 'configure shortcut' )
InitialiseControls()
self._action = action
PopulateControls()
self._shortcut = ClientGUICommon.Shortcut( self, modifier, key )
ArrangeControls()
self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', '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', 'open_externally' ] )
self._ok = wx.Button( self, id= wx.ID_OK, label = 'Ok' )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._actions.SetSelection( self._actions.FindString( action ) )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._shortcut, CC.FLAGS_MIXED )
hbox.AddF( self._actions, CC.FLAGS_EXPAND_PERPENDICULAR )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.AddF( self._ok, CC.FLAGS_MIXED )
b_box.AddF( self._cancel, CC.FLAGS_MIXED )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
@ -2915,80 +2904,69 @@ class DialogPathsToTags( Dialog ):
def __init__( self, parent, paths ):
def InitialiseControls():
self._tag_repositories = ClientGUICommon.ListBook( self )
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
self._add_button = wx.Button( self, id = wx.ID_OK, label = 'Import Files' )
self._add_button.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Back to File Selection' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.TAG_REPOSITORY, ) )
for service in services:
account = service.GetInfo( 'account' )
if account.HasPermission( HC.POST_DATA ) or account.IsUnknownAccount():
service_key = service.GetServiceKey()
name = service.GetName()
self._tag_repositories.AddPageArgs( name, self._Panel, ( self._tag_repositories, service_key, paths ), {} )
page = self._Panel( self._tag_repositories, CC.LOCAL_TAG_SERVICE_KEY, paths )
name = CC.LOCAL_TAG_SERVICE_KEY
self._tag_repositories.AddPage( name, page )
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
default_tag_repository = HydrusGlobals.client_controller.GetServicesManager().GetService( default_tag_repository_key )
self._tag_repositories.Select( default_tag_repository.GetName() )
def ArrangeControls():
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._add_button, CC.FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tag_repositories, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( width, height ) = self.GetMinSize()
width = max( width, 930 )
height = max( height, 680 )
self.SetInitialSize( ( width, height ) )
Dialog.__init__( self, parent, 'path tagging' )
self._paths = paths
InitialiseControls()
self._tag_repositories = ClientGUICommon.ListBook( self )
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
PopulateControls()
self._add_button = wx.Button( self, id = wx.ID_OK, label = 'Import Files' )
self._add_button.SetForegroundColour( ( 0, 128, 0 ) )
ArrangeControls()
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Back to File Selection' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.TAG_REPOSITORY, ) )
for service in services:
account = service.GetInfo( 'account' )
if account.HasPermission( HC.POST_DATA ) or account.IsUnknownAccount():
service_key = service.GetServiceKey()
name = service.GetName()
self._tag_repositories.AddPageArgs( name, self._Panel, ( self._tag_repositories, service_key, paths ), {} )
page = self._Panel( self._tag_repositories, CC.LOCAL_TAG_SERVICE_KEY, paths )
name = CC.LOCAL_TAG_SERVICE_KEY
self._tag_repositories.AddPage( name, page )
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
default_tag_repository = HydrusGlobals.client_controller.GetServicesManager().GetService( default_tag_repository_key )
self._tag_repositories.Select( default_tag_repository.GetName() )
#
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._add_button, CC.FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tag_repositories, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( width, height ) = self.GetMinSize()
width = max( width, 930 )
height = max( height, 680 )
self.SetInitialSize( ( width, height ) )
interested_actions = [ 'set_search_focus' ]
@ -3048,177 +3026,173 @@ class DialogPathsToTags( Dialog ):
def __init__( self, parent, service_key, paths ):
def InitialiseControls():
self._paths_list = ClientGUICommon.SaneListCtrl( self, 250, [ ( '#', 50 ), ( 'path', 400 ), ( 'tags', -1 ) ] )
self._paths_list.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
self._paths_list.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
#
self._quick_namespaces_panel = ClientGUICommon.StaticBox( self, 'quick namespaces' )
self._quick_namespaces_list = ClientGUICommon.SaneListCtrl( self._quick_namespaces_panel, 200, [ ( 'namespace', 80 ), ( 'regex', -1 ) ], delete_key_callback = self.DeleteQuickNamespaces )
self._add_quick_namespace_button = wx.Button( self._quick_namespaces_panel, label = 'add' )
self._add_quick_namespace_button.Bind( wx.EVT_BUTTON, self.EventAddQuickNamespace )
self._add_quick_namespace_button.SetMinSize( ( 20, -1 ) )
self._edit_quick_namespace_button = wx.Button( self._quick_namespaces_panel, label = 'edit' )
self._edit_quick_namespace_button.Bind( wx.EVT_BUTTON, self.EventEditQuickNamespace )
self._edit_quick_namespace_button.SetMinSize( ( 20, -1 ) )
self._delete_quick_namespace_button = wx.Button( self._quick_namespaces_panel, label = 'delete' )
self._delete_quick_namespace_button.Bind( wx.EVT_BUTTON, self.EventDeleteQuickNamespace )
self._delete_quick_namespace_button.SetMinSize( ( 20, -1 ) )
#
self._regexes_panel = ClientGUICommon.StaticBox( self, 'regexes' )
self._regexes = wx.ListBox( self._regexes_panel )
self._regexes.Bind( wx.EVT_LISTBOX_DCLICK, self.EventRemoveRegex )
self._regex_box = wx.TextCtrl( self._regexes_panel, style=wx.TE_PROCESS_ENTER )
self._regex_box.Bind( wx.EVT_TEXT_ENTER, self.EventAddRegex )
self._regex_shortcuts = ClientGUICommon.RegexButton( self._regexes_panel )
self._regex_link = wx.HyperlinkCtrl( self._regexes_panel, id = -1, label = 'a good regex introduction', url = 'http://www.aivosto.com/vbtips/regex.html' )
#
self._num_panel = ClientGUICommon.StaticBox( self, '#' )
self._num_base = wx.SpinCtrl( self._num_panel, min = -10000000, max = 10000000, size = ( 60, -1 ) )
self._num_base.SetValue( 1 )
self._num_base.Bind( wx.EVT_SPINCTRL, self.EventRecalcNum )
self._num_step = wx.SpinCtrl( self._num_panel, min = -1000000, max = 1000000, size = ( 60, -1 ) )
self._num_step.SetValue( 1 )
self._num_step.Bind( wx.EVT_SPINCTRL, self.EventRecalcNum )
self._num_namespace = wx.TextCtrl( self._num_panel, size = ( 100, -1 ) )
self._num_namespace.Bind( wx.EVT_TEXT, self.EventNumNamespaceChanged )
#
self._tags_panel = ClientGUICommon.StaticBox( self, 'tags for all' )
self._tags = ClientGUICommon.ListBoxTagsStrings( self._tags_panel, self.TagsRemoved )
expand_parents = True
self._tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._tags_panel, self.EnterTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
#
self._single_tags_panel = ClientGUICommon.StaticBox( self, 'tags just for selected files' )
self._paths_to_single_tags = collections.defaultdict( set )
self._single_tags = ClientGUICommon.ListBoxTagsStrings( self._single_tags_panel, self.SingleTagsRemoved )
expand_parents = True
self._single_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._single_tags_panel, self.EnterTagsSingle, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
def PopulateControls():
num_base = self._num_base.GetValue()
num_step = self._num_step.GetValue()
for ( num, path ) in enumerate( self._paths ):
processed_num = num_base + num * num_step
pretty_num = HydrusData.ConvertIntToPrettyString( processed_num )
tags = self._GetTags( num, path )
tags_string = ', '.join( tags )
self._paths_list.Append( ( pretty_num, path, tags_string ), ( ( num, processed_num ), path, tags ) )
self._single_tag_box.Disable()
def ArrangeControls():
button_box = wx.BoxSizer( wx.HORIZONTAL )
button_box.AddF( self._add_quick_namespace_button, CC.FLAGS_EXPAND_BOTH_WAYS )
button_box.AddF( self._edit_quick_namespace_button, CC.FLAGS_EXPAND_BOTH_WAYS )
button_box.AddF( self._delete_quick_namespace_button, CC.FLAGS_EXPAND_BOTH_WAYS )
self._quick_namespaces_panel.AddF( self._quick_namespaces_list, CC.FLAGS_EXPAND_BOTH_WAYS )
self._quick_namespaces_panel.AddF( button_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
self._regexes_panel.AddF( self._regexes, CC.FLAGS_EXPAND_BOTH_WAYS )
self._regexes_panel.AddF( self._regex_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._regexes_panel.AddF( self._regex_shortcuts, CC.FLAGS_LONE_BUTTON )
self._regexes_panel.AddF( self._regex_link, CC.FLAGS_LONE_BUTTON )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( wx.StaticText( self._num_panel, label = '# base/step: ' ), CC.FLAGS_MIXED )
hbox.AddF( self._num_base, CC.FLAGS_MIXED )
hbox.AddF( self._num_step, CC.FLAGS_MIXED )
self._num_panel.AddF( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( wx.StaticText( self._num_panel, label = '# namespace: ' ), CC.FLAGS_MIXED )
hbox.AddF( self._num_namespace, CC.FLAGS_EXPAND_BOTH_WAYS )
self._num_panel.AddF( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
second_vbox = wx.BoxSizer( wx.VERTICAL )
second_vbox.AddF( self._regexes_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
second_vbox.AddF( self._num_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
#
self._tags_panel.AddF( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self._tags_panel.AddF( self._tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._single_tags_panel.AddF( self._single_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self._single_tags_panel.AddF( self._single_tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._quick_namespaces_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( second_vbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
hbox.AddF( self._tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( self._single_tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._paths_list, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.SetSizer( vbox )
wx.Panel.__init__( self, parent )
self._service_key = service_key
self._paths = paths
InitialiseControls()
self._load_from_txt_files = False
PopulateControls()
self._paths_list = ClientGUICommon.SaneListCtrl( self, 250, [ ( '#', 50 ), ( 'path', 400 ), ( 'tags', -1 ) ] )
ArrangeControls()
self._paths_list.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
self._paths_list.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
#
self._quick_namespaces_panel = ClientGUICommon.StaticBox( self, 'quick namespaces' )
self._quick_namespaces_list = ClientGUICommon.SaneListCtrl( self._quick_namespaces_panel, 200, [ ( 'namespace', 80 ), ( 'regex', -1 ) ], delete_key_callback = self.DeleteQuickNamespaces )
self._add_quick_namespace_button = wx.Button( self._quick_namespaces_panel, label = 'add' )
self._add_quick_namespace_button.Bind( wx.EVT_BUTTON, self.EventAddQuickNamespace )
self._add_quick_namespace_button.SetMinSize( ( 20, -1 ) )
self._edit_quick_namespace_button = wx.Button( self._quick_namespaces_panel, label = 'edit' )
self._edit_quick_namespace_button.Bind( wx.EVT_BUTTON, self.EventEditQuickNamespace )
self._edit_quick_namespace_button.SetMinSize( ( 20, -1 ) )
self._delete_quick_namespace_button = wx.Button( self._quick_namespaces_panel, label = 'delete' )
self._delete_quick_namespace_button.Bind( wx.EVT_BUTTON, self.EventDeleteQuickNamespace )
self._delete_quick_namespace_button.SetMinSize( ( 20, -1 ) )
#
self._regexes_panel = ClientGUICommon.StaticBox( self, 'regexes' )
self._regexes = wx.ListBox( self._regexes_panel )
self._regexes.Bind( wx.EVT_LISTBOX_DCLICK, self.EventRemoveRegex )
self._regex_box = wx.TextCtrl( self._regexes_panel, style=wx.TE_PROCESS_ENTER )
self._regex_box.Bind( wx.EVT_TEXT_ENTER, self.EventAddRegex )
self._regex_shortcuts = ClientGUICommon.RegexButton( self._regexes_panel )
self._regex_link = wx.HyperlinkCtrl( self._regexes_panel, id = -1, label = 'a good regex introduction', url = 'http://www.aivosto.com/vbtips/regex.html' )
#
self._num_panel = ClientGUICommon.StaticBox( self, '#' )
self._num_base = wx.SpinCtrl( self._num_panel, min = -10000000, max = 10000000, size = ( 60, -1 ) )
self._num_base.SetValue( 1 )
self._num_base.Bind( wx.EVT_SPINCTRL, self.EventRecalcNum )
self._num_step = wx.SpinCtrl( self._num_panel, min = -1000000, max = 1000000, size = ( 60, -1 ) )
self._num_step.SetValue( 1 )
self._num_step.Bind( wx.EVT_SPINCTRL, self.EventRecalcNum )
self._num_namespace = wx.TextCtrl( self._num_panel, size = ( 100, -1 ) )
self._num_namespace.Bind( wx.EVT_TEXT, self.EventNumNamespaceChanged )
#
self._tags_panel = ClientGUICommon.StaticBox( self, 'tags for all' )
self._tags = ClientGUICommon.ListBoxTagsStrings( self._tags_panel, self.TagsRemoved )
expand_parents = True
self._tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._tags_panel, self.EnterTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
#
self._single_tags_panel = ClientGUICommon.StaticBox( self, 'tags just for selected files' )
self._paths_to_single_tags = collections.defaultdict( set )
self._single_tags = ClientGUICommon.ListBoxTagsStrings( self._single_tags_panel, self.SingleTagsRemoved )
expand_parents = True
self._single_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._single_tags_panel, self.EnterTagsSingle, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._load_from_txt_files_checkbox = wx.CheckBox( self, label = 'try to load tags from neighbouring .txt files' )
self._load_from_txt_files_checkbox.SetToolTipString( 'This looks for a [path].txt file, and will try to load line-separated tags from it. Look at thumbnail->share->export->files\'s txt file checkbox for an example.' )
self._load_from_txt_files_checkbox.Bind( wx.EVT_CHECKBOX, self.EventLoadFromTextFiles )
#
num_base = self._num_base.GetValue()
num_step = self._num_step.GetValue()
for ( num, path ) in enumerate( self._paths ):
processed_num = num_base + num * num_step
pretty_num = HydrusData.ConvertIntToPrettyString( processed_num )
tags = self._GetTags( num, path )
tags_string = ', '.join( tags )
self._paths_list.Append( ( pretty_num, path, tags_string ), ( ( num, processed_num ), path, tags ) )
self._single_tag_box.Disable()
#
button_box = wx.BoxSizer( wx.HORIZONTAL )
button_box.AddF( self._add_quick_namespace_button, CC.FLAGS_EXPAND_BOTH_WAYS )
button_box.AddF( self._edit_quick_namespace_button, CC.FLAGS_EXPAND_BOTH_WAYS )
button_box.AddF( self._delete_quick_namespace_button, CC.FLAGS_EXPAND_BOTH_WAYS )
self._quick_namespaces_panel.AddF( self._quick_namespaces_list, CC.FLAGS_EXPAND_BOTH_WAYS )
self._quick_namespaces_panel.AddF( button_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
self._regexes_panel.AddF( self._regexes, CC.FLAGS_EXPAND_BOTH_WAYS )
self._regexes_panel.AddF( self._regex_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._regexes_panel.AddF( self._regex_shortcuts, CC.FLAGS_LONE_BUTTON )
self._regexes_panel.AddF( self._regex_link, CC.FLAGS_LONE_BUTTON )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( wx.StaticText( self._num_panel, label = '# base/step: ' ), CC.FLAGS_MIXED )
hbox.AddF( self._num_base, CC.FLAGS_MIXED )
hbox.AddF( self._num_step, CC.FLAGS_MIXED )
self._num_panel.AddF( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( wx.StaticText( self._num_panel, label = '# namespace: ' ), CC.FLAGS_MIXED )
hbox.AddF( self._num_namespace, CC.FLAGS_EXPAND_BOTH_WAYS )
self._num_panel.AddF( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
second_vbox = wx.BoxSizer( wx.VERTICAL )
second_vbox.AddF( self._regexes_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
second_vbox.AddF( self._num_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
#
self._tags_panel.AddF( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self._tags_panel.AddF( self._tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._single_tags_panel.AddF( self._single_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self._single_tags_panel.AddF( self._single_tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._quick_namespaces_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( second_vbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
hbox.AddF( self._tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( self._single_tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._paths_list, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._load_from_txt_files_checkbox, CC.FLAGS_LONE_BUTTON )
vbox.AddF( hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.SetSizer( vbox )
@ -3226,6 +3200,30 @@ class DialogPathsToTags( Dialog ):
tags = []
if self._load_from_txt_files:
txt_path = path + '.txt'
if os.path.exists( txt_path ):
with open( txt_path, 'rb' ) as f:
txt_tags_string = f.read()
try:
txt_tags = [ HydrusData.ToUnicode( tag ) for tag in txt_tags_string.split( os.linesep ) ]
tags.extend( txt_tags )
except:
HydrusData.Print( 'Could not parse the tags from ' + txt_path + '!' )
tags.extend( self._tags.GetTags() )
for regex in self._regexes.GetStrings():
@ -3443,6 +3441,13 @@ class DialogPathsToTags( Dialog ):
self._single_tags.SetTags( single_tags )
def EventLoadFromTextFiles( self, event ):
self._load_from_txt_files = self._load_from_txt_files_checkbox.IsChecked()
self._RefreshFileList()
def EventNumNamespaceChanged( self, event ): self._RefreshFileList()
def EventRecalcNum( self, event ):

View File

@ -591,17 +591,17 @@ class FullscreenHoverFrameRatings( FullscreenHoverFrame ):
self._icon_panel.Hide()
file_repo_strings = self._current_media.GetLocationsManager().GetFileRepositoryStrings()
remote_strings = self._current_media.GetLocationsManager().GetRemoteLocationStrings()
if len( file_repo_strings ) == 0:
if len( remote_strings ) == 0:
self._file_repos.Hide()
else:
file_repo_string = os.linesep.join( file_repo_strings )
remote_string = os.linesep.join( remote_strings )
self._file_repos.SetLabel( file_repo_string )
self._file_repos.SetLabel( remote_string )
self._file_repos.Show()

View File

@ -31,18 +31,18 @@ import HydrusGlobals
ID_TIMER_ANIMATION = wx.NewId()
def AddFileServiceKeysToMenu( menu, file_service_keys, phrase, action ):
def AddServiceKeysToMenu( menu, service_keys, phrase, action ):
services_manager = HydrusGlobals.client_controller.GetServicesManager()
if len( file_service_keys ) == 1:
if len( service_keys ) == 1:
( file_service_key, ) = file_service_keys
( service_key, ) = service_keys
if action == CC.ID_NULL: id = CC.ID_NULL
else: id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( action, file_service_key )
else: id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( action, service_key )
file_service = services_manager.GetService( file_service_key )
file_service = services_manager.GetService( service_key )
menu.Append( id, phrase + ' ' + file_service.GetName() )
@ -50,12 +50,12 @@ def AddFileServiceKeysToMenu( menu, file_service_keys, phrase, action ):
submenu = wx.Menu()
for file_service_key in file_service_keys:
for service_key in service_keys:
if action == CC.ID_NULL: id = CC.ID_NULL
else: id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( action, file_service_key )
else: id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( action, service_key )
file_service = services_manager.GetService( file_service_key )
file_service = services_manager.GetService( service_key )
submenu.Append( id, file_service.GetName() )
@ -199,6 +199,42 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
HydrusGlobals.client_controller.pub( 'clipboard', 'text', path )
def _CopyServiceFilenameToClipboard( self, service_key ):
display_media = self._focussed_media.GetDisplayMedia()
hash = display_media.GetHash()
( filename, ) = HydrusGlobals.client_controller.Read( 'service_filenames', service_key, { hash } )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', filename )
def _CopyServiceFilenamesToClipboard( self, service_key ):
hashes = self._GetSelectedHashes( has_location = service_key )
if len( hashes ) > 0:
filenames = HydrusGlobals.client_controller.Read( 'service_filenames', service_key, hashes )
if len( filenames ) > 0:
copy_string = os.linesep.join( filenames )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', copy_string )
else:
HydrusData.ShowText( 'Could not find any service filenames for that selection!' )
else:
HydrusData.ShowText( 'Could not find any files with the requested service!' )
def _CustomFilter( self, shortcuts_name = None ):
shortcuts = None
@ -619,30 +655,49 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
def _PetitionFiles( self, file_service_key ):
def _PetitionFiles( self, remote_service_key ):
hashes = self._GetSelectedHashes()
if hashes is not None and len( hashes ) > 0:
file_service = HydrusGlobals.client_controller.GetServicesManager().GetService( file_service_key )
remote_service = HydrusGlobals.client_controller.GetServicesManager().GetService( remote_service_key )
if len( hashes ) == 1: message = 'Enter a reason for this file to be removed from ' + file_service.GetName() + '.'
else: message = 'Enter a reason for these ' + HydrusData.ConvertIntToPrettyString( len( hashes ) ) + ' files to be removed from ' + file_service.GetName() + '.'
service_type = remote_service.GetServiceType()
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if service_type == HC.FILE_REPOSITORY:
if dlg.ShowModal() == wx.ID_OK:
if len( hashes ) == 1:
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_PETITION, ( hashes, dlg.GetValue() ) )
message = 'Enter a reason for this file to be removed from ' + remote_service.GetName() + '.'
service_keys_to_content_updates = { file_service_key : ( content_update, ) }
else:
HydrusGlobals.client_controller.Write( 'content_updates', service_keys_to_content_updates )
message = 'Enter a reason for these ' + HydrusData.ConvertIntToPrettyString( len( hashes ) ) + ' files to be removed from ' + remote_service.GetName() + '.'
self.SetFocus()
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_PETITION, ( hashes, dlg.GetValue() ) )
service_keys_to_content_updates = { remote_service_key : ( content_update, ) }
HydrusGlobals.client_controller.Write( 'content_updates', service_keys_to_content_updates )
self.SetFocus()
elif service_type == HC.IPFS:
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_PETITION, ( hashes, 'ipfs' ) )
service_keys_to_content_updates = { remote_service_key : ( content_update, ) }
HydrusGlobals.client_controller.Write( 'content_updates', service_keys_to_content_updates )
@ -1717,6 +1772,9 @@ class MediaPanelThumbnails( MediaPanel ):
elif command == 'copy_hash': self._CopyHashToClipboard( data )
elif command == 'copy_hashes': self._CopyHashesToClipboard( data )
elif command == 'copy_local_url': self._CopyLocalUrlToClipboard()
elif command == 'copy_hashes': self._CopyHashesToClipboard( data )
elif command == 'copy_service_filename': self._CopyServiceFilenameToClipboard( data )
elif command == 'copy_service_filenames': self._CopyServiceFilenamesToClipboard( data )
elif command == 'copy_path': self._CopyPathToClipboard()
elif command == 'ctrl-space':
@ -1959,12 +2017,18 @@ class MediaPanelThumbnails( MediaPanel ):
multiple_selected = num_selected > 1
services = HydrusGlobals.client_controller.GetServicesManager().GetServices()
services_manager = HydrusGlobals.client_controller.GetServicesManager()
services = services_manager.GetServices()
service_keys_to_names = { service.GetServiceKey() : service.GetName() for service in services }
tag_repositories = [ service for service in services if service.GetServiceType() == HC.TAG_REPOSITORY ]
file_repositories = [ service for service in services if service.GetServiceType() == HC.FILE_REPOSITORY ]
ipfs_services = [ service for service in services if service.GetServiceType() == HC.IPFS ]
local_ratings_services = [ service for service in services if service.GetServiceType() in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) ]
local_booru_service = [ service for service in services if service.GetServiceType() == HC.LOCAL_BOORU ][0]
@ -1975,12 +2039,16 @@ class MediaPanelThumbnails( MediaPanel ):
focussed_is_local = CC.LOCAL_FILE_SERVICE_KEY in self._focussed_media.GetLocationsManager().GetCurrent()
file_service_keys = { repository.GetServiceKey() for repository in file_repositories }
downloadable_file_service_keys = { repository.GetServiceKey() for repository in file_repositories if repository.GetInfo( 'account' ).HasPermission( HC.GET_DATA ) or repository.GetInfo( 'account' ).IsUnknownAccount() }
uploadable_file_service_keys = { repository.GetServiceKey() for repository in file_repositories if repository.GetInfo( 'account' ).HasPermission( HC.POST_DATA ) or repository.GetInfo( 'account' ).IsUnknownAccount() }
petition_resolvable_file_service_keys = { repository.GetServiceKey() for repository in file_repositories if repository.GetInfo( 'account' ).HasPermission( HC.RESOLVE_PETITIONS ) }
petitionable_file_service_keys = { repository.GetServiceKey() for repository in file_repositories if repository.GetInfo( 'account' ).HasPermission( HC.POST_PETITIONS ) } - petition_resolvable_file_service_keys
user_manageable_file_service_keys = { repository.GetServiceKey() for repository in file_repositories if repository.GetInfo( 'account' ).HasPermission( HC.MANAGE_USERS ) }
admin_file_service_keys = { repository.GetServiceKey() for repository in file_repositories if repository.GetInfo( 'account' ).HasPermission( HC.GENERAL_ADMIN ) }
ipfs_service_keys = { service.GetServiceKey() for service in ipfs_services }
focussed_is_ipfs = True in ( service_key in ipfs_service_keys for service_key in self._focussed_media.GetLocationsManager().GetCurrentRemote() )
if multiple_selected:
@ -1998,6 +2066,13 @@ class MediaPanelThumbnails( MediaPanel ):
remote_delete_phrase = 'delete all possible selected from'
modify_account_phrase = 'modify the accounts that uploaded selected to'
pinned_phrase = 'selected pinned to'
pin_phrase = 'pin all to'
rescind_pin_phrase = 'rescind pin to'
unpin_phrase = 'unpin all from'
rescind_unpin_phrase = 'rescind unpin from'
manage_tags_phrase = 'selected files\' tags'
manage_ratings_phrase = 'selected files\' ratings'
@ -2027,6 +2102,13 @@ class MediaPanelThumbnails( MediaPanel ):
remote_delete_phrase = 'delete from'
modify_account_phrase = 'modify the account that uploaded this to'
pinned_phrase = 'pinned to'
pin_phrase = 'pin to'
rescind_pin_phrase = 'rescind pin to'
unpin_phrase = 'unpin from'
rescind_unpin_phrase = 'rescind unpin from'
manage_tags_phrase = 'file\'s tags'
manage_ratings_phrase = 'file\'s ratings'
@ -2045,76 +2127,111 @@ class MediaPanelThumbnails( MediaPanel ):
def MassUnion( lists ): return { item for item in itertools.chain.from_iterable( lists ) }
all_current_file_service_keys = [ locations_manager.GetCurrentRemote() for locations_manager in selected_locations_managers ]
groups_of_current_remote_service_keys = [ locations_manager.GetCurrentRemote() for locations_manager in selected_locations_managers ]
groups_of_pending_remote_service_keys = [ locations_manager.GetPendingRemote() for locations_manager in selected_locations_managers ]
groups_of_petitioned_remote_service_keys = [ locations_manager.GetPetitionedRemote() for locations_manager in selected_locations_managers ]
groups_of_deleted_remote_service_keys = [ locations_manager.GetDeletedRemote() for locations_manager in selected_locations_managers ]
current_file_service_keys = HydrusData.IntelligentMassIntersect( all_current_file_service_keys )
current_remote_service_keys = MassUnion( groups_of_current_remote_service_keys )
pending_remote_service_keys = MassUnion( groups_of_pending_remote_service_keys )
petitioned_remote_service_keys = MassUnion( groups_of_petitioned_remote_service_keys )
deleted_remote_service_keys = MassUnion( groups_of_deleted_remote_service_keys )
some_current_file_service_keys = MassUnion( all_current_file_service_keys ) - current_file_service_keys
common_current_remote_service_keys = HydrusData.IntelligentMassIntersect( groups_of_current_remote_service_keys )
common_pending_remote_service_keys = HydrusData.IntelligentMassIntersect( groups_of_pending_remote_service_keys )
common_petitioned_remote_service_keys = HydrusData.IntelligentMassIntersect( groups_of_petitioned_remote_service_keys )
common_deleted_remote_service_keys = HydrusData.IntelligentMassIntersect( groups_of_deleted_remote_service_keys )
all_pending_file_service_keys = [ locations_manager.GetPendingRemote() for locations_manager in selected_locations_managers ]
disparate_current_remote_service_keys = current_remote_service_keys - common_current_remote_service_keys
disparate_pending_remote_service_keys = pending_remote_service_keys - common_pending_remote_service_keys
disparate_petitioned_remote_service_keys = petitioned_remote_service_keys - common_petitioned_remote_service_keys
disparate_deleted_remote_service_keys = deleted_remote_service_keys - common_deleted_remote_service_keys
some_downloading = True in ( CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetPending() for locations_manager in selected_locations_managers )
pending_file_service_keys = HydrusData.IntelligentMassIntersect( all_pending_file_service_keys )
pending_file_service_keys = pending_remote_service_keys.intersection( file_service_keys )
petitioned_file_service_keys = petitioned_remote_service_keys.intersection( file_service_keys )
some_pending_file_service_keys = MassUnion( all_pending_file_service_keys ) - pending_file_service_keys
common_current_file_service_keys = common_current_remote_service_keys.intersection( file_service_keys )
common_pending_file_service_keys = common_pending_remote_service_keys.intersection( file_service_keys )
common_petitioned_file_service_keys = common_petitioned_remote_service_keys.intersection( file_service_keys )
common_deleted_file_service_keys = common_deleted_remote_service_keys.intersection( file_service_keys )
selection_uploaded_file_service_keys = some_pending_file_service_keys.union( pending_file_service_keys )
disparate_current_file_service_keys = disparate_current_remote_service_keys.intersection( file_service_keys )
disparate_pending_file_service_keys = disparate_pending_remote_service_keys.intersection( file_service_keys )
disparate_petitioned_file_service_keys = disparate_petitioned_remote_service_keys.intersection( file_service_keys )
disparate_deleted_file_service_keys = disparate_deleted_remote_service_keys.intersection( file_service_keys )
all_petitioned_file_service_keys = [ locations_manager.GetPetitionedRemote() for locations_manager in selected_locations_managers ]
pending_ipfs_service_keys = pending_remote_service_keys.intersection( ipfs_service_keys )
petitioned_ipfs_service_keys = petitioned_remote_service_keys.intersection( ipfs_service_keys )
petitioned_file_service_keys = HydrusData.IntelligentMassIntersect( all_petitioned_file_service_keys )
common_current_ipfs_service_keys = common_current_remote_service_keys.intersection( ipfs_service_keys )
common_pending_ipfs_service_keys = common_pending_file_service_keys.intersection( ipfs_service_keys )
common_petitioned_ipfs_service_keys = common_petitioned_remote_service_keys.intersection( ipfs_service_keys )
some_petitioned_file_service_keys = MassUnion( all_petitioned_file_service_keys ) - petitioned_file_service_keys
selection_petitioned_file_service_keys = some_petitioned_file_service_keys.union( petitioned_file_service_keys )
all_deleted_file_service_keys = [ locations_manager.GetDeletedRemote() for locations_manager in selected_locations_managers ]
deleted_file_service_keys = HydrusData.IntelligentMassIntersect( all_deleted_file_service_keys )
some_deleted_file_service_keys = MassUnion( all_deleted_file_service_keys ) - deleted_file_service_keys
disparate_current_ipfs_service_keys = disparate_current_remote_service_keys.intersection( ipfs_service_keys )
disparate_pending_ipfs_service_keys = disparate_pending_remote_service_keys.intersection( ipfs_service_keys )
disparate_petitioned_ipfs_service_keys = disparate_petitioned_remote_service_keys.intersection( ipfs_service_keys )
# valid commands for the files
selection_uploadable_file_service_keys = set()
uploadable_file_service_keys = set()
selection_downloadable_file_service_keys = set()
downloadable_file_service_keys = set()
selection_petitionable_file_service_keys = set()
petitionable_file_service_keys = set()
deletable_file_service_keys = set()
modifyable_file_service_keys = set()
pinnable_ipfs_service_keys = set()
unpinnable_ipfs_service_keys = set()
for locations_manager in selected_locations_managers:
# FILE REPOS
# we can upload (set pending) to a repo_id when we have permission, a file is local, not current, not pending, and either ( not deleted or admin )
if locations_manager.HasLocal(): selection_uploadable_file_service_keys.update( uploadable_file_service_keys - locations_manager.GetCurrentRemote() - locations_manager.GetPendingRemote() - ( locations_manager.GetDeletedRemote() - admin_file_service_keys ) )
if locations_manager.HasLocal():
uploadable_file_service_keys.update( uploadable_file_service_keys - locations_manager.GetCurrentRemote() - locations_manager.GetPendingRemote() - ( locations_manager.GetDeletedRemote() - admin_file_service_keys ) )
# we can download (set pending to local) when we have permission, a file is not local and not already downloading and current
if not CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent() and not locations_manager.HasDownloading(): selection_downloadable_file_service_keys.update( downloadable_file_service_keys & locations_manager.GetCurrentRemote() )
if not CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent() and not locations_manager.HasDownloading():
downloadable_file_service_keys.update( downloadable_file_service_keys & locations_manager.GetCurrentRemote() )
# we can petition when we have permission and a file is current
# we can re-petition an already petitioned file
selection_petitionable_file_service_keys.update( petitionable_file_service_keys & locations_manager.GetCurrentRemote() )
selection_deletable_file_service_keys = set()
for locations_manager in selected_locations_managers:
petitionable_file_service_keys.update( petitionable_file_service_keys & locations_manager.GetCurrentRemote() )
# we can delete remote when we have permission and a file is current and it is not already petitioned
selection_deletable_file_service_keys.update( ( petition_resolvable_file_service_keys & locations_manager.GetCurrentRemote() ) - locations_manager.GetPetitionedRemote() )
selection_modifyable_file_service_keys = set()
for locations_manager in selected_locations_managers:
deletable_file_service_keys.update( ( petition_resolvable_file_service_keys & locations_manager.GetCurrentRemote() ) - locations_manager.GetPetitionedRemote() )
# we can modify users when we have permission and the file is current or deleted
selection_modifyable_file_service_keys.update( user_manageable_file_service_keys & ( locations_manager.GetCurrentRemote() | locations_manager.GetDeletedRemote() ) )
modifyable_file_service_keys.update( user_manageable_file_service_keys & ( locations_manager.GetCurrentRemote() | locations_manager.GetDeletedRemote() ) )
# IPFS
# we can pin if a file is local, not current, not pending
if locations_manager.HasLocal():
pinnable_ipfs_service_keys.update( ipfs_service_keys - locations_manager.GetCurrentRemote() - locations_manager.GetPendingRemote() )
# we can unpin a file if it is current and not petitioned
unpinnable_ipfs_service_keys.update( ( ipfs_service_keys & locations_manager.GetCurrentRemote() ) - locations_manager.GetPetitionedRemote() )
# do the actual menu
@ -2126,57 +2243,81 @@ class MediaPanelThumbnails( MediaPanel ):
menu.Append( CC.ID_NULL, thumbnail.GetPrettyAge() )
if len( some_current_file_service_keys ) > 0: AddFileServiceKeysToMenu( menu, some_current_file_service_keys, 'some uploaded to', CC.ID_NULL )
if len( disparate_current_file_service_keys ) > 0: AddServiceKeysToMenu( menu, disparate_current_file_service_keys, 'some uploaded to', CC.ID_NULL )
if len( current_file_service_keys ) > 0: AddFileServiceKeysToMenu( menu, current_file_service_keys, uploaded_phrase, CC.ID_NULL )
if len( common_current_file_service_keys ) > 0: AddServiceKeysToMenu( menu, common_current_file_service_keys, uploaded_phrase, CC.ID_NULL )
if len( some_pending_file_service_keys ) > 0: AddFileServiceKeysToMenu( menu, some_pending_file_service_keys, 'some pending to', CC.ID_NULL )
if len( disparate_pending_file_service_keys ) > 0: AddServiceKeysToMenu( menu, disparate_pending_file_service_keys, 'some pending to', CC.ID_NULL )
if len( pending_file_service_keys ) > 0: AddFileServiceKeysToMenu( menu, pending_file_service_keys, pending_phrase, CC.ID_NULL )
if len( common_pending_file_service_keys ) > 0: AddServiceKeysToMenu( menu, common_pending_file_service_keys, pending_phrase, CC.ID_NULL )
if len( some_petitioned_file_service_keys ) > 0: AddFileServiceKeysToMenu( menu, some_petitioned_file_service_keys, 'some petitioned from', CC.ID_NULL )
if len( disparate_petitioned_file_service_keys ) > 0: AddServiceKeysToMenu( menu, disparate_petitioned_file_service_keys, 'some petitioned from', CC.ID_NULL )
if len( petitioned_file_service_keys ) > 0: AddFileServiceKeysToMenu( menu, petitioned_file_service_keys, petitioned_phrase, CC.ID_NULL )
if len( common_petitioned_file_service_keys ) > 0: AddServiceKeysToMenu( menu, common_petitioned_file_service_keys, petitioned_phrase, CC.ID_NULL )
if len( some_deleted_file_service_keys ) > 0: AddFileServiceKeysToMenu( menu, some_deleted_file_service_keys, 'some deleted from', CC.ID_NULL )
if len( disparate_deleted_file_service_keys ) > 0: AddServiceKeysToMenu( menu, disparate_deleted_file_service_keys, 'some deleted from', CC.ID_NULL )
if len( deleted_file_service_keys ) > 0: AddFileServiceKeysToMenu( menu, deleted_file_service_keys, deleted_phrase, CC.ID_NULL )
if len( common_deleted_file_service_keys ) > 0: AddServiceKeysToMenu( menu, common_deleted_file_service_keys, deleted_phrase, CC.ID_NULL )
if len( disparate_current_ipfs_service_keys ) > 0: AddServiceKeysToMenu( menu, disparate_current_ipfs_service_keys, 'some pinned to', CC.ID_NULL )
if len( common_current_ipfs_service_keys ) > 0: AddServiceKeysToMenu( menu, common_current_ipfs_service_keys, pinned_phrase, CC.ID_NULL )
if len( disparate_pending_ipfs_service_keys ) > 0: AddServiceKeysToMenu( menu, disparate_pending_ipfs_service_keys, 'some to be pinned to', CC.ID_NULL )
if len( common_pending_ipfs_service_keys ) > 0: AddServiceKeysToMenu( menu, common_pending_ipfs_service_keys, pending_phrase, CC.ID_NULL )
if len( disparate_petitioned_ipfs_service_keys ) > 0: AddServiceKeysToMenu( menu, disparate_petitioned_ipfs_service_keys, 'some to be unpinned from', CC.ID_NULL )
if len( common_petitioned_ipfs_service_keys ) > 0: AddServiceKeysToMenu( menu, common_petitioned_ipfs_service_keys, unpin_phrase, CC.ID_NULL )
menu.AppendSeparator()
#
len_interesting_file_service_keys = 0
len_interesting_remote_service_keys = 0
len_interesting_file_service_keys += len( selection_downloadable_file_service_keys )
len_interesting_file_service_keys += len( selection_uploadable_file_service_keys )
len_interesting_file_service_keys += len( selection_uploaded_file_service_keys )
len_interesting_file_service_keys += len( selection_petitionable_file_service_keys )
len_interesting_file_service_keys += len( selection_petitioned_file_service_keys )
len_interesting_file_service_keys += len( selection_deletable_file_service_keys )
len_interesting_file_service_keys += len( selection_modifyable_file_service_keys )
len_interesting_remote_service_keys += len( downloadable_file_service_keys )
len_interesting_remote_service_keys += len( uploadable_file_service_keys )
len_interesting_remote_service_keys += len( pending_file_service_keys )
len_interesting_remote_service_keys += len( petitionable_file_service_keys )
len_interesting_remote_service_keys += len( petitioned_file_service_keys )
len_interesting_remote_service_keys += len( deletable_file_service_keys )
len_interesting_remote_service_keys += len( modifyable_file_service_keys )
len_interesting_remote_service_keys += len( pinnable_ipfs_service_keys )
len_interesting_remote_service_keys += len( pending_ipfs_service_keys )
len_interesting_remote_service_keys += len( unpinnable_ipfs_service_keys )
len_interesting_remote_service_keys += len( petitioned_ipfs_service_keys )
if len_interesting_file_service_keys > 0:
if len_interesting_remote_service_keys > 0:
file_repo_menu = wx.Menu()
remote_action_menu = wx.Menu()
if len( selection_downloadable_file_service_keys ) > 0: file_repo_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'download' ), download_phrase )
if len( downloadable_file_service_keys ) > 0: remote_action_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'download' ), download_phrase )
if some_downloading: file_repo_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'rescind_download' ), rescind_download_phrase )
if some_downloading: remote_action_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'rescind_download' ), rescind_download_phrase )
if len( selection_uploadable_file_service_keys ) > 0: AddFileServiceKeysToMenu( file_repo_menu, selection_uploadable_file_service_keys, upload_phrase, 'upload' )
if len( uploadable_file_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, uploadable_file_service_keys, upload_phrase, 'upload' )
if len( selection_uploaded_file_service_keys ) > 0: AddFileServiceKeysToMenu( file_repo_menu, selection_uploaded_file_service_keys, rescind_upload_phrase, 'rescind_upload' )
if len( pending_file_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, pending_file_service_keys, rescind_upload_phrase, 'rescind_upload' )
if len( selection_petitionable_file_service_keys ) > 0: AddFileServiceKeysToMenu( file_repo_menu, selection_petitionable_file_service_keys, petition_phrase, 'petition' )
if len( petitionable_file_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, petitionable_file_service_keys, petition_phrase, 'petition' )
if len( selection_petitioned_file_service_keys ) > 0: AddFileServiceKeysToMenu( file_repo_menu, selection_petitioned_file_service_keys, rescind_petition_phrase, 'rescind_petition' )
if len( petitioned_file_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, petitioned_file_service_keys, rescind_petition_phrase, 'rescind_petition' )
if len( selection_deletable_file_service_keys ) > 0: AddFileServiceKeysToMenu( file_repo_menu, selection_deletable_file_service_keys, remote_delete_phrase, 'delete' )
if len( deletable_file_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, deletable_file_service_keys, remote_delete_phrase, 'delete' )
if len( selection_modifyable_file_service_keys ) > 0: AddFileServiceKeysToMenu( file_repo_menu, selection_modifyable_file_service_keys, modify_account_phrase, 'modify_account' )
if len( modifyable_file_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, modifyable_file_service_keys, modify_account_phrase, 'modify_account' )
menu.AppendMenu( CC.ID_NULL, 'file repositories', file_repo_menu )
if len( pinnable_ipfs_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, pinnable_ipfs_service_keys, pin_phrase, 'upload' )
if len( pending_ipfs_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, pending_ipfs_service_keys, rescind_pin_phrase, 'rescind_upload' )
if len( unpinnable_ipfs_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, unpinnable_ipfs_service_keys, unpin_phrase, 'petition' )
if len( petitioned_ipfs_service_keys ) > 0: AddServiceKeysToMenu( remote_action_menu, petitioned_ipfs_service_keys, rescind_unpin_phrase, 'rescind_petition' )
menu.AppendMenu( CC.ID_NULL, 'remote services', remote_action_menu )
#
@ -2301,6 +2442,23 @@ class MediaPanelThumbnails( MediaPanel ):
if multiple_selected: copy_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_hashes', 'sha256' ) , 'sha256 hashes' )
for ipfs_service_key in self._focussed_media.GetLocationsManager().GetCurrentRemote().intersection( ipfs_service_keys ):
name = service_keys_to_names[ ipfs_service_key ]
copy_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_service_filename', ipfs_service_key ) , name + ' multihash' )
if multiple_selected:
for ipfs_service_key in disparate_current_ipfs_service_keys.union( common_current_ipfs_service_keys ):
name = service_keys_to_names[ ipfs_service_key ]
copy_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_service_filenames', ipfs_service_key ) , name + ' multihashes' )
if focussed_is_local:
if self._focussed_media.GetMime() in HC.IMAGES and self._focussed_media.GetDuration() is None:
@ -2879,33 +3037,70 @@ class Thumbnail( Selectable ):
# repo icons
services_manager = HydrusGlobals.client_controller.GetServicesManager()
repo_icon_x = 0
num_current = len( locations_manager.GetCurrentRemote() )
num_pending = len( locations_manager.GetPendingRemote() )
num_petitioned = len( locations_manager.GetPetitionedRemote() )
current = locations_manager.GetCurrentRemote()
pending = locations_manager.GetPendingRemote()
petitioned = locations_manager.GetPetitionedRemote()
if num_current > num_petitioned:
current_to_display = current.difference( petitioned )
#
service_types = [ services_manager.GetService( service_key ).GetServiceType() for service_key in current_to_display ]
if HC.FILE_REPOSITORY in service_types:
dc.DrawBitmap( CC.GlobalBMPs.file_repository, repo_icon_x, 0 )
repo_icon_x += 20
if num_pending > 0:
if HC.IPFS in service_types:
dc.DrawBitmap( CC.GlobalBMPs.ipfs, repo_icon_x, 0 )
repo_icon_x += 20
#
service_types = [ services_manager.GetService( service_key ).GetServiceType() for service_key in pending ]
if HC.FILE_REPOSITORY in service_types:
dc.DrawBitmap( CC.GlobalBMPs.file_repository_pending, repo_icon_x, 0 )
repo_icon_x += 20
if num_petitioned > 0:
if HC.IPFS in service_types:
dc.DrawBitmap( CC.GlobalBMPs.ipfs_pending, repo_icon_x, 0 )
repo_icon_x += 20
#
service_types = [ services_manager.GetService( service_key ).GetServiceType() for service_key in petitioned ]
if HC.FILE_REPOSITORY in service_types:
dc.DrawBitmap( CC.GlobalBMPs.file_repository_petitioned, repo_icon_x, 0 )
repo_icon_x += 20
if HC.IPFS in service_types:
dc.DrawBitmap( CC.GlobalBMPs.ipfs_petitioned, repo_icon_x, 0 )
repo_icon_x += 20
return bmp

View File

@ -92,46 +92,6 @@ class LocationsManager( object ):
return self._deleted - self.LOCAL_LOCATIONS
def GetFileRepositoryStrings( self ):
current = self.GetCurrentRemote()
pending = self.GetPendingRemote()
petitioned = self.GetPetitionedRemote()
file_repo_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.FILE_REPOSITORY, ) )
file_repo_services = list( file_repo_services )
cmp_func = lambda a, b: cmp( a.GetName(), b.GetName() )
file_repo_services.sort( cmp = cmp_func )
file_repo_service_keys_and_names = [ ( file_repo_service.GetServiceKey(), file_repo_service.GetName() ) for file_repo_service in file_repo_services ]
file_repo_strings = []
for ( service_key, name ) in file_repo_service_keys_and_names:
if service_key in pending:
file_repo_strings.append( name + ' (+)' )
elif service_key in current:
if service_key in petitioned:
file_repo_strings.append( name + ' (-)' )
else:
file_repo_strings.append( name )
return file_repo_strings
def GetPending( self ): return self._pending
def GetPendingRemote( self ):
@ -144,6 +104,50 @@ class LocationsManager( object ):
return self._petitioned - self.LOCAL_LOCATIONS
def GetRemoteLocationStrings( self ):
current = self.GetCurrentRemote()
pending = self.GetPendingRemote()
petitioned = self.GetPetitionedRemote()
remote_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.FILE_REPOSITORY, HC.IPFS ) )
remote_services = list( remote_services )
def key( s ):
return s.GetName()
remote_services.sort( key = key )
remote_service_strings = []
for remote_service in remote_services:
name = remote_service.GetName()
service_key = remote_service.GetServiceKey()
if service_key in pending:
remote_service_strings.append( name + ' (+)' )
elif service_key in current:
if service_key in petitioned:
remote_service_strings.append( name + ' (-)' )
else:
remote_service_strings.append( name )
return remote_service_strings
def HasDownloading( self ): return CC.LOCAL_FILE_SERVICE_KEY in self._pending
def HasLocal( self ): return len( self._current.intersection( self.LOCAL_LOCATIONS ) ) > 0
@ -1241,7 +1245,7 @@ class MediaResult( object ):
service_type = service.GetServiceType()
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ): tags_manager.ProcessContentUpdate( service_key, content_update )
elif service_type in ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ):
elif service_type in ( HC.FILE_REPOSITORY, HC.LOCAL_FILE, HC.IPFS ):
if service_type == HC.LOCAL_FILE:

View File

@ -5,6 +5,7 @@ import HydrusSerialisable
import errno
import httplib
import os
import requests
import socket
import socks
import threading
@ -109,6 +110,73 @@ def ConvertHydrusGETArgsToQuery( request_args ):
return query
def RequestsGet( url, stream = False ):
response = requests.get( url, stream = stream )
RequestsCheckResponse( response )
return response
def RequestsPost( url, data = None, files = None ):
response = requests.post( url, data = data, files = files )
RequestsCheckResponse( response )
return response
def RequestsCheckResponse( response ):
if not response.ok:
error_text = response.content
if len( error_text ) > 1024:
large_chunk = error_text[:4096]
smaller_chunk = large_chunk[:256]
HydrusData.DebugPrint( large_chunk )
error_text = 'The server\'s error text was too long to display. The first part follows, while a larger chunk has been written to the log.'
error_text += os.linesep
error_text += smaller_chunk
if response.status_code == 304:
eclass = HydrusExceptions.NotModifiedException
elif response.status_code == 401:
eclass = HydrusExceptions.PermissionException
elif response.status_code == 403:
eclass = HydrusExceptions.ForbiddenException
elif response.status_code == 404:
eclass = HydrusExceptions.NotFoundException
elif response.status_code == 419:
eclass = HydrusExceptions.SessionException
elif response.status_code == 426:
eclass = HydrusExceptions.NetworkVersionException
else:
eclass = HydrusExceptions.NetworkException
raise eclass( error_text )
def ParseURL( url ):
try:

View File

@ -53,7 +53,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 193
SOFTWARE_VERSION = 194
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -275,6 +275,8 @@ UNDETERMINED_WM = 19
VIDEO_MKV = 20
VIDEO_WEBM = 21
APPLICATION_JSON = 22
VIDEO_APNG = 23
UNDETERMINED_PNG = 24
APPLICATION_OCTET_STREAM = 100
APPLICATION_UNKNOWN = 101
@ -325,6 +327,7 @@ mime_enum_lookup[ 'audio/ogg' ] = AUDIO_OGG
mime_enum_lookup[ 'audio/flac' ] = AUDIO_FLAC
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/mp4' ] = VIDEO_MP4
mime_enum_lookup[ 'video/x-ms-wmv' ] = VIDEO_WMV
@ -356,6 +359,7 @@ mime_string_lookup[ AUDIO_FLAC ] = 'audio/flac'
mime_string_lookup[ AUDIO_WMA ] = 'audio/x-ms-wma'
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_MP4 ] = 'video/mp4'
mime_string_lookup[ VIDEO_WMV ] = 'video/x-ms-wmv'
@ -385,6 +389,7 @@ mime_ext_lookup[ AUDIO_OGG ] = '.ogg'
mime_ext_lookup[ AUDIO_FLAC ] = '.flac'
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_MP4 ] = '.mp4'
mime_ext_lookup[ VIDEO_WMV ] = '.wmv'

View File

@ -27,7 +27,7 @@ header_and_mime = [
( 0, '\xff\xd8', HC.IMAGE_JPEG ),
( 0, 'GIF87a', HC.IMAGE_GIF ),
( 0, 'GIF89a', HC.IMAGE_GIF ),
( 0, '\x89PNG', HC.IMAGE_PNG ),
( 0, '\x89PNG', HC.UNDETERMINED_PNG ),
( 0, 'BM', HC.IMAGE_BMP ),
( 0, 'CWS', HC.APPLICATION_FLASH ),
( 0, 'FWS', HC.APPLICATION_FLASH ),
@ -216,7 +216,25 @@ def GetMime( path ):
# we'll catch and verify wma later
else: return mime
elif mime == HC.UNDETERMINED_PNG:
return HC.IMAGE_PNG
# atm (Feb 2016), ffmpeg doesn't report duration for apngs, so can't do this just yet.
#
#if HydrusVideoHandling.HasVideoStream( path ):
#
# return HC.VIDEO_APNG
#
#else:
#
# return HC.IMAGE_PNG
#
else:
return mime

View File

@ -72,15 +72,13 @@ def GetLocalIP(): return socket.gethostbyname( socket.gethostname() )
def AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = 3600 ):
cmd = [ upnpc_path, '-e', description, '-a', internal_client, str( internal_port ), str( external_port ), protocol, str( duration ) ]
HydrusData.DebugPrint( cmd )
p = subprocess.Popen( cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, startupinfo = HydrusData.GetSubprocessStartupInfo() )
p.wait()
( output, error ) = p.communicate()
HydrusData.DebugPrint( output )
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 ) )

View File

@ -88,7 +88,14 @@ def GetMatroskaOrWebMProperties( path ):
def HasVideoStream( path ):
info = Hydrusffmpeg_parse_infos( path )
try:
info = Hydrusffmpeg_parse_infos( path )
except IOError as e:
HydrusData.ShowException( e )
return False
return info[ 'video_found' ]

BIN
static/ipfs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
static/ipfs_pending.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

BIN
static/ipfs_petitioned.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

BIN
static/ipfs_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B