Version 121
This commit is contained in:
parent
11f375509f
commit
5fe43e355b
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
After Width: | Height: | Size: 227 KiB |
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
|
@ -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 ):
|
||||
|
||||
|
|
|
@ -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 )
|
||||
|
||||
|
|
|
@ -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( ' ' )
|
||||
|
|
|
@ -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() )
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 )
|
||||
|
||||
|
|
|
@ -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 ) )
|
||||
|
||||
|
||||
|
|
|
@ -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 )
|
||||
|
||||
#
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 13
|
||||
SOFTWARE_VERSION = 120
|
||||
SOFTWARE_VERSION = 121
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
Loading…
Reference in New Issue