Version 121

This commit is contained in:
Hydrus 2014-07-09 17:15:14 -05:00
parent 11f375509f
commit 5fe43e355b
21 changed files with 1049 additions and 180 deletions

View File

@ -8,6 +8,10 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 121</h3></li>
<ul>
<li>first version of local booru</li>
</ul>
<li><h3>version 120</h3></li>
<ul>
<li>improved quality of downsized animation rendering</li>

View File

@ -0,0 +1,42 @@
<html>
<head>
<title>getting started - local booru</title>
<link href="hydrus.ico" rel="shortcut icon" />
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="content">
<h3>local booru</h3>
<p>I have been working on a local booru, hosted from your client, to make it easier to share files with others over the internet. It is very new, but I will be adding more features to it. This help page will grow with it.</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, the local booru will no longer work. And if you want people outside your home network (i.e. on the internet) to be able to see it, you will need to forward your client computer's booru port to your internet router.</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><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, as long as your port is being forwarded correctly.</p>
<p>If you use a dynamic-ip service like <a href="http://www.noip.com/">No-IP</a>, you can replace your external IP with your redirect hostname. You have to do it by hand right now, but I'll add a way to do it automatically in future.</p>
<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 class="warning"><b>Hold the phone! I forgot to write the clientside UPnP Daemon to register the local booru's UPnP port! I'll add it next week, so for now, you probably want to add a custom entry via the <i>services->manage local upnp</i> dialog. Use (45866, TCP, 45866, hydrus client local booru, 604800). Send me an email if you can't figure it out.</b></p>
<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. I'm insane about privacy, though, so the hydrus network will not open any external ports without your permission. You 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. 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 want to test that the port forward is set up correctly, just 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>
<h3>so, what do you get?</h3>
<p>Since the booru is still prototype, the actual html layout is currently very simple:</p>
<hr />
<p><img src="local_booru_html.png" /></p>
<hr />
<p>It uses a very similar stylesheet to these help pages, although I've made the thumbnail grid look the way the client does it. At the moment, the thumbnails are the gigantic max size of 200x200, but I will add an option to display the resized versions. Also, flash and audio files will 404 on the thumbnail for the moment.</p>
<p>Clicking a thumbnail leads to a barebones image page that I will flush out in future.</p>
<h3>editing an existing share</h3>
<p>You can review all your shares on <i>services->review services</i>, under <i>local->booru</i>. You can copy the links again, change the title/text/expiration, and delete any shares you don't want any more.</p>
<h3>future plans</h3>
<p>Now I have the simplest possible booru working, there is a lot I can add. I would like to make the thumbnails resizeable, and add sorting controls, allow for custom css, add a normal tag display, make those tags searchable, and generally AJAX-ify the entire thing to offload CPU time to the client's web browser.</p>
<p>Also, the bandwidth tracking feature does not work yet, so I'll get that going.</p>
</div>
</body>
</html>

View File

@ -22,6 +22,7 @@
<li><a href="getting_started_tags.html">getting started with tags</a></li>
<li><a href="getting_started_ratings.html">getting started with ratings</a></li>
<li><a href="getting_started_subscriptions.html">getting started with subscriptions</a></li>
<li><a href="getting_started_local_booru.html">getting started with the local booru</a></li>
<li><a><del>getting started with messages</del></a> (currently reworking this)</li>
<li><a href="access_keys.html">access keys to my server</a></li>
<li><a href="tagging_schema.html">thoughts on a public tagging schema</a></li>

BIN
help/local_booru_dialog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
help/local_booru_html.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
help/local_booru_upnp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1088,9 +1088,116 @@ class CDPPFileServiceIdentifiers():
self._petitioned.discard( service_identifier )
class LocalRatings():
class LocalBooruCache( object ):
# c for current; feel free to rename this stupid thing
def __init__( self ):
self._lock = threading.Lock()
self._RefreshShares()
HC.pubsub.sub( self, 'RefreshShares', 'refresh_local_booru_shares' )
def _CheckFileAuthorised( self, share_key, hash ):
self._CheckShareAuthorised( share_key )
info = self._GetInfo( share_key )
if hash not in info[ 'hashes_set' ]: raise HydrusExceptions.NotFoundException( 'That file was not found in that share.' )
def _CheckShareAuthorised( self, share_key ):
info = self._GetInfo( share_key )
timeout = info[ 'timeout' ]
if timeout is not None and HC.GetNow() > timeout: raise HydrusExceptions.ForbiddenException( 'This share has expired.' )
def _GetInfo( self, share_key ):
try: info = self._keys_to_infos[ share_key ]
except: raise HydrusExceptions.NotFoundException( 'Did not find that share on this booru.' )
if info is None:
info = HC.app.Read( 'local_booru_share', share_key )
hashes = info[ 'hashes' ]
info[ 'hashes_set' ] = set( hashes )
self._keys_to_infos[ share_key ] = info
return info
def _RefreshShares( self ):
self._keys_to_infos = {}
share_keys = HC.app.Read( 'local_booru_share_keys' )
for share_key in share_keys: self._keys_to_infos[ share_key ] = None
def CheckShareAuthorised( self, share_key ):
with self._lock: self._CheckShareAuthorised( share_key )
def CheckFileAuthorised( self, share_key, hash ):
with self._lock: self._CheckFileAuthorised( share_key, hash )
def GetGalleryInfo( self, share_key ):
with self._lock:
info = self._GetInfo( share_key )
name = info[ 'name' ]
text = info[ 'text' ]
timeout = info[ 'timeout' ]
hashes = info[ 'hashes' ]
# eventually move to a set of media results that has tags from ~whatever~
# the media_results should be generated on demand and cached with a timeout
return ( name, text, timeout, hashes )
def GetPageInfo( self, share_key, hash ):
with self._lock:
info = self._GetInfo( share_key )
name = info[ 'name' ]
text = info[ 'text' ]
timeout = info[ 'timeout' ]
# eventually move to returning resolution and so on so we can show tags and whatever, doing a proper page
return ( name, text, timeout )
def RefreshShares( self ):
with self._lock:
self._RefreshShares()
class LocalRatings():
def __init__( self, service_identifiers_to_ratings ):

View File

@ -172,7 +172,7 @@ The database will be locked while the backup occurs, which may lock up your gui
self._timestamps[ 'boot' ] = HC.GetNow()
self._local_service = None
self._server = None
self._booru_service = None
init_result = True
@ -261,6 +261,7 @@ The database will be locked while the backup occurs, which may lock up your gui
self._managers[ 'tag_parents' ] = HydrusTags.TagParentsManager()
self._managers[ 'undo' ] = CC.UndoManager()
self._managers[ 'web_sessions' ] = HydrusSessions.WebSessionManagerClient()
self._managers[ 'local_booru' ] = CC.LocalBooruCache()
self._fullscreen_image_cache = CC.RenderedImageCache( 'fullscreen' )
self._preview_image_cache = CC.RenderedImageCache( 'preview' )
@ -275,6 +276,7 @@ The database will be locked while the backup occurs, which may lock up your gui
HC.pubsub.sub( self, 'Clipboard', 'clipboard' )
HC.pubsub.sub( self, 'RestartServer', 'restart_server' )
HC.pubsub.sub( self, 'RestartBooru', 'restart_booru' )
self.Bind( HC.EVT_PUBSUB, self.EventPubSub )
@ -288,6 +290,7 @@ The database will be locked while the backup occurs, which may lock up your gui
if HC.is_db_updated: wx.CallLater( 1, HC.ShowText, 'The client has updated to version ' + HC.u( HC.SOFTWARE_VERSION ) + '!' )
self.RestartServer()
self.RestartBooru()
self._db.StartDaemons()
self._last_idle_time = 0.0
@ -345,6 +348,74 @@ The database will be locked while the backup occurs, which may lock up your gui
return result
def RestartBooru( self ):
service = self.Read( 'service', HC.LOCAL_BOORU_SERVICE_IDENTIFIER )
info = service.GetInfo()
port = info[ 'port' ]
def TWISTEDRestartServer():
def StartServer( *args, **kwargs ):
try:
connection = httplib.HTTPConnection( '127.0.0.1', port, timeout = 10 )
try:
connection.connect()
connection.close()
text = 'The client\'s booru server could not start because something was already bound to port ' + HC.u( port ) + '.'
text += os.linesep * 2
text += 'This usually means another hydrus client is already running and occupying that port. It could be a previous instantiation of this client that has yet to shut itself down.'
text += os.linesep * 2
text += 'You can change the port this client tries to host its local server on in services->manage services.'
wx.CallLater( 1, HC.ShowText, text )
except:
booru_server_service_identifier = HC.ServerServiceIdentifier( 'local booru', HC.LOCAL_BOORU )
self._booru_service = reactor.listenTCP( port, HydrusServer.HydrusServiceBooru( booru_server_service_identifier, 'This is the local booru.' ) )
connection = httplib.HTTPConnection( '127.0.0.1', port, timeout = 10 )
try:
connection.connect()
connection.close()
except:
text = 'Tried to bind port ' + HC.u( port ) + ' for the local booru, but it failed.'
wx.CallLater( 1, HC.ShowText, text )
except Exception as e:
wx.CallAfter( HC.ShowException, e )
if self._booru_service is None: StartServer()
else:
deferred = defer.maybeDeferred( self._booru_service.stopListening )
deferred.addCallback( StartServer )
reactor.callFromThread( TWISTEDRestartServer )
def RestartServer( self ):
port = HC.options[ 'local_port' ]
@ -374,7 +445,7 @@ The database will be locked while the backup occurs, which may lock up your gui
local_file_server_service_identifier = HC.ServerServiceIdentifier( 'local file', HC.LOCAL_FILE )
self._local_service = reactor.listenTCP( port, HydrusServer.HydrusServiceLocal( local_file_server_service_identifier, 'hello' ) )
self._local_service = reactor.listenTCP( port, HydrusServer.HydrusServiceLocal( local_file_server_service_identifier, 'This is the local file service.' ) )
connection = httplib.HTTPConnection( '127.0.0.1', port, timeout = 10 )
@ -385,7 +456,7 @@ The database will be locked while the backup occurs, which may lock up your gui
except:
text = 'Tried to bind port ' + HC.u( port ) + ' but it failed'
text = 'Tried to bind port ' + HC.u( port ) + ' for the local server, but it failed.'
wx.CallLater( 1, HC.ShowText, text )

View File

@ -29,13 +29,14 @@ import wx
import yaml
YAML_DUMP_ID_SINGLE = 0
YAML_DUMP_ID_BOORU = 1
YAML_DUMP_ID_REMOTE_BOORU = 1
YAML_DUMP_ID_FAVOURITE_CUSTOM_FILTER_ACTIONS = 2
YAML_DUMP_ID_GUI_SESSION = 3
YAML_DUMP_ID_IMAGEBOARD = 4
YAML_DUMP_ID_IMPORT_FOLDER = 5
YAML_DUMP_ID_EXPORT_FOLDER = 6
YAML_DUMP_ID_SUBSCRIPTION = 7
YAML_DUMP_ID_LOCAL_BOORU = 8
class FileDB():
@ -1591,7 +1592,21 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def _DeleteYAMLDump( self, c, dump_type, dump_name = None ):
if dump_name is None: c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ?;', ( dump_type, ) )
else: c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ? AND dump_name = ?;', ( dump_type, dump_name ) )
else:
if dump_type == YAML_DUMP_ID_LOCAL_BOORU: dump_name = dump_name.encode( 'hex' )
c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ? AND dump_name = ?;', ( dump_type, dump_name ) )
if dump_type == YAML_DUMP_ID_LOCAL_BOORU:
service_id = self._GetServiceId( c, HC.LOCAL_BOORU_SERVICE_IDENTIFIER )
c.execute( 'DELETE FROM service_info WHERE service_id = ? AND info_type = ?;', ( service_id, HC.SERVICE_INFO_NUM_SHARES ) )
HC.pubsub.pub( 'refresh_local_booru_shares' )
def _FattenAutocompleteCache( self, c ):
@ -2804,7 +2819,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
elif service_type == HC.LOCAL_BOORU:
if info_type == HC.SERVICE_INFO_NUM_SHARES: result = c.execute( 'SELECT COUNT( * ) FROM booru_shares WHERE service_id = ?;', ( service_id, ) ).fetchone()
if info_type == HC.SERVICE_INFO_NUM_SHARES: result = c.execute( 'SELECT COUNT( * ) FROM yaml_dumps WHERE dump_type = ?;', ( YAML_DUMP_ID_LOCAL_BOORU, ) ).fetchone()
if result is None: info = 0
@ -2995,6 +3010,11 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
result = { dump_name : data for ( dump_name, data ) in c.execute( 'SELECT dump_name, dump FROM yaml_dumps WHERE dump_type = ?;', ( dump_type, ) ) }
if dump_type == YAML_DUMP_ID_LOCAL_BOORU:
result = { dump_name.decode( 'hex' ) : data for ( dump_name, data ) in result.items() }
if dump_type == YAML_DUMP_ID_SUBSCRIPTION:
for ( dump_name, data ) in result.items():
@ -3005,6 +3025,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
else:
if dump_type == YAML_DUMP_ID_LOCAL_BOORU: dump_name = dump_name.encode( 'hex' )
result = c.execute( 'SELECT dump FROM yaml_dumps WHERE dump_type = ? AND dump_name = ?;', ( dump_type, dump_name ) ).fetchone()
if result is None:
@ -3031,6 +3053,18 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
return result
def _GetYAMLDumpNames( self, c, dump_type ):
names = [ name for ( name, ) in c.execute( 'SELECT dump_name FROM yaml_dumps WHERE dump_type = ?;', ( dump_type, ) ) ]
if dump_type == YAML_DUMP_ID_LOCAL_BOORU:
names = [ name.decode( 'hex' ) for name in names ]
return names
def _ImportFile( self, c, path, advanced_import_options = {}, service_identifiers_to_tags = {}, generate_media_result = False, override_deleted = False, url = None ):
result = 'successful'
@ -3824,6 +3858,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def _SetYAMLDump( self, c, dump_type, dump_name, data ):
if dump_type == YAML_DUMP_ID_LOCAL_BOORU: dump_name = dump_name.encode( 'hex' )
c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ? AND dump_name = ?;', ( dump_type, dump_name ) )
if dump_type == YAML_DUMP_ID_SUBSCRIPTION: data[ 'advanced_tag_options' ] = data[ 'advanced_tag_options' ].items()
@ -3836,6 +3872,15 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
raise
if dump_type == YAML_DUMP_ID_LOCAL_BOORU:
service_id = self._GetServiceId( c, HC.LOCAL_BOORU_SERVICE_IDENTIFIER )
c.execute( 'DELETE FROM service_info WHERE service_id = ? AND info_type = ?;', ( service_id, HC.SERVICE_INFO_NUM_SHARES ) )
HC.pubsub.pub( 'refresh_local_booru_shares' )
def _UpdateAutocompleteTagCacheFromFiles( self, c, file_service_id, hash_ids, direction ):
@ -4381,6 +4426,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
self._UpdateServiceInfo( c, service_id, update )
if service_type == HC.LOCAL_BOORU: HC.pubsub.pub( 'restart_booru' )
if recalc_combined_mappings:
@ -4735,7 +4782,7 @@ class DB( ServiceDB ):
self._AddService( c, init_service_identifier, info )
c.executemany( 'INSERT INTO yaml_dumps VALUES ( ?, ?, ? );', ( ( YAML_DUMP_ID_BOORU, name, booru ) for ( name, booru ) in CC.DEFAULT_BOORUS.items() ) )
c.executemany( 'INSERT INTO yaml_dumps VALUES ( ?, ?, ? );', ( ( YAML_DUMP_ID_REMOTE_BOORU, name, booru ) for ( name, booru ) in CC.DEFAULT_BOORUS.items() ) )
c.executemany( 'INSERT INTO yaml_dumps VALUES ( ?, ?, ? );', ( ( YAML_DUMP_ID_IMAGEBOARD, name, imageboards ) for ( name, imageboards ) in CC.DEFAULT_IMAGEBOARDS ) )
@ -4882,7 +4929,7 @@ class DB( ServiceDB ):
if version == 116:
c.execute( 'DELETE FROM service_info WHERE info_type == ?;', ( HC.SERVICE_INFO_NUM_THUMBNAILS, ) )
c.execute( 'DELETE FROM service_info WHERE info_type = ?;', ( HC.SERVICE_INFO_NUM_THUMBNAILS, ) )
if version == 117:
@ -6716,7 +6763,7 @@ class DB( ServiceDB ):
data = c.execute( 'SELECT name, booru FROM boorus;' ).fetchall()
inserts.extend( ( ( YAML_DUMP_ID_BOORU, name, booru ) for ( name, booru ) in data ) )
inserts.extend( ( ( YAML_DUMP_ID_REMOTE_BOORU, name, booru ) for ( name, booru ) in data ) )
# favourite custom filter actions
@ -6956,8 +7003,6 @@ class DB( ServiceDB ):
if action == '4chan_pass': result = self._GetYAMLDump( c, YAML_DUMP_ID_SINGLE, '4chan_pass' )
elif action == 'autocomplete_contacts': result = self._GetAutocompleteContacts( c, *args, **kwargs )
elif action == 'autocomplete_tags': result = self._GetAutocompleteTags( c, *args, **kwargs )
elif action == 'booru': result = self._GetYAMLDump( c, YAML_DUMP_ID_BOORU, *args, **kwargs )
elif action == 'boorus': result = self._GetYAMLDump( c, YAML_DUMP_ID_BOORU )
elif action == 'contact_names': result = self._GetContactNames( c, *args, **kwargs )
elif action == 'do_message_query': result = self._DoMessageQuery( c, *args, **kwargs )
elif action == 'downloads': result = self._GetDownloads( c, *args, **kwargs )
@ -6971,6 +7016,9 @@ class DB( ServiceDB ):
elif action == 'identities': result = self._GetIdentities( c, *args, **kwargs )
elif action == 'imageboards': result = self._GetYAMLDump( c, YAML_DUMP_ID_IMAGEBOARD, *args, **kwargs )
elif action == 'import_folders': result = self._GetYAMLDump( c, YAML_DUMP_ID_IMPORT_FOLDER, *args, **kwargs )
elif action == 'local_booru_share_keys': result = self._GetYAMLDumpNames( c, YAML_DUMP_ID_LOCAL_BOORU )
elif action == 'local_booru_share': result = self._GetYAMLDump( c, YAML_DUMP_ID_LOCAL_BOORU, *args, **kwargs )
elif action == 'local_booru_shares': result = self._GetYAMLDump( c, YAML_DUMP_ID_LOCAL_BOORU )
elif action == 'md5_status': result = self._GetMD5Status( c, *args, **kwargs )
elif action == 'media_results': result = self._GetMediaResultsFromHashes( c, *args, **kwargs )
elif action == 'media_results_from_ids': result = self._GetMediaResults( c, *args, **kwargs )
@ -6983,6 +7031,8 @@ class DB( ServiceDB ):
elif action == 'pixiv_account': result = self._GetYAMLDump( c, YAML_DUMP_ID_SINGLE, 'pixiv_account' )
elif action == 'ratings_filter': result = self._GetRatingsFilter( c, *args, **kwargs )
elif action == 'ratings_media_result': result = self._GetRatingsMediaResult( c, *args, **kwargs )
elif action == 'remote_booru': result = self._GetYAMLDump( c, YAML_DUMP_ID_REMOTE_BOORU, *args, **kwargs )
elif action == 'remote_boorus': result = self._GetYAMLDump( c, YAML_DUMP_ID_REMOTE_BOORU )
elif action == 'service': result = self._GetService( c, *args, **kwargs )
elif action == 'service_identifiers': result = self._GetServiceIdentifiers( c, *args, **kwargs )
elif action == 'service_info': result = self._GetServiceInfo( c, *args, **kwargs )
@ -7011,21 +7061,21 @@ class DB( ServiceDB ):
elif action == 'add_uploads': result = self._AddUploads( c, *args, **kwargs )
elif action == 'archive_conversation': result = self._ArchiveConversation( c, *args, **kwargs )
elif action == 'backup': result = self._Backup( c, *args, **kwargs )
elif action == 'booru': result = self._SetYAMLDump( c, YAML_DUMP_ID_BOORU, *args, **kwargs )
elif action == 'contact_associated': result = self._AssociateContact( c, *args, **kwargs )
elif action == 'content_updates': result = self._ProcessContentUpdates( c, *args, **kwargs )
elif action == 'copy_files': result = self._CopyFiles( c, *args, **kwargs )
elif action == 'delete_booru': result = self._DeleteYAMLDump( c, YAML_DUMP_ID_BOORU, *args, **kwargs )
elif action == 'delete_conversation': result = self._DeleteConversation( c, *args, **kwargs )
elif action == 'delete_draft': result = self._DeleteDraft( c, *args, **kwargs )
elif action == 'delete_export_folder': result = self._DeleteYAMLDump( c, YAML_DUMP_ID_EXPORT_FOLDER, *args, **kwargs )
elif action == 'delete_favourite_custom_filter_actions': result = self._DeleteYAMLDump( c, YAML_DUMP_ID_FAVOURITE_CUSTOM_FILTER_ACTIONS, *args, **kwargs )
elif action == 'delete_gui_session': result = self._DeleteYAMLDump( c, YAML_DUMP_ID_GUI_SESSION, *args, **kwargs )
elif action == 'delete_hydrus_session_key': result = self._DeleteHydrusSessionKey( c, *args, **kwargs )
elif action == 'delete_imageboard': result = self._DeleteYAMLDump( c, YAML_DUMP_ID_IMAGEBOARD, *args, **kwargs )
elif action == 'delete_import_folder': result = self._DeleteYAMLDump( c, YAML_DUMP_ID_IMPORT_FOLDER, *args, **kwargs )
elif action == 'delete_local_booru_share': result = self._DeleteYAMLDump( c, YAML_DUMP_ID_LOCAL_BOORU, *args, **kwargs )
elif action == 'delete_orphans': result = self._DeleteOrphans( c, *args, **kwargs )
elif action == 'delete_pending': result = self._DeletePending( c, *args, **kwargs )
elif action == 'delete_hydrus_session_key': result = self._DeleteHydrusSessionKey( c, *args, **kwargs )
elif action == 'delete_remote_booru': result = self._DeleteYAMLDump( c, YAML_DUMP_ID_REMOTE_BOORU, *args, **kwargs )
elif action == 'delete_subscription': result = self._DeleteYAMLDump( c, YAML_DUMP_ID_SUBSCRIPTION, *args, **kwargs )
elif action == 'draft_message': result = self._DraftMessage( c, *args, **kwargs )
elif action == 'export_folder': result = self._SetYAMLDump( c, YAML_DUMP_ID_EXPORT_FOLDER, *args, **kwargs )
@ -7039,10 +7089,12 @@ class DB( ServiceDB ):
elif action == 'import_file': result = self._ImportFile( c, *args, **kwargs )
elif action == 'import_folder': result = self._SetYAMLDump( c, YAML_DUMP_ID_IMPORT_FOLDER, *args, **kwargs )
elif action == 'inbox_conversation': result = self._InboxConversation( c, *args, **kwargs )
elif action == 'local_booru_share': result = self._SetYAMLDump( c, YAML_DUMP_ID_LOCAL_BOORU, *args, **kwargs )
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 == 'pixiv_account': result = self._SetYAMLDump( c, YAML_DUMP_ID_SINGLE, 'pixiv_account', *args, **kwargs )
elif action == 'remote_booru': result = self._SetYAMLDump( c, YAML_DUMP_ID_REMOTE_BOORU, *args, **kwargs )
elif action == 'reset_service': result = self._ResetService( c, *args, **kwargs )
elif action == 'save_options': result = self._SaveOptions( c, *args, **kwargs )
elif action == 'service_updates': result = self._ProcessServiceUpdates( c, *args, **kwargs )
@ -8102,7 +8154,7 @@ def DAEMONSynchroniseSubscriptions():
( booru_name, booru_query_type ) = query_type
try: booru = HC.app.ReadDaemon( 'booru', booru_name )
try: booru = HC.app.ReadDaemon( 'remote_booru', booru_name )
except: raise Exception( 'While attempting to execute a subscription on booru ' + name + ', the client could not find that booru in the db.' )
tags = query.split( ' ' )

View File

@ -8,6 +8,7 @@ import ClientGUIPages
import HydrusDownloading
import HydrusFileHandling
import HydrusImageHandling
import HydrusNATPunch
import HydrusThreading
import itertools
import os
@ -2276,8 +2277,6 @@ class FrameReviewServices( ClientGUICommon.Frame ):
elif service_type == HC.LOCAL_BOORU:
self._link = wx.HyperlinkCtrl( self._info_panel, label = 'link', url = 'link', id = -1 )
self._num_shares = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._bytes = ClientGUICommon.Gauge( self._info_panel )
@ -2314,6 +2313,25 @@ class FrameReviewServices( ClientGUICommon.Frame ):
self._updates_text = wx.StaticText( self._synchro_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
if service_type == HC.LOCAL_BOORU:
self._booru_shares_panel = ClientGUICommon.StaticBox( self, 'shares' )
self._booru_shares = ClientGUICommon.SaneListCtrl( self._booru_shares_panel, -1, [ ( 'title', 110 ), ( 'text', -1 ), ( 'expires', 170 ), ( 'num files', 70 ) ] )
self._copy_internal_share_link = wx.Button( self._booru_shares_panel, label = 'copy internal share link' )
self._copy_internal_share_link.Bind( wx.EVT_BUTTON, self.EventCopyInternalShareURL )
self._copy_external_share_link = wx.Button( self._booru_shares_panel, label = 'copy external share link' )
self._copy_external_share_link.Bind( wx.EVT_BUTTON, self.EventCopyExternalShareURL )
self._booru_edit = wx.Button( self._booru_shares_panel, label = 'edit' )
self._booru_edit.Bind( wx.EVT_BUTTON, self.EventBooruEdit )
self._booru_delete = wx.Button( self._booru_shares_panel, label = 'delete' )
self._booru_delete.Bind( wx.EVT_BUTTON, self.EventBooruDelete )
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
self._service_wide_update = wx.Button( self, label = 'perform a service-wide update' )
@ -2372,7 +2390,6 @@ class FrameReviewServices( ClientGUICommon.Frame ):
elif service_type == HC.LOCAL_BOORU:
self._info_panel.AddF( self._link, FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._num_shares, FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._bytes, FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._bytes_text, FLAGS_EXPAND_PERPENDICULAR )
@ -2402,6 +2419,21 @@ class FrameReviewServices( ClientGUICommon.Frame ):
vbox.AddF( self._synchro_panel, FLAGS_EXPAND_PERPENDICULAR )
if service_type == HC.LOCAL_BOORU:
self._booru_shares_panel.AddF( self._booru_shares, FLAGS_EXPAND_BOTH_WAYS )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.AddF( self._copy_internal_share_link, FLAGS_MIXED )
b_box.AddF( self._copy_external_share_link, FLAGS_MIXED )
b_box.AddF( self._booru_edit, FLAGS_MIXED )
b_box.AddF( self._booru_delete, FLAGS_MIXED )
self._booru_shares_panel.AddF( b_box, FLAGS_BUTTON_SIZERS )
vbox.AddF( self._booru_shares_panel, FLAGS_EXPAND_BOTH_WAYS )
if service_type in HC.RESTRICTED_SERVICES + [ HC.LOCAL_TAG ]:
repo_buttons_hbox = wx.BoxSizer( wx.HORIZONTAL )
@ -2452,6 +2484,7 @@ class FrameReviewServices( ClientGUICommon.Frame ):
HC.pubsub.sub( self, 'ProcessServiceUpdates', 'service_updates_gui' )
HC.pubsub.sub( self, 'AddThumbnailCount', 'add_thumbnail_count' )
if service_type == HC.LOCAL_BOORU: HC.pubsub.sub( self, 'RefreshLocalBooruShares', 'refresh_local_booru_shares' )
def _DisplayAccountInfo( self ):
@ -2466,13 +2499,6 @@ class FrameReviewServices( ClientGUICommon.Frame ):
info = self._service.GetInfo()
port = info[ 'port' ]
url = 'http://127.0.0.1:' + str( port ) + '/'
self._link.SetLabel( url )
self._link.SetURL( url )
max_monthly_data = info[ 'max_monthly_data' ]
used_monthly_data = info[ 'used_monthly_data' ]
@ -2640,6 +2666,23 @@ class FrameReviewServices( ClientGUICommon.Frame ):
if service_type == HC.LOCAL_BOORU:
booru_shares = HC.app.Read( 'local_booru_shares' )
self._booru_shares.DeleteAllItems()
for ( share_key, info ) in booru_shares.items():
name = info[ 'name' ]
text = info[ 'text' ]
timeout = info[ 'timeout' ]
hashes = info[ 'hashes' ]
self._booru_shares.Append( ( name, text, HC.ConvertTimestampToPrettyExpiry( timeout ), len( hashes ) ), ( name, text, timeout, ( len( hashes ), hashes, share_key ) ) )
if service_type == HC.SERVER_ADMIN:
if self._service.IsInitialised():
@ -2665,6 +2708,87 @@ class FrameReviewServices( ClientGUICommon.Frame ):
def EventBooruDelete( self, event ):
for ( name, text, timeout, ( num_hashes, hashes, share_key ) ) in self._booru_shares.GetSelectedClientData():
HC.app.Write( 'delete_local_booru_share', share_key )
def EventBooruEdit( self, event ):
writes = []
for ( name, text, timeout, ( num_hashes, hashes, share_key ) ) in self._booru_shares.GetSelectedClientData():
with ClientGUIDialogs.DialogInputLocalBooruShare( self, share_key, name, text, timeout, hashes, new_share = False) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( share_key, name, text, timeout, hashes ) = dlg.GetInfo()
info = {}
info[ 'name' ] = name
info[ 'text' ] = text
info[ 'timeout' ] = timeout
info[ 'hashes' ] = hashes
writes.append( ( share_key, info ) )
for ( share_key, info ) in writes: HC.app.Write( 'local_booru_share', share_key, info )
def EventCopyExternalShareURL( self, event ):
shares = self._booru_shares.GetSelectedClientData()
if len( shares ) > 0:
( name, text, timeout, ( num_hashes, hashes, share_key ) ) = shares[0]
self._service = HC.app.Read( 'service', HC.LOCAL_BOORU_SERVICE_IDENTIFIER )
info = self._service.GetInfo()
external_ip = HydrusNATPunch.GetExternalIP() # eventually check for optional host replacement here
external_port = info[ 'upnp' ]
if external_port is None: external_port = info[ 'port' ]
url = 'http://' + external_ip + ':' + str( external_port ) + '/gallery?share_key=' + share_key.encode( 'hex' )
HC.pubsub.pub( 'clipboard', 'text', url )
def EventCopyInternalShareURL( self, event ):
shares = self._booru_shares.GetSelectedClientData()
if len( shares ) > 0:
( name, text, timeout, ( num_hashes, hashes, share_key ) ) = shares[0]
self._service = HC.app.Read( 'service', HC.LOCAL_BOORU_SERVICE_IDENTIFIER )
info = self._service.GetInfo()
internal_ip = '127.0.0.1'
internal_port = info[ 'port' ]
url = 'http://' + internal_ip + ':' + str( internal_port ) + '/gallery?share_key=' + share_key.encode( 'hex' )
HC.pubsub.pub( 'clipboard', 'text', url )
def EventServiceWideUpdate( self, event ):
with ClientGUIDialogs.DialogAdvancedContentUpdate( self, self._service_identifier ) as dlg:
@ -2720,6 +2844,11 @@ class FrameReviewServices( ClientGUICommon.Frame ):
def RefreshLocalBooruShares( self ):
self._DisplayService()
def TIMEREventUpdates( self, event ): self._updates_text.SetLabel( self._service.GetUpdateStatus() )

View File

@ -4042,8 +4042,8 @@ class StaticImage( wx.Window ):
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
self._Draw()
if not self._image_container.IsRendered(): self._timer_render_wait.Start()
if not self._image_container.IsRendered(): self._timer_render_wait.Start( 16, wx.TIMER_ONE_SHOT)

View File

@ -3100,7 +3100,7 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
num_columns = len( columns )
wx.ListCtrl.__init__( self, parent, size=( -1, height ), style=wx.LC_REPORT )
wx.ListCtrl.__init__( self, parent, style = wx.LC_REPORT )
ListCtrlAutoWidthMixin.__init__( self )
ColumnSorterMixin.__init__( self, num_columns )
@ -3120,6 +3120,8 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
self.setResizeColumn( resize_column )
self.SetMinSize( ( -1, height ) )
def Append( self, display_tuple, data_tuple ):
@ -3178,6 +3180,20 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
def GetListCtrl( self ): return self
def GetSelectedClientData( self ):
indices = self.GetAllSelected()
results = []
for index in indices:
results.append( self.GetClientData( index ) )
return results
def RemoveAllSelected( self ):
indices = self.GetAllSelected()

View File

@ -4,6 +4,7 @@ import HydrusDownloading
import HydrusEncryption
import HydrusExceptions
import HydrusFileHandling
import HydrusNATPunch
import HydrusTags
import HydrusThreading
import ClientConstants as CC
@ -1987,6 +1988,169 @@ class DialogInputFileSystemPredicate( Dialog ):
def GetPredicate( self ): return self._predicate
class DialogInputLocalBooruShare( Dialog ):
def __init__( self, parent, share_key, name, text, timeout, hashes, new_share = False ):
def InitialiseControls():
self._name = wx.TextCtrl( self )
self._text = wx.TextCtrl( self, style = wx.TE_MULTILINE )
self._text.SetMinSize( ( -1, 100 ) )
message = 'expires in'
self._timeout_number = ClientGUICommon.NoneableSpinCtrl( self, message, none_phrase = 'no expiration', max = 1000000, multiplier = 1 )
self._timeout_multiplier = ClientGUICommon.BetterChoice( self )
self._timeout_multiplier.Append( 'minutes', 60 )
self._timeout_multiplier.Append( 'hours', 60 * 60 )
self._timeout_multiplier.Append( 'days', 60 * 60 * 24 )
self._copy_internal_share_link = wx.Button( self, label = 'copy internal share link' )
self._copy_internal_share_link.Bind( wx.EVT_BUTTON, self.EventCopyInternalShareURL )
self._copy_external_share_link = wx.Button( self, label = 'copy external share link' )
self._copy_external_share_link.Bind( wx.EVT_BUTTON, self.EventCopyExternalShareURL )
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._share_key = share_key
self._name.SetValue( name )
self._text.SetValue( text )
if timeout is None:
self._timeout_number.SetValue( None )
self._timeout_multiplier.SelectClientData( 60 )
else:
time_left = max( 0, timeout - HC.GetNow() )
if time_left < 60 * 60 * 12: time_value = 60
elif time_left < 60 * 60 * 24 * 7: time_value = 60 * 60
else: time_value = 60 * 60 * 24
self._timeout_number.SetValue( time_left / time_value )
self._timeout_multiplier.SelectClientData( time_value )
self._hashes = hashes
def ArrangeControls():
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self, label = 'share name' ), FLAGS_MIXED )
gridbox.AddF( self._name, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label = 'share text' ), FLAGS_MIXED )
gridbox.AddF( self._text, FLAGS_EXPAND_BOTH_WAYS )
timeout_box = wx.BoxSizer( wx.HORIZONTAL )
timeout_box.AddF( self._timeout_number, FLAGS_EXPAND_BOTH_WAYS )
timeout_box.AddF( self._timeout_multiplier, FLAGS_EXPAND_BOTH_WAYS )
link_box = wx.BoxSizer( wx.HORIZONTAL )
link_box.AddF( self._copy_internal_share_link, FLAGS_MIXED )
link_box.AddF( self._copy_external_share_link, FLAGS_MIXED )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.AddF( self._ok, FLAGS_MIXED )
b_box.AddF( self._cancel, FLAGS_MIXED )
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Sharing ' + HC.ConvertIntToPrettyString( len( self._hashes ) ) + ' files.'
intro += os.linesep + 'Title and text are optional.'
if new_share: intro += os.linesep + 'The link will not work until you ok this dialog.'
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( timeout_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( link_box, FLAGS_BUTTON_SIZERS )
vbox.AddF( b_box, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 350 )
self.SetInitialSize( ( x, y ) )
Dialog.__init__( self, parent, 'configure local booru share' )
InitialiseControls()
PopulateControls()
ArrangeControls()
wx.CallAfter( self._ok.SetFocus )
def EventCopyExternalShareURL( self, event ):
self._service = HC.app.Read( 'service', HC.LOCAL_BOORU_SERVICE_IDENTIFIER )
info = self._service.GetInfo()
external_ip = HydrusNATPunch.GetExternalIP() # eventually check for optional host replacement here
external_port = info[ 'upnp' ]
if external_port is None: external_port = info[ 'port' ]
url = 'http://' + external_ip + ':' + str( external_port ) + '/gallery?share_key=' + self._share_key.encode( 'hex' )
HC.pubsub.pub( 'clipboard', 'text', url )
def EventCopyInternalShareURL( self, event ):
self._service = HC.app.Read( 'service', HC.LOCAL_BOORU_SERVICE_IDENTIFIER )
info = self._service.GetInfo()
internal_ip = '127.0.0.1'
internal_port = info[ 'port' ]
url = 'http://' + internal_ip + ':' + str( internal_port ) + '/gallery?share_key=' + self._share_key.encode( 'hex' )
HC.pubsub.pub( 'clipboard', 'text', url )
def GetInfo( self ):
name = self._name.GetValue()
text = self._text.GetValue()
timeout = self._timeout_number.GetValue()
if timeout is not None: timeout = timeout * self._timeout_multiplier.GetChoice() + HC.GetNow()
return ( self._share_key, name, text, timeout, self._hashes )
class DialogInputLocalFiles( Dialog ):
def __init__( self, parent, paths = [] ):
@ -4473,7 +4637,7 @@ class DialogSelectBooru( Dialog ):
def PopulateControls():
boorus = HC.app.Read( 'boorus' )
boorus = HC.app.Read( 'remote_boorus' )
for ( name, booru ) in boorus.items(): self._boorus.Append( name, booru )

View File

@ -421,7 +421,7 @@ class DialogManageBoorus( ClientGUIDialogs.Dialog ):
def PopulateControls():
boorus = HC.app.Read( 'boorus' )
boorus = HC.app.Read( 'remote_boorus' )
for ( name, booru ) in boorus.items():
@ -534,13 +534,13 @@ class DialogManageBoorus( ClientGUIDialogs.Dialog ):
( name, booru ) = data
HC.app.Write( 'booru', name, booru )
HC.app.Write( 'remote_booru', name, booru )
elif action == HC.DELETE:
name = data
HC.app.Write( 'delete_booru', name )
HC.app.Write( 'delete_remote_booru', name )
@ -5032,12 +5032,6 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
self._max_monthly_data = ClientGUICommon.NoneableSpinCtrl( self._booru_options_panel, 'max monthly MB', multiplier = 1024 * 1024 )
#
self._booru_shares_panel = ClientGUICommon.StaticBox( self, 'shares' )
# add listctrl here
def PopulateControls():
@ -5055,8 +5049,6 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
self._upnp.SetValue( self._info[ 'upnp' ] )
self._max_monthly_data.SetValue( self._info[ 'max_monthly_data' ] )
# fill up listctrl here
def ArrangeControls():
@ -5137,10 +5129,6 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
vbox.AddF( self._booru_options_panel, FLAGS_EXPAND_PERPENDICULAR )
self._booru_shares_panel.AddF( wx.StaticText( self._booru_shares_panel, label = 'listctrl goes here' ), FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._booru_shares_panel, FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
@ -5731,7 +5719,7 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
if self._booru_selector.GetCount() == 0:
boorus = HC.app.Read( 'boorus' )
boorus = HC.app.Read( 'remote_boorus' )
for ( name, booru ) in boorus.items(): self._booru_selector.Append( name, booru )
@ -5782,7 +5770,7 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
if self._booru_selector.GetCount() == 0:
boorus = HC.app.Read( 'boorus' )
boorus = HC.app.Read( 'remote_boorus' )
for ( name, booru ) in boorus.items(): self._booru_selector.Append( name, booru )
@ -7594,9 +7582,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
self._mappings_list_ctrl = ClientGUICommon.SaneListCtrl( self, 760, [ ( 'description', -1 ), ( 'internal ip', 100 ), ( 'internal port', 80 ), ( 'external ip', 100 ), ( 'external port', 80 ), ( 'protocol', 80 ), ( 'lease', 80 ) ] )
self._mappings_list_ctrl.SetMinSize( ( 760, 660 ) )
self._mappings_list_ctrl = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'description', -1 ), ( 'internal ip', 100 ), ( 'internal port', 80 ), ( 'external ip', 100 ), ( 'external port', 80 ), ( 'protocol', 80 ), ( 'lease', 80 ) ] )
self._add_local = wx.Button( self, label = 'add service mapping' )
self._add_local.Bind( wx.EVT_BUTTON, self.EventAddServiceMapping )
@ -7641,6 +7627,8 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 760 )
self.SetInitialSize( ( x, y ) )

View File

@ -647,6 +647,38 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ):
HC.pubsub.pub( 'focus_changed', self._page_key, media )
def _ShareOnLocalBooru( self ):
if len( self._selected_media ) > 0:
share_key = os.urandom( 32 )
name = ''
text = ''
timeout = HC.GetNow() + 60 * 60 * 24
hashes = self._GetSelectedHashes()
with ClientGUIDialogs.DialogInputLocalBooruShare( self, share_key, name, text, timeout, hashes, new_share = True ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( share_key, name, text, timeout, hashes ) = dlg.GetInfo()
info = {}
info[ 'name' ] = name
info[ 'text' ] = text
info[ 'timeout' ] = timeout
info[ 'hashes' ] = hashes
HC.app.Write( 'local_booru_share', share_key, info )
self.SetFocus()
def _ShowSelectionInNewQueryPage( self ):
hashes = self._GetSelectedHashes()
@ -1528,6 +1560,7 @@ class MediaPanelThumbnails( MediaPanel ):
elif command == 'shift_scroll_end': self._ScrollEnd( True )
elif command == 'shift_scroll_home': self._ScrollHome( True )
elif command == 'select': self._Select( data )
elif command == 'share_on_local_booru': self._ShareOnLocalBooru()
elif command == 'show_selection_in_new_query_page': self._ShowSelectionInNewQueryPage()
elif command == 'upload': self._UploadFiles( data )
elif command == 'key_up': self._MoveFocussedThumbnail( -1, 0, False )
@ -1684,7 +1717,7 @@ class MediaPanelThumbnails( MediaPanel ):
inbox_phrase = 'return all to inbox'
remove_phrase = 'remove all'
local_delete_phrase = 'delete all'
dump_phrase = 'dump all'
dump_phrase = 'dump all to 4chan'
export_phrase = 'files'
copy_phrase = 'files'
@ -1710,7 +1743,7 @@ class MediaPanelThumbnails( MediaPanel ):
inbox_phrase = 'return to inbox'
remove_phrase = 'remove'
local_delete_phrase = 'delete'
dump_phrase = 'dump'
dump_phrase = 'dump to 4chan'
export_phrase = 'file'
copy_phrase = 'file'
@ -1901,18 +1934,9 @@ class MediaPanelThumbnails( MediaPanel ):
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', HC.LOCAL_FILE_SERVICE_IDENTIFIER ), local_delete_phrase )
#
# share
export_menu = wx.Menu()
export_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'export_files' ), export_phrase )
export_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'export_tags' ), 'tags' )
menu.AppendMenu( CC.ID_NULL, 'export', export_menu )
#
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'new_thread_dumper' ), dump_phrase )
share_menu = wx.Menu()
#
@ -1924,7 +1948,28 @@ class MediaPanelThumbnails( MediaPanel ):
copy_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_path' ) , 'path' )
copy_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_local_url' ) , 'local url' )
menu.AppendMenu( CC.ID_NULL, 'copy', copy_menu )
share_menu.AppendMenu( CC.ID_NULL, 'copy', copy_menu )
#
share_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'new_thread_dumper' ), dump_phrase )
#
export_menu = wx.Menu()
export_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'export_files' ), export_phrase )
export_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'export_tags' ), 'tags' )
share_menu.AppendMenu( CC.ID_NULL, 'export', export_menu )
#
share_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'share_on_local_booru' ), 'on local booru' )
#
menu.AppendMenu( CC.ID_NULL, 'share', share_menu )
#

View File

@ -64,7 +64,7 @@ options = {}
# Misc
NETWORK_VERSION = 13
SOFTWARE_VERSION = 120
SOFTWARE_VERSION = 121
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -9,6 +9,46 @@ from twisted.internet import reactor, defer
from twisted.internet.threads import deferToThread
from twisted.python import log
# new stuff starts here
if HC.PLATFORM_LINUX: upnpc_path = '"' + HC.BIN_DIR + os.path.sep + 'upnpc_linux"'
elif HC.PLATFORM_OSX: upnpc_path = '"' + HC.BIN_DIR + os.path.sep + 'upnpc_osx"'
elif HC.PLATFORM_WINDOWS: upnpc_path = '"' + HC.BIN_DIR + os.path.sep + 'upnpc_win32.exe"'
external_ip = None
external_ip_time = 0
def GetExternalIP():
if HC.GetNow() - external_ip_time > 3600 * 24:
command = upnpc_path + ' -l'
p = subprocess.Popen( command, shell = True, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE )
p.wait()
( output, error ) = p.communicate()
if error is not None and len( error ) > 0: raise Exception( 'Problem while trying to fetch External IP:' + os.linesep + os.linesep + HC.u( error ) )
else:
try:
lines = output.split( os.linesep )
i = lines.index( ' i protocol exPort->inAddr:inPort description remoteHost leaseTime' )
'''ExternalIPAddress = ip'''
( gumpf, external_ip ) = lines[ i - 1 ].split( ' = ' )
except: raise Exception( 'Problem while trying to fetch External IP.' )
return external_ip
def GetLocalIP(): return socket.gethostbyname( socket.gethostname() )
# old, win32 only stuff
@ -77,12 +117,6 @@ def RemoveUPnPMapping( external_port, protocol ):
except: raise Exception( 'Attempt to remove UPnP mapping failed.' )
'''
# new stuff starts here
if HC.PLATFORM_LINUX: upnpc_path = '"' + HC.BIN_DIR + os.path.sep + 'upnpc_linux"'
elif HC.PLATFORM_OSX: upnpc_path = '"' + HC.BIN_DIR + os.path.sep + 'upnpc_osx"'
elif HC.PLATFORM_WINDOWS: upnpc_path = '"' + HC.BIN_DIR + os.path.sep + 'upnpc_win32.exe"'
def AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = 3600 ):
command = upnpc_path + ' -e "' + description + '" -a ' + internal_client + ' ' + str( internal_port ) + ' ' + str( external_port ) + ' ' + protocol + ' ' + str( duration )

View File

@ -253,6 +253,10 @@ ROOT_MESSAGE_END = '''</p>
self.end_headers()
'''
LOCAL_DOMAIN = HydrusServerResources.HydrusDomain( True )
REMOTE_DOMAIN = HydrusServerResources.HydrusDomain( False )
class HydrusRequest( Request ):
def __init__( self, *args, **kwargs ):
@ -298,14 +302,28 @@ class HydrusService( Site ):
return root
class HydrusServiceBooru( HydrusService ):
def _InitRoot( self ):
root = HydrusService._InitRoot( self )
root.putChild( 'gallery', HydrusServerResources.HydrusResourceCommandBooruGallery( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'page', HydrusServerResources.HydrusResourceCommandBooruPage( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'file', HydrusServerResources.HydrusResourceCommandBooruFile( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'thumbnail', HydrusServerResources.HydrusResourceCommandBooruThumbnail( self._service_identifier, REMOTE_DOMAIN ) )
return root
class HydrusServiceLocal( HydrusService ):
def _InitRoot( self ):
root = HydrusService._InitRoot( self )
root.putChild( 'file', HydrusServerResources.HydrusResourceCommandFileLocal( self._service_identifier ) )
root.putChild( 'thumbnail', HydrusServerResources.HydrusResourceCommandThumbnailLocal( self._service_identifier ) )
root.putChild( 'file', HydrusServerResources.HydrusResourceCommandLocalFile( self._service_identifier, LOCAL_DOMAIN ) )
root.putChild( 'thumbnail', HydrusServerResources.HydrusResourceCommandLocalThumbnail( self._service_identifier, LOCAL_DOMAIN ) )
return root
@ -323,15 +341,15 @@ class HydrusServiceRestricted( HydrusService ):
root = HydrusService._InitRoot( self )
root.putChild( 'access_key', HydrusServerResources.HydrusResourceCommandAccessKey( self._service_identifier ) )
root.putChild( 'access_key_verification', HydrusServerResources.HydrusResourceCommandAccessKeyVerification( self._service_identifier ) )
root.putChild( 'session_key', HydrusServerResources.HydrusResourceCommandSessionKey( self._service_identifier ) )
root.putChild( 'access_key', HydrusServerResources.HydrusResourceCommandAccessKey( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'access_key_verification', HydrusServerResources.HydrusResourceCommandAccessKeyVerification( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'session_key', HydrusServerResources.HydrusResourceCommandSessionKey( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'account', HydrusServerResources.HydrusResourceCommandRestrictedAccount( self._service_identifier ) )
root.putChild( 'account_info', HydrusServerResources.HydrusResourceCommandRestrictedAccountInfo( self._service_identifier ) )
root.putChild( 'account_types', HydrusServerResources.HydrusResourceCommandRestrictedAccountTypes( self._service_identifier ) )
root.putChild( 'registration_keys', HydrusServerResources.HydrusResourceCommandRestrictedRegistrationKeys( self._service_identifier ) )
root.putChild( 'stats', HydrusServerResources.HydrusResourceCommandRestrictedStats( self._service_identifier ) )
root.putChild( 'account', HydrusServerResources.HydrusResourceCommandRestrictedAccount( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'account_info', HydrusServerResources.HydrusResourceCommandRestrictedAccountInfo( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'account_types', HydrusServerResources.HydrusResourceCommandRestrictedAccountTypes( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'registration_keys', HydrusServerResources.HydrusResourceCommandRestrictedRegistrationKeys( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'stats', HydrusServerResources.HydrusResourceCommandRestrictedStats( self._service_identifier, REMOTE_DOMAIN ) )
return root
@ -342,9 +360,9 @@ class HydrusServiceAdmin( HydrusServiceRestricted ):
root = HydrusServiceRestricted._InitRoot( self )
root.putChild( 'backup', HydrusServerResources.HydrusResourceCommandRestrictedBackup( self._service_identifier ) )
root.putChild( 'init', HydrusServerResources.HydrusResourceCommandInit( self._service_identifier ) )
root.putChild( 'services', HydrusServerResources.HydrusResourceCommandRestrictedServices( self._service_identifier ) )
root.putChild( 'backup', HydrusServerResources.HydrusResourceCommandRestrictedBackup( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'init', HydrusServerResources.HydrusResourceCommandInit( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'services', HydrusServerResources.HydrusResourceCommandRestrictedServices( self._service_identifier, REMOTE_DOMAIN ) )
return root
@ -355,10 +373,10 @@ class HydrusServiceRepository( HydrusServiceRestricted ):
root = HydrusServiceRestricted._InitRoot( self )
root.putChild( 'news', HydrusServerResources.HydrusResourceCommandRestrictedNews( self._service_identifier ) )
root.putChild( 'num_petitions', HydrusServerResources.HydrusResourceCommandRestrictedNumPetitions( self._service_identifier ) )
root.putChild( 'petition', HydrusServerResources.HydrusResourceCommandRestrictedPetition( self._service_identifier ) )
root.putChild( 'update', HydrusServerResources.HydrusResourceCommandRestrictedUpdate( self._service_identifier ) )
root.putChild( 'news', HydrusServerResources.HydrusResourceCommandRestrictedNews( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'num_petitions', HydrusServerResources.HydrusResourceCommandRestrictedNumPetitions( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'petition', HydrusServerResources.HydrusResourceCommandRestrictedPetition( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'update', HydrusServerResources.HydrusResourceCommandRestrictedUpdate( self._service_identifier, REMOTE_DOMAIN ) )
return root
@ -369,9 +387,9 @@ class HydrusServiceRepositoryFile( HydrusServiceRepository ):
root = HydrusServiceRepository._InitRoot( self )
root.putChild( 'file', HydrusServerResources.HydrusResourceCommandRestrictedFileRepository( self._service_identifier ) )
root.putChild( 'ip', HydrusServerResources.HydrusResourceCommandRestrictedIP( self._service_identifier ) )
root.putChild( 'thumbnail', HydrusServerResources.HydrusResourceCommandRestrictedThumbnailRepository( self._service_identifier ) )
root.putChild( 'file', HydrusServerResources.HydrusResourceCommandRestrictedRepositoryFile( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'ip', HydrusServerResources.HydrusResourceCommandRestrictedIP( self._service_identifier, REMOTE_DOMAIN ) )
root.putChild( 'thumbnail', HydrusServerResources.HydrusResourceCommandRestrictedRepositoryThumbnail( self._service_identifier, REMOTE_DOMAIN ) )
return root

View File

@ -268,6 +268,18 @@ def ParseFileArguments( path ):
hydrus_favicon = FileResource( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', defaultType = HC.IMAGE_ICON )
class HydrusDomain( object ):
def __init__( self, local_only ):
self._local_only = local_only
def CheckValid( self, client_ip ):
if self._local_only and client_ip != '127.0.0.1': raise HydrusExceptions.ForbiddenException( 'Only local access allowed!' )
class HydrusResourceWelcome( Resource ):
def __init__( self, service_identifier, message ):
@ -293,14 +305,14 @@ class HydrusResourceWelcome( Resource ):
class HydrusResourceCommand( Resource ):
local_only = False
def __init__( self, service_identifier ):
def __init__( self, service_identifier, domain ):
Resource.__init__( self )
self._service_identifier = service_identifier
self._domain = domain
service_type = self._service_identifier.GetType()
self._server_version_string = HC.service_string_lookup[ service_type ] + '/' + str( HC.NETWORK_VERSION )
@ -310,7 +322,7 @@ class HydrusResourceCommand( Resource ):
self._checkUserAgent( request )
self._checkLocal( request )
self._domain.CheckValid( request.getClientIP() )
return request
@ -330,7 +342,7 @@ class HydrusResourceCommand( Resource ):
try: hydrus_args[ name ] = int( value )
except: raise HydrusExceptions.ForbiddenException( 'I was expecting to parse \'' + name + '\' as an integer, but it failed.' )
elif name in ( 'access_key', 'title', 'subject_access_key', 'contact_key', 'hash', 'subject_hash', 'subject_tag', 'message_key' ):
elif name in ( 'access_key', 'title', 'subject_access_key', 'contact_key', 'hash', 'subject_hash', 'subject_tag', 'message_key', 'share_key' ):
try: hydrus_args[ name ] = value.decode( 'hex' )
except: raise HydrusExceptions.ForbiddenException( 'I was expecting to parse \'' + name + '\' as a hex-encoded string, but it failed.' )
@ -508,11 +520,6 @@ class HydrusResourceCommand( Resource ):
return d
def _checkLocal( self, request ):
if self.local_only and request.getClientIP() != '127.0.0.1': raise HydrusExceptions.ForbiddenException( 'Only local access allowed!' )
def _checkUserAgent( self, request ):
request.is_hydrus_user_agent = False
@ -710,14 +717,111 @@ class HydrusResourceCommandAccessKeyVerification( HydrusResourceCommand ):
return response_context
class HydrusResourceCommandFileLocal( HydrusResourceCommand ):
class HydrusResourceCommandBooru( HydrusResourceCommand ):
local_only = True
def _callbackCheckRestrictions( self, request ):
self._checkUserAgent( request )
self._domain.CheckValid( request.getClientIP() )
# extract booru key, hence fetch booru cache thing and assign it to this request
# or fail with a 404
return request
class HydrusResourceCommandBooruGallery( HydrusResourceCommandBooru ):
def _threadDoGETJob( self, request ):
# in future, make this a standard frame with a search key that'll load xml or yaml AJAX stuff
# with file info included, so the page can sort and whatever
share_key = request.hydrus_args[ 'share_key' ]
local_booru_manager = HC.app.GetManager( 'local_booru' )
local_booru_manager.CheckShareAuthorised( share_key )
( name, text, timeout, hashes ) = local_booru_manager.GetGalleryInfo( share_key )
body = '''<html>
<head>'''
if name == '': body += '''
<title>hydrus network local booru share</title>'''
else: body += '''
<title>''' + name + '''</title>'''
body += '''
</head>
<style>
body { font-family: "Calibri", Arial, sans-serif; color: rgb( 85, 85, 85 ); line-height: 1.5; }
a { color: #222; text-decoration: none; font-weight: bold; }
h3 { color: #222; }
a:hover { color: #555 }
.footer { text-align: center; font-size: 80% }
.media { clear: both; }
.name { font-weight: bold; font-size: 150%; }
.thumbnail { margin: 1px; border: 1px rgb( 223, 227, 230 ) solid; display: inline-block; }
.thumbnail_container { text-align: center; width: 200px; height: 200px; display: table-cell; vertical-align: middle; }
.thumbnail_container img { display: block; margin-left: auto; margin-right: auto; }
.timeout { font-size: 80%; float: right; margin: 2px }
</style>
<body>'''
body += '''
<span class="timeout">This share ''' + HC.ConvertTimestampToPrettyExpiry( timeout ) + '''.</span>'''
if name != '': body += '''
<div class="name">''' + name + '''</div>'''
if text != '':
newline = '''</p>
<p>'''
body += '''
<p>''' + text.replace( os.linesep, newline ).replace( '\n', newline ) + '''</p>'''
body+= '''
<div class="media">'''
for hash in hashes:
body += '''
<span class="thumbnail">
<span class="thumbnail_container">
<a href="page?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''">
<img src="thumbnail?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''" />
</a>
</span>
</span>'''
body += '''
</div>
<div class="footer"><a href="http://hydrusnetwork.github.io/hydrus/">hydrus network</a></div>
</body>
</html>'''
response_context = HC.ResponseContext( 200, mime = HC.TEXT_HTML, body = body )
return response_context
class HydrusResourceCommandBooruFile( HydrusResourceCommandBooru ):
def _threadDoGETJob( self, request ):
share_key = request.hydrus_args[ 'share_key' ]
hash = request.hydrus_args[ 'hash' ]
local_booru_manager = HC.app.GetManager( 'local_booru' )
local_booru_manager.CheckFileAuthorised( share_key, hash )
path = CC.GetFilePath( hash )
response_context = HC.ResponseContext( 200, path = path )
@ -725,6 +829,89 @@ class HydrusResourceCommandFileLocal( HydrusResourceCommand ):
return response_context
class HydrusResourceCommandBooruPage( HydrusResourceCommandBooru ):
def _threadDoGETJob( self, request ):
share_key = request.hydrus_args[ 'share_key' ]
hash = request.hydrus_args[ 'hash' ]
local_booru_manager = HC.app.GetManager( 'local_booru' )
local_booru_manager.CheckFileAuthorised( share_key, hash )
( name, text, timeout ) = local_booru_manager.GetPageInfo( share_key, hash )
body = '''<html>
<head>'''
if name == '': body += '''
<title>hydrus network local booru share</title>'''
else: body += '''
<title>''' + name + '''</title>'''
body += '''
</head>
<style>
body { font-family: "Calibri", Arial, sans-serif; color: rgb( 85, 85, 85 ); line-height: 1.5; }
a { color: #222; text-decoration: none; font-weight: bold; }
h3 { color: #222; }
a:hover { color: #555 }
.footer { text-align: center; font-size: 80% }
.media { clear: both; }
.name { font-weight: bold; font-size: 150%; }
.thumbnail { margin: 1px; border: 1px rgb( 223, 227, 230 ) solid; display: inline-block; }
.thumbnail_container { text-align: center; width: 200px; height: 200px; display: table-cell; vertical-align: middle; }
.thumbnail_container img { display: block; margin-left: auto; margin-right: auto; }
.timeout { font-size: 80%; float: right; margin: 2px }
</style>
<body>'''
body += '''
<span class="timeout">This share ''' + HC.ConvertTimestampToPrettyExpiry( timeout ) + '''.</span>'''
if name != '': body += '''
<div class="name">''' + name + '''</div>'''
if text != '':
newline = '''</p>
<p>'''
body += '''
<p>''' + text.replace( os.linesep, newline ) + '''</p>'''
body+= '''
<div class="media">
<img src="file?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''" />
</div>
<div class="footer"><a href="http://hydrusnetwork.github.io/hydrus/">hydrus network</a></div>
</body>
</html>'''
response_context = HC.ResponseContext( 200, mime = HC.TEXT_HTML, body = body )
return response_context
class HydrusResourceCommandBooruThumbnail( HydrusResourceCommandBooru ):
def _threadDoGETJob( self, request ):
share_key = request.hydrus_args[ 'share_key' ]
hash = request.hydrus_args[ 'hash' ]
local_booru_manager = HC.app.GetManager( 'local_booru' )
local_booru_manager.CheckFileAuthorised( share_key, hash )
path = CC.GetThumbnailPath( hash )
response_context = HC.ResponseContext( 200, path = path )
return response_context
class HydrusResourceCommandInit( HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
@ -738,6 +925,32 @@ class HydrusResourceCommandInit( HydrusResourceCommand ):
return response_context
class HydrusResourceCommandLocalFile( HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
path = CC.GetFilePath( hash )
response_context = HC.ResponseContext( 200, path = path )
return response_context
class HydrusResourceCommandLocalThumbnail( HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
path = CC.GetThumbnailPath( hash )
response_context = HC.ResponseContext( 200, path = path )
return response_context
class HydrusResourceCommandSessionKey( HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
@ -761,21 +974,6 @@ class HydrusResourceCommandSessionKey( HydrusResourceCommand ):
return response_context
class HydrusResourceCommandThumbnailLocal( HydrusResourceCommand ):
local_only = True
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
path = CC.GetThumbnailPath( hash )
response_context = HC.ResponseContext( 200, path = path )
return response_context
class HydrusResourceCommandRestricted( HydrusResourceCommand ):
GET_PERMISSION = HC.GENERAL_ADMIN
@ -787,7 +985,7 @@ class HydrusResourceCommandRestricted( HydrusResourceCommand ):
self._checkUserAgent( request )
self._checkLocal( request )
self._domain.CheckValid( request.getClientIP() )
self._checkSession( request )
@ -952,41 +1150,6 @@ class HydrusResourceCommandRestrictedBackup( HydrusResourceCommandRestricted ):
return response_context
class HydrusResourceCommandRestrictedFileRepository( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
POST_PERMISSION = HC.POST_DATA
RECORD_GET_DATA_USAGE = True
RECORD_POST_DATA_USAGE = True
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
# don't I need to check that we aren't stealing the file from another service?
path = SC.GetPath( 'file', hash )
response_context = HC.ResponseContext( 200, path = path )
return response_context
def _threadDoPOSTJob( self, request ):
account = request.hydrus_account
file_dict = request.hydrus_args
file_dict[ 'ip' ] = request.getClientIP()
HC.app.Write( 'file', self._service_identifier, account, file_dict )
response_context = HC.ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedIP( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
@ -1070,6 +1233,59 @@ class HydrusResourceCommandRestrictedRegistrationKeys( HydrusResourceCommandRest
return response_context
class HydrusResourceCommandRestrictedRepositoryFile( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
POST_PERMISSION = HC.POST_DATA
RECORD_GET_DATA_USAGE = True
RECORD_POST_DATA_USAGE = True
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
# don't I need to check that we aren't stealing the file from another service?
path = SC.GetPath( 'file', hash )
response_context = HC.ResponseContext( 200, path = path )
return response_context
def _threadDoPOSTJob( self, request ):
account = request.hydrus_account
file_dict = request.hydrus_args
file_dict[ 'ip' ] = request.getClientIP()
HC.app.Write( 'file', self._service_identifier, account, file_dict )
response_context = HC.ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedRepositoryThumbnail( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
RECORD_GET_DATA_USAGE = True
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
# don't I need to check that we aren't stealing the file from another service?
path = SC.GetPath( 'thumbnail', hash )
response_context = HC.ResponseContext( 200, path = path )
return response_context
class HydrusResourceCommandRestrictedServices( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
@ -1116,24 +1332,6 @@ class HydrusResourceCommandRestrictedStats( HydrusResourceCommandRestricted ):
return response_context
class HydrusResourceCommandRestrictedThumbnailRepository( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
RECORD_GET_DATA_USAGE = True
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
# don't I need to check that we aren't stealing the file from another service?
path = SC.GetPath( 'thumbnail', hash )
response_context = HC.ResponseContext( 200, path = path )
return response_context
class HydrusResourceCommandRestrictedUpdate( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA

View File

@ -181,14 +181,14 @@ class TestClientDB( unittest.TestCase ):
for ( name, booru ) in CC.DEFAULT_BOORUS.items():
read_booru = self._read( 'booru', name )
read_booru = self._read( 'remote_booru', name )
self.assertEqual( booru.GetData(), read_booru.GetData() )
#
result = self._read( 'boorus' )
result = self._read( 'remote_boorus' )
for name in CC.DEFAULT_BOORUS: self.assertEqual( result[ name ].GetData(), CC.DEFAULT_BOORUS[ name ].GetData() )
@ -205,19 +205,19 @@ class TestClientDB( unittest.TestCase ):
booru = CC.Booru( name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces )
self._write( 'booru', 'blah', booru )
self._write( 'remote_booru', 'blah', booru )
read_booru = self._read( 'booru', name )
read_booru = self._read( 'remote_booru', name )
self.assertEqual( booru.GetData(), read_booru.GetData() )
#
self._write( 'delete_booru', 'blah' )
self._write( 'delete_remote_booru', 'blah' )
with self.assertRaises( Exception ):
read_booru = self._read( 'booru', name )
read_booru = self._read( 'remote_booru', name )

View File

@ -25,7 +25,7 @@ class TestDBDialogs( unittest.TestCase ):
def test_dialog_select_booru( self ):
HC.app.SetRead( 'boorus', CC.DEFAULT_BOORUS )
HC.app.SetRead( 'remote_boorus', CC.DEFAULT_BOORUS )
with ClientGUIDialogs.DialogSelectBooru( None ) as dlg: