Version 116

This commit is contained in:
Hydrus 2014-05-21 16:37:35 -05:00
parent 239b2ce31b
commit 2d4353a232
21 changed files with 801 additions and 501 deletions

View File

@ -8,6 +8,14 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 116</h3></li>
<ul>
<li>gifs now render their frames as needed, rather than building a giant memory-hogging cache of all of them</li>
<li>webms now have thumbnails!</li>
<li>refactored important hydrus threading code to a separate module</li>
<li>added new 'call-to' worker threads for snappier job-thread behaviour</li>
<li>fixed a typo that was stopping new repositories being entered in the manage repositories dialog</li>
</ul>
<li><h3>version 115</h3></li>
<ul>
<li>moved review and manage services windows to a local/remote dichotomy</li>

View File

@ -20,7 +20,6 @@ import sqlite3
import sys
import threading
import time
import threading
import traceback
import urllib
import yaml
@ -650,7 +649,7 @@ def GetThumbnailPath( hash, full_size = True ):
thumbnail_dimensions = HC.options[ 'thumbnail_dimensions' ]
thumbnail_resized = HydrusImageHandling.GenerateThumbnail( full_size_path, thumbnail_dimensions )
thumbnail_resized = HydrusFileHandling.GenerateThumbnail( full_size_path, thumbnail_dimensions )
with open( path, 'wb' ) as f: f.write( thumbnail_resized )
@ -2009,8 +2008,6 @@ class MediaResult():
def GetTimestamp( self ): return self._tuple[4]
def IsAnimated( self ): return self.GetNumFrames() > 0
def ProcessContentUpdate( self, service_identifier, content_update ):
( data_type, action, row ) = content_update.ToTuple()
@ -2113,7 +2110,7 @@ class RenderedImageCache():
elif key in self._keys_being_rendered: return self._keys_being_rendered[ key ]
else:
image_container = HydrusImageHandling.RenderImage( media, target_resolution )
image_container = HydrusImageHandling.ImageContainerStatic( media, target_resolution )
self._keys_being_rendered[ key ] = image_container
@ -2430,7 +2427,7 @@ class ThumbnailCache():
path = HC.STATIC_DIR + os.path.sep + name + '.png'
thumbnail = HydrusImageHandling.GenerateThumbnail( path, HC.options[ 'thumbnail_dimensions' ] )
thumbnail = HydrusFileHandling.GenerateThumbnail( path, HC.options[ 'thumbnail_dimensions' ] )
temp_path = HC.GetTempPath()
@ -2446,7 +2443,7 @@ class ThumbnailCache():
mime = media.GetDisplayMedia().GetMime()
if mime in HC.IMAGES:
if mime in HC.MIMES_WITH_THUMBNAILS:
hash = media.GetDisplayMedia().GetHash()

View File

@ -12,7 +12,6 @@ import random
import sqlite3
import threading
import time
import threading
import traceback
import yaml
import wx

View File

@ -9,6 +9,7 @@ import HydrusImageHandling
import HydrusSessions
import HydrusServer
import HydrusTags
import HydrusThreading
import ClientConstants as CC
import ClientDB
import ClientGUI
@ -454,10 +455,7 @@ Once it is done, the client will restart.'''
self.Yield() # this processes the event queue immediately, so the paint event can occur
def StartFileQuery( self, query_key, search_context ):
threading.Thread( target = self.THREADDoFileQuery, name = 'file query', args = ( query_key, search_context ) ).start()
def StartFileQuery( self, query_key, search_context ): HydrusThreading.CallToThread( self.THREADDoFileQuery, query_key, search_context )
def THREADDoFileQuery( self, query_key, search_context ):

View File

@ -12,6 +12,7 @@ import HydrusImageHandling
import HydrusMessageHandling
import HydrusServer
import HydrusTags
import HydrusThreading
import ClientConstants as CC
import ClientConstantsMessages
import os
@ -46,7 +47,7 @@ class FileDB():
with open( thumbnail_path, 'wb' ) as f: f.write( thumbnail )
thumbnail_resized = HydrusImageHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
thumbnail_resized_path = CC.GetExpectedThumbnailPath( hash, False )
@ -3110,7 +3111,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
if mime in HC.MIMES_WITH_THUMBNAILS:
thumbnail = HydrusImageHandling.GenerateThumbnail( path )
thumbnail = HydrusFileHandling.GenerateThumbnail( path )
self._AddThumbnails( c, [ ( hash, thumbnail ) ] )
@ -4856,6 +4857,29 @@ class DB( ServiceDB ):
c.execute( 'CREATE TABLE booru_shares ( service_id INTEGER REFERENCES services ( service_id ) ON DELETE CASCADE, share_key BLOB_BYTES, share TEXT_YAML, expiry INTEGER, used_monthly_data INTEGER, max_monthly_data INTEGER, ip_restriction TEXT, notes TEXT, PRIMARY KEY( service_id, share_key ) );' )
if version == 115:
for path in CC.IterateAllFilePaths():
try:
filename = os.path.basename( path )
( hash_encoded, ext ) = filename.split( '.', 1 )
hash = hash_encoded.decode( 'hex' )
if ext == 'webm':
thumbnail = HydrusFileHandling.GenerateThumbnail( path )
with open( CC.GetExpectedThumbnailPath( hash ), 'wb' ) as f: f.write( thumbnail )
except: print( traceback.format_exc())
c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
HC.is_db_updated = True
@ -7088,15 +7112,15 @@ class DB( ServiceDB ):
def StartDaemons( self ):
HC.DAEMONWorker( 'CheckImportFolders', DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 )
HC.DAEMONWorker( 'CheckExportFolders', DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 )
HC.DAEMONWorker( 'DownloadFiles', DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) )
HC.DAEMONWorker( 'DownloadThumbnails', DAEMONDownloadThumbnails, ( 'notify_new_permissions', 'notify_new_thumbnails' ) )
HC.DAEMONWorker( 'ResizeThumbnails', DAEMONResizeThumbnails, init_wait = 600 )
HC.DAEMONWorker( 'SynchroniseAccounts', DAEMONSynchroniseAccounts, ( 'notify_new_services', 'permissions_are_stale' ) )
HC.DAEMONWorker( 'SynchroniseRepositories', DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ) )
HC.DAEMONWorker( 'SynchroniseSubscriptions', DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ) )
HC.DAEMONQueue( 'FlushRepositoryUpdates', DAEMONFlushServiceUpdates, 'service_updates_delayed', period = 5 )
HydrusThreading.DAEMONWorker( 'CheckImportFolders', DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 )
HydrusThreading.DAEMONWorker( 'CheckExportFolders', DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 )
HydrusThreading.DAEMONWorker( 'DownloadFiles', DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) )
HydrusThreading.DAEMONWorker( 'DownloadThumbnails', DAEMONDownloadThumbnails, ( 'notify_new_permissions', 'notify_new_thumbnails' ) )
HydrusThreading.DAEMONWorker( 'ResizeThumbnails', DAEMONResizeThumbnails, init_wait = 600 )
HydrusThreading.DAEMONWorker( 'SynchroniseAccounts', DAEMONSynchroniseAccounts, ( 'notify_new_services', 'permissions_are_stale' ) )
HydrusThreading.DAEMONWorker( 'SynchroniseRepositories', DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ) )
HydrusThreading.DAEMONWorker( 'SynchroniseSubscriptions', DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ) )
HydrusThreading.DAEMONQueue( 'FlushRepositoryUpdates', DAEMONFlushServiceUpdates, 'service_updates_delayed', period = 5 )
def WaitUntilGoodTimeToUseDBThread( self ):
@ -7457,7 +7481,7 @@ def DAEMONResizeThumbnails():
try:
thumbnail_resized = HydrusImageHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
thumbnail_resized_path = thumbnail_path + '_resized'

View File

@ -8,6 +8,7 @@ import ClientGUIPages
import HydrusDownloading
import HydrusFileHandling
import HydrusImageHandling
import HydrusThreading
import itertools
import os
import random
@ -1418,7 +1419,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
if dlg.ShowModal() == wx.ID_YES:
def do_it():
def THREADRegenerateThumbnails():
message = HC.Message( HC.MESSAGE_TYPE_TEXT, { 'text' : 'regenerating thumbnails - creating directories' } )
@ -1455,13 +1456,13 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
hash = hash_encoded.decode( 'hex' )
thumbnail = HydrusImageHandling.GenerateThumbnail( path )
thumbnail = HydrusFileHandling.GenerateThumbnail( path )
thumbnail_path = CC.GetExpectedThumbnailPath( hash, True )
with open( thumbnail_path, 'wb' ) as f: f.write( thumbnail )
thumbnail_resized = HydrusImageHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
thumbnail_resized_path = CC.GetExpectedThumbnailPath( hash, False )
@ -1476,7 +1477,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
message.SetInfo( 'text', 'done regenerating thumbnails' )
threading.Thread( target = do_it ).start()
HydrusThreading.CallToThread( THREADRegenerateThumbnails )
@ -1589,7 +1590,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
HC.pubsub.pub( 'message', message )
threading.Thread( target = HydrusDownloading.THREADDownloadURL, args = ( message, url, url_string ) ).start()
HydrusThreading.CallToThread( HydrusDownloading.THREADDownloadURL, message, url, url_string )
@ -1639,7 +1640,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
def _UploadPending( self, service_identifier ):
threading.Thread( target = self._THREADUploadPending, args = ( service_identifier, ) ).start()
HydrusThreading.CallToThread( self._THREADUploadPending, service_identifier )
def _VacuumDatabase( self ):

View File

@ -11,7 +11,6 @@ import Queue
import random
import shutil
import subprocess
import threading
import time
import traceback
import urllib
@ -21,6 +20,7 @@ import wx.media
if HC.PLATFORM_WINDOWS: import wx.lib.flashwin
ID_TIMER_ANIMATED = wx.NewId()
ID_TIMER_RENDER_WAIT = wx.NewId()
ID_TIMER_ANIMATION_BAR_UPDATE = wx.NewId()
ID_TIMER_SLIDESHOW = wx.NewId()
ID_TIMER_CURSOR_HIDE = wx.NewId()
@ -73,6 +73,174 @@ def GetExtraDimensions( media ):
return ( extra_width, extra_height )
class Animation( wx.Window ):
def __init__( self, parent, media, initial_size, initial_position ):
wx.Window.__init__( self, parent, size = initial_size, pos = initial_position )
self.SetDoubleBuffered( True )
self._media = media
self._animation_container = None
self._animation_bar = None
self._current_frame_index = 0
self._current_frame_drawn = False
self._current_frame_drawn_at = 0
self._paused = False
self._canvas_bmp = wx.EmptyBitmap( 0, 0, 24 )
self._timer_animated = wx.Timer( self, id = ID_TIMER_ANIMATED )
self._paused = False
self.Bind( wx.EVT_PAINT, self.EventPaint )
self.Bind( wx.EVT_SIZE, self.EventResize )
self.Bind( wx.EVT_TIMER, self.TIMEREventAnimated, id = ID_TIMER_ANIMATED )
self.Bind( wx.EVT_MOUSE_EVENTS, self.EventPropagateMouse )
self.EventResize( None )
self._timer_animated.Start( 5, wx.TIMER_CONTINUOUS )
def _DrawFrame( self ):
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
current_frame = self._animation_container.GetFrame( self._current_frame_index )
( my_width, my_height ) = self._canvas_bmp.GetSize()
( frame_width, frame_height ) = current_frame.GetSize()
x_scale = my_width / float( frame_width )
y_scale = my_height / float( frame_height )
dc.SetUserScale( x_scale, y_scale )
hydrus_bmp = current_frame.CreateWxBmp()
dc.DrawBitmap( hydrus_bmp, 0, 0 )
wx.CallAfter( hydrus_bmp.Destroy )
dc.SetUserScale( 1.0, 1.0 )
if self._animation_bar is not None: self._animation_bar.GotoFrame( self._current_frame_index )
self._current_frame_drawn = True
self._current_frame_drawn_at = time.clock()
def _DrawWhite( self ):
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
dc.SetBackground( wx.Brush( wx.WHITE ) )
dc.Clear()
def CurrentFrame( self ): return self._current_frame_index
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
def EventPropagateMouse( self, event ):
screen_position = self.ClientToScreen( event.GetPosition() )
( x, y ) = self.GetParent().ScreenToClient( screen_position )
event.SetX( x )
event.SetY( y )
event.ResumePropagation( 1 )
event.Skip()
def EventResize( self, event ):
( my_width, my_height ) = self.GetClientSize()
( current_bmp_width, current_bmp_height ) = self._canvas_bmp.GetSize()
if my_width != current_bmp_width or my_height != current_bmp_height:
if my_width > 0 and my_height > 0:
if self._animation_container is None: self._animation_container = HydrusImageHandling.ImageContainerAnimated( self._media, ( my_width, my_height ) )
else:
( image_width, image_height ) = self._animation_container.GetSize()
we_just_zoomed_in = my_width > image_width
if we_just_zoomed_in and self._animation_container.IsScaled():
full_resolution = self._animation_container.GetResolution()
self._animation_container = HydrusImageHandling.ImageContainerAnimated( self._media, full_resolution )
self._animation_container.SetPosition( self._current_frame_index )
self._current_frame_drawn = False
wx.CallAfter( self._canvas_bmp.Destroy )
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
if self._animation_container.HasFrame( self._current_frame_index ): self._DrawFrame()
else: self._DrawWhite()
def GotoFrame( self, frame_index ):
if frame_index != self._current_frame_index:
self._current_frame_index = frame_index
self._current_frame_drawn = False
self._animation_container.SetPosition( frame_index )
self._paused = True
def Play( self ): self._paused = False
def SetAnimationBar( self, animation_bar ): self._animation_bar = animation_bar
def TIMEREventAnimated( self, event ):
if self.IsShown():
if self._current_frame_drawn:
ms_since_current_frame_drawn = int( 1000.0 * ( time.clock() - self._current_frame_drawn_at ) )
if not self._paused and ms_since_current_frame_drawn > self._animation_container.GetDuration( self._current_frame_index ):
num_frames = self._media.GetNumFrames()
self._current_frame_index = ( self._current_frame_index + 1 ) % num_frames
self._current_frame_drawn = False
elif self._animation_container.HasFrame( self._current_frame_index ): self._DrawFrame()
class AnimationBar( wx.Window ):
def __init__( self, parent, media, media_window ):
@ -88,7 +256,6 @@ class AnimationBar( wx.Window ):
self._media = media
self._media_window = media_window
self._num_frames = self._media.GetNumFrames()
self._num_frames_rendered = 0
self._current_frame_index = 0
self._currently_in_a_drag = False
@ -112,30 +279,9 @@ class AnimationBar( wx.Window ):
dc.SetPen( wx.TRANSPARENT_PEN )
if self._media.GetMime() in HC.IMAGES:
image_container = self._media_window.GetImageContainer()
self._num_frames_rendered = image_container.GetNumFramesRendered()
num_frames = image_container.GetNumFrames()
my_rendered_width = int( my_width * ( float( self._num_frames_rendered ) / num_frames ) )
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
dc.DrawRectangle( 0, 0, my_rendered_width, ANIMATED_SCANBAR_HEIGHT )
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_SCROLLBAR ) ) )
dc.DrawRectangle( my_rendered_width, 0, my_width - my_rendered_width, ANIMATED_SCANBAR_HEIGHT )
else:
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
dc.DrawRectangle( 0, 0, my_width, ANIMATED_SCANBAR_HEIGHT )
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
dc.DrawRectangle( 0, 0, my_width, ANIMATED_SCANBAR_HEIGHT )
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_SCROLLBAR ) ) )
@ -207,16 +353,7 @@ class AnimationBar( wx.Window ):
if self.IsShown():
if self._media.GetMime() in HC.IMAGES:
image_container = self._media_window.GetImageContainer()
if self._num_frames_rendered != image_container.GetNumFramesRendered():
self._Draw()
elif self._media.GetMime() == HC.APPLICATION_FLASH:
if self._media.GetMime() == HC.APPLICATION_FLASH:
frame_index = self._media_window.CurrentFrame()
@ -2760,7 +2897,7 @@ class RatingsFilterFrameNumerical( ClientGUICommon.FrameThatResizes ):
if best_media_first.qsize() > 0: ( value, media_to_rate_against ) = best_media_first.get()
if not best_media_first.empty(): ( value, media_to_rate_against ) = best_media_first.get()
if media_to_rate_against is None:
@ -3477,7 +3614,11 @@ class MediaContainer( wx.Window ):
media_initial_size = ( x, y - ANIMATED_SCANBAR_HEIGHT )
if self._media.GetMime() in HC.IMAGES: self._media_window = Image( self, self._media, self._image_cache, media_initial_size, media_initial_position )
if self._media.GetMime() in HC.IMAGES:
if ShouldHaveAnimationBar( self._media ): self._media_window = Animation( self, self._media, media_initial_size, media_initial_position )
else: self._media_window = self._media_window = StaticImage( self, self._media, self._image_cache, media_initial_size, media_initial_position )
elif self._media.GetMime() == HC.APPLICATION_FLASH:
self._media_window = wx.lib.flashwin.FlashWindow( self, size = media_initial_size, pos = media_initial_position )
@ -3748,7 +3889,28 @@ class EmbedWindowVideo( wx.Window ):
subprocess.call( 'start "" "' + path + '"', shell = True )
class Image( wx.Window ):
class PDFButton( wx.Button ):
def __init__( self, parent, hash, size ):
wx.Button.__init__( self, parent, label = 'launch pdf', size = size )
self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) )
self._hash = hash
self.Bind( wx.EVT_BUTTON, self.EventButton )
def EventButton( self, event ):
path = CC.GetFilePath( self._hash, HC.APPLICATION_PDF )
# os.system( 'start ' + path )
subprocess.call( 'start "" "' + path + '"', shell = True )
class StaticImage( wx.Window ):
def __init__( self, parent, media, image_cache, initial_size, initial_position ):
@ -3760,43 +3922,33 @@ class Image( wx.Window ):
self._image_container = None
self._image_cache = image_cache
self._animation_bar = None
self._last_clock = time.clock()
self._current_frame_index = 0
self._canvas_bmp = wx.EmptyBitmap( 0, 0, 24 )
self._timer_animated = wx.Timer( self, id = ID_TIMER_ANIMATED )
self._yet_to_draw_initial_frame = True
self._timer_render_wait = wx.Timer( self, id = ID_TIMER_RENDER_WAIT )
self._paused = False
self.Bind( wx.EVT_PAINT, self.EventPaint )
self.Bind( wx.EVT_SIZE, self.EventResize )
self.Bind( wx.EVT_TIMER, self.TIMEREventAnimated, id = ID_TIMER_ANIMATED )
self.Bind( wx.EVT_TIMER, self.TIMEREventRenderWait, id = ID_TIMER_RENDER_WAIT )
self.Bind( wx.EVT_MOUSE_EVENTS, self.EventPropagateMouse )
self.EventResize( None )
self._timer_animated.Start( 16, wx.TIMER_CONTINUOUS )
self._timer_render_wait.Start( 16, wx.TIMER_CONTINUOUS )
def _Draw( self ):
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
dc.SetBackground( wx.Brush( wx.WHITE ) )
if self._image_container.HasFrame( self._current_frame_index ):
dc.Clear()
if self._image_container.IsRendered():
if not self._image_container.IsAnimated():
dc.SetBackground( wx.Brush( wx.WHITE ) )
dc.Clear()
current_frame = self._image_container.GetFrame( self._current_frame_index )
current_frame = self._image_container.GetFrame()
( my_width, my_height ) = self._canvas_bmp.GetSize()
@ -3815,22 +3967,10 @@ class Image( wx.Window ):
dc.SetUserScale( 1.0, 1.0 )
if self._image_container.IsAnimated():
if self._animation_bar is not None: self._animation_bar.GotoFrame( self._current_frame_index )
else: self._timer_animated.Stop()
else:
dc.SetBackground( wx.Brush( wx.WHITE ) )
dc.Clear()
self._timer_render_wait.Stop()
def CurrentFrame( self ): return self._current_frame_index
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
def EventPropagateMouse( self, event ):
@ -3855,27 +3995,18 @@ class Image( wx.Window ):
if my_width > 0 and my_height > 0:
if self._image_container is None:
if self._media.IsAnimated(): self._image_container = HydrusImageHandling.RenderImage( self._media, ( my_width, my_height ) )
else: self._image_container = self._image_cache.GetImage( self._media, ( my_width, my_height ) )
if self._image_container is None: self._image_container = self._image_cache.GetImage( self._media, ( my_width, my_height ) )
else:
( image_width, image_height ) = self._image_container.GetSize()
we_just_zoomed_in = my_width > image_width
we_just_zoomed_in = my_width > image_width or my_height > image_height
if we_just_zoomed_in and self._image_container.IsScaled():
full_resolution = self._image_container.GetResolution()
full_resolution = self._media.GetResolution()
if self._media.IsAnimated(): self._image_container = HydrusImageHandling.RenderImage( self._media, full_resolution )
else: self._image_container = self._image_cache.GetImage( self._media, full_resolution )
self._yet_to_draw_initial_frame = True
self._timer_animated.Start()
self._image_container = self._image_cache.GetImage( self._media, full_resolution )
@ -3884,76 +4015,14 @@ class Image( 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()
def GetImageContainer( self ): return self._image_container
def GotoFrame( self, frame_index ):
def TIMEREventRenderWait( self, event ):
self._current_frame_index = frame_index
self._Draw()
self._timer_animated.Stop()
def Play( self ): self._timer_animated.Start()
def SetAnimationBar( self, animation_bar ): self._animation_bar = animation_bar
def TIMEREventAnimated( self, event ):
if self.IsShown():
now = time.clock()
ms_since_last_clock = int( 1000.0 * ( now - self._last_clock ) )
if self._yet_to_draw_initial_frame or ms_since_last_clock > self._image_container.GetDuration( self._current_frame_index ):
if self._yet_to_draw_initial_frame: next_frame = 0
else:
num_frames = self._media.GetNumFrames()
if num_frames is None: next_frame = 0
else: next_frame = ( self._current_frame_index + 1 ) % num_frames
if self._image_container.HasFrame( next_frame ):
self._current_frame_index = next_frame
self._last_clock = now
self._Draw()
self._yet_to_draw_initial_frame = False
class PDFButton( wx.Button ):
def __init__( self, parent, hash, size ):
wx.Button.__init__( self, parent, label = 'launch pdf', size = size )
self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) )
self._hash = hash
self.Bind( wx.EVT_BUTTON, self.EventButton )
def EventButton( self, event ):
path = CC.GetFilePath( self._hash, HC.APPLICATION_PDF )
# os.system( 'start ' + path )
subprocess.call( 'start "" "' + path + '"', shell = True )
if self.IsShown() and self._image_container.IsRendered(): self._Draw()

View File

@ -5,6 +5,7 @@ import HydrusEncryption
import HydrusExceptions
import HydrusFileHandling
import HydrusTags
import HydrusThreading
import ClientConstants as CC
import ClientGUICommon
import collections
@ -2125,7 +2126,7 @@ class DialogInputLocalFiles( Dialog ):
self._job_key = HC.JobKey()
threading.Thread( target = self.THREADParseImportablePaths, args = ( paths, self._job_key ) ).start()
HydrusThreading.CallToThread( self.THREADParseImportablePaths, paths, self._job_key )
self.SetGaugeInfo( None, None, '' )
@ -4726,7 +4727,7 @@ class DialogSelectYoutubeURL( Dialog ):
message = HC.MessageGauge( HC.MESSAGE_TYPE_GAUGE, url_string )
threading.Thread( target = HydrusDownloading.THREADDownloadURL, args = ( message, url, url_string ) ).start()
HydrusThreading.CallToThread( HydrusDownloading.THREADDownloadURL, message, url, url_string )
HC.pubsub.pub( 'message', message )

View File

@ -4731,6 +4731,8 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
info[ 'port' ] = port
if service_type in HC.REPOSITORIES: info[ 'paused' ] = False
if service_type == HC.LOCAL_RATING_LIKE:
info[ 'like' ] = 'like'

View File

@ -4,6 +4,7 @@ import HydrusDownloading
import HydrusExceptions
import HydrusFileHandling
import HydrusImageHandling
import HydrusThreading
import ClientConstants as CC
import ClientConstantsMessages
import ClientGUICommon
@ -1119,7 +1120,7 @@ class ManagementPanelDumper( ManagementPanel ):
self._actually_dumping = True
threading.Thread( target = self._THREADDoDump, args = ( hash, post_field_info, headers, body ) ).start()
HydrusThreading.CallToThread( self._THREADDoDump, hash, post_field_info, headers, body )
except Exception as e:

View File

@ -736,8 +736,6 @@ class MediaSingleton( Media ):
def HasInbox( self ): return self._media_result.GetInbox()
def IsAnimated( self ): return self._media_result.IsAnimated()
def IsCollection( self ): return False
def IsImage( self ): return HC.IsImage( self._media_result.GetMime() )

View File

@ -355,7 +355,7 @@ class PageImport( PageWithMedia ):
self._import_controller = HydrusDownloading.ImportController( import_args_generator_factory, import_queue_generator_factory, page_key = self._page_key )
self._import_controller.StartThread()
self._import_controller.StartDaemon()
def _PauseControllers( self ):

View File

@ -64,7 +64,7 @@ options = {}
# Misc
NETWORK_VERSION = 13
SOFTWARE_VERSION = 115
SOFTWARE_VERSION = 116
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -303,7 +303,7 @@ NOISY_MIMES = tuple( [ APPLICATION_FLASH ] + list( AUDIO ) + list( VIDEO ) )
ARCHIVES = ( APPLICATION_ZIP, APPLICATION_HYDRUS_ENCRYPTED_ZIP )
MIMES_WITH_THUMBNAILS = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP )
MIMES_WITH_THUMBNAILS = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP, VIDEO_WEBM )
# mp3 header is complicated
@ -1676,98 +1676,6 @@ class ContentUpdate():
def ToTuple( self ): return ( self._data_type, self._action, self._row )
class DAEMON( threading.Thread ):
def __init__( self, name, callable, period = 1200 ):
threading.Thread.__init__( self, name = name )
self._name = name
self._callable = callable
self._period = period
self._event = threading.Event()
pubsub.sub( self, 'shutdown', 'shutdown' )
def shutdown( self ): self._event.set()
class DAEMONQueue( DAEMON ):
def __init__( self, name, callable, queue_topic, period = 10 ):
DAEMON.__init__( self, name, callable, period )
self._queue = Queue.Queue()
self._queue_topic = queue_topic
self.start()
pubsub.sub( self, 'put', queue_topic )
def put( self, data ): self._queue.put( data )
def run( self ):
time.sleep( 3 )
while True:
while self._queue.qsize() == 0:
if shutdown: return
self._event.wait( self._period )
self._event.clear()
items = []
while self._queue.qsize() > 0: items.append( self._queue.get() )
try: self._callable( items )
except Exception as e: ShowException( e )
class DAEMONWorker( DAEMON ):
def __init__( self, name, callable, topics = [], period = 1200, init_wait = 3 ):
DAEMON.__init__( self, name, callable, period )
self._topics = topics
self._init_wait = init_wait
self.start()
for topic in topics: pubsub.sub( self, 'set', topic )
def run( self ):
self._event.wait( self._init_wait )
while True:
if shutdown: return
try: self._callable()
except Exception as e: ShowException( e )
if shutdown: return
self._event.wait( self._period )
self._event.clear()
def set( self, *args, **kwargs ): self._event.set()
class JobDatabase():
yaml_tag = u'!JobDatabase'

View File

@ -4,6 +4,7 @@ import httplib
import HydrusConstants as HC
import HydrusExceptions
import HydrusNetworking
import HydrusThreading
import json
import lxml
import os
@ -1484,7 +1485,7 @@ class ImportController():
args_generator = self._import_args_generator_factory( self._import_job_key, item )
threading.Thread( target = args_generator, name = 'Generate Import Args' ).start()
HydrusThreading.CallToThread( args_generator )
else:
@ -1518,7 +1519,8 @@ class ImportController():
queue_generator = self._import_queue_generator_factory( self._import_queue_job_key, item )
threading.Thread( target = queue_generator, name = 'Generate Import Items' ).start()
# make it a daemon, not a thread job, as it has a loop!
threading.Thread( target = queue_generator ).start()
@ -1535,10 +1537,7 @@ class ImportController():
def StartThread( self ):
threading.Thread( target = self.MainLoop ).start()
def StartDaemon( self ): threading.Thread( target = self.MainLoop ).start()
class ImportQueueGenerator():

View File

@ -1,3 +1,4 @@
import cv2
import hashlib
import hsaudiotag
import hsaudiotag.auto
@ -16,11 +17,61 @@ import threading
import time
import traceback
import wx
import cStringIO
# Mime
#magic_mime = magic.Magic( HC.STATIC_DIR + os.path.sep + 'magic.mime', HC.STATIC_DIR + os.path.sep + 'magic.mime.cache' )
def GenerateThumbnail( path, dimensions = HC.UNSCALED_THUMBNAIL_DIMENSIONS ):
mime = GetMime( path )
if mime in HC.IMAGES:
pil_image = HydrusImageHandling.GeneratePILImage( path )
HydrusImageHandling.EfficientlyThumbnailPILImage( pil_image, dimensions )
f = cStringIO.StringIO()
if pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ):
pil_image.save( f, 'PNG', transparency = pil_image.info[ 'transparency' ] )
elif pil_image.mode == 'RGBA': pil_image.save( f, 'PNG' )
else:
pil_image = pil_image.convert( 'RGB' )
pil_image.save( f, 'JPEG', quality=92 )
f.seek( 0 )
thumbnail = f.read()
f.close()
else:
cv_video = cv2.VideoCapture( path )
cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
( retval, cv_image ) = cv_video.read()
if not retval: raise Exception( 'Could not read first frame of ' + HC.u( path ) + ' to create thumbnail!' )
cv_image = HydrusImageHandling.EfficientlyThumbnailCVImage( cv_image, dimensions )
( retval, thumbnail ) = cv2.imencode( '.jpg', cv_image, ( cv2.cv.CV_IMWRITE_JPEG_QUALITY, 92 ) )
if not retval: raise Exception( 'Could not export thumbnail for ' + HC.u( path ) + '!' )
return thumbnail
def GetFileInfo( path, hash ):
info = os.lstat( path )

View File

@ -5,6 +5,7 @@ import cv
import cv2
import HydrusConstants as HC
import HydrusExceptions
import HydrusThreading
import lz4
import os
from PIL import Image as PILImage
@ -36,67 +37,52 @@ def ConvertToPngIfBmp( path ):
os.remove( temp_path )
def EfficientlyResizeCVImage( cv_image, ( x, y ) ):
def EfficientlyResizeCVImage( cv_image, ( target_x, target_y ) ):
( im_y, im_x, depth ) = cv_image.shape
if x >= im_x and y >= im_y: return cv_image
if target_x >= im_x and target_y >= im_y: return cv_image
result = cv_image
# this seems to slow things down a lot, at least for cv!
#if im_x > 2 * x and im_y > 2 * y: result = cv2.resize( cv_image, ( 2 * x, 2 * y ), interpolation = cv2.INTER_NEAREST )
#if im_x > 2 * target_x and im_y > 2 * target_y: result = cv2.resize( cv_image, ( 2 * target_x, 2 * target_y ), interpolation = cv2.INTER_NEAREST )
return cv2.resize( result, ( x, y ), interpolation = cv2.INTER_AREA )
return cv2.resize( result, ( target_x, target_y ), interpolation = cv2.INTER_AREA )
def EfficientlyResizePILImage( pil_image, ( x, y ) ):
def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ):
( im_x, im_y ) = pil_image.size
if x >= im_x and y >= im_y: return pil_image
if target_x >= im_x and target_y >= im_y: return pil_image
if pil_image.mode == 'RGB': # low quality resize screws up alpha channel!
if im_x > 2 * x and im_y > 2 * y: pil_image.thumbnail( ( 2 * x, 2 * y ), PILImage.NEAREST )
#if pil_image.mode == 'RGB': # low quality resize screws up alpha channel!
#
# if im_x > 2 * target_x and im_y > 2 * target_y: pil_image.thumbnail( ( 2 * target_x, 2 * target_y ), PILImage.NEAREST )
#
return pil_image.resize( ( x, y ), PILImage.ANTIALIAS )
return pil_image.resize( ( target_x, target_y ), PILImage.ANTIALIAS )
def EfficientlyThumbnailPILImage( pil_image, ( x, y ) ):
def EfficientlyThumbnailCVImage( cv_image, ( target_x, target_y ) ):
( im_y, im_x, depth ) = cv_image.shape
if target_x >= im_x and target_y >= im_y: return cv_image
( target_x, target_y ) = GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) )
return cv2.resize( cv_image, ( target_x, target_y ), interpolation = cv2.INTER_AREA )
def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ):
( im_x, im_y ) = pil_image.size
if pil_image.mode == 'RGB': # low quality resize screws up alpha channel!
if im_x > 2 * x or im_y > 2 * y: pil_image.thumbnail( ( 2 * x, 2 * y ), PILImage.NEAREST )
if im_x > 2 * target_x or im_y > 2 * target_y: pil_image.thumbnail( ( 2 * target_x, 2 * target_y ), PILImage.NEAREST )
pil_image.thumbnail( ( x, y ), PILImage.ANTIALIAS )
def GenerateAnimatedFrame( pil_image, target_resolution, canvas ):
if 'duration' not in pil_image.info: duration = 40 # 25 fps default when duration is missing or too funky to extract. most stuff looks ok at this.
else:
duration = pil_image.info[ 'duration' ]
if duration == 0: duration = 40
current_frame = EfficientlyResizePILImage( pil_image, target_resolution )
if pil_image.mode == 'P' and 'transparency' in pil_image.info:
# I think gif problems are around here somewhere; the transparency info is not converted to RGBA properly, so it starts drawing colours when it should draw nothing
current_frame = current_frame.convert( 'RGBA' )
if canvas is None: canvas = current_frame
else: canvas.paste( current_frame, None, current_frame ) # yeah, use the rgba image as its own mask, wut.
else: canvas = current_frame
return ( canvas, duration )
pil_image.thumbnail( ( target_x, target_y ), PILImage.ANTIALIAS )
def GenerateCVImage( path ):
@ -238,34 +224,6 @@ def GeneratePerceptualHash( path ):
def GeneratePILImage( path ): return PILImage.open( path )
def GenerateThumbnail( path, dimensions = HC.UNSCALED_THUMBNAIL_DIMENSIONS ):
pil_image = GeneratePILImage( path )
EfficientlyThumbnailPILImage( pil_image, dimensions )
f = cStringIO.StringIO()
if pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ):
pil_image.save( f, 'PNG', transparency = pil_image.info[ 'transparency' ] )
elif pil_image.mode == 'RGBA': pil_image.save( f, 'PNG' )
else:
pil_image = pil_image.convert( 'RGB' )
pil_image.save( f, 'JPEG', quality=92 )
f.seek( 0 )
thumbnail = f.read()
f.close()
return thumbnail
def GetFrameDurations( path ):
pil_image_for_duration = GeneratePILImage( path )
@ -359,26 +317,23 @@ def GetResolutionAndNumFrames( path ):
return ( ( x, y ), num_frames )
def RenderImage( media, target_resolution = None ):
def GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) ):
if target_resolution is None: target_resolution = media.GetResolution()
im_x = float( im_x )
im_y = float( im_y )
if media.IsAnimated():
image_container = ImageContainerAnimated( media, target_resolution )
renderer = AnimatedFrameRenderer( image_container, media, target_resolution )
else:
image_container = ImageContainerStatic( media, target_resolution )
renderer = StaticFrameRenderer( image_container, media, target_resolution )
target_x = float( target_x )
target_y = float( target_y )
threading.Thread( target = renderer.THREADRender ).start()
x_ratio = im_x / target_x
y_ratio = im_y / target_y
return image_container
ratio_to_use = max( x_ratio, y_ratio )
target_x = int( im_x / ratio_to_use )
target_y = int( im_y / ratio_to_use )
return ( target_x, target_y )
class FrameRenderer():
@ -394,138 +349,239 @@ class FrameRenderer():
self._path = CC.GetFilePath( hash, mime )
''' # old pil code
def _GetCurrentFramePIL( pil_image, target_resolution, canvas ):
current_frame = EfficientlyResizePILImage( pil_image, target_resolution )
if pil_image.mode == 'P' and 'transparency' in pil_image.info:
# I think gif problems are around here somewhere; the transparency info is not converted to RGBA properly, so it starts drawing colours when it should draw nothing
current_frame = current_frame.convert( 'RGBA' )
if canvas is None: canvas = current_frame
else: canvas.paste( current_frame, None, current_frame ) # yeah, use the rgba image as its own mask, wut.
else: canvas = current_frame
return canvas
def _GetFramePIL( self, index ):
pil_image = self._image_object
pil_image.seek( index )
canvas = self._GetCurrentFramePIL( pil_image, self._target_resolution, canvas )
return GenerateHydrusBitmapFromPILImage( canvas )
def _GetFramesPIL( self ):
pil_image = self._image_object
canvas = None
global_palette = pil_image.palette
dirty = pil_image.palette.dirty
mode = pil_image.palette.mode
rawmode = pil_image.palette.rawmode
# believe it or not, doing this actually fixed a couple of gifs!
pil_image.seek( 1 )
pil_image.seek( 0 )
while True:
canvas = self._GetCurrentFramePIL( pil_image, self._target_resolution, canvas )
yield GenerateHydrusBitmapFromPILImage( canvas )
try:
pil_image.seek( pil_image.tell() + 1 )
if pil_image.palette == global_palette: # for some reason, when we fall back to global palette (no local-frame palette), we reset bunch of important variables!
pil_image.palette.dirty = dirty
pil_image.palette.mode = mode
pil_image.palette.rawmode = rawmode
except: break
'''
# the cv code was initially written by @fluffy_cub
class AnimatedFrameRenderer( FrameRenderer ):
def _GetFramesCV( self ):
def __init__( self, image_container, media, target_resolution ):
# this code initially written by @fluffy_cub
FrameRenderer.__init__( self, image_container, media, target_resolution )
frame_durations = GetFrameDurations( self._path )
self._lock = threading.Lock()
cv_video = cv2.VideoCapture( self._path )
cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
self._cv_video = cv2.VideoCapture( self._path )
self._cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
self._current_index = 0
self._last_index_rendered = -1
self._render_to_index = -1
def _GetCurrentFrame( self ):
( retval, cv_image ) = self._cv_video.read()
self._last_index_rendered = self._current_index
num_frames = self._media.GetNumFrames()
if not retval:
raise HydrusExceptions.CantRenderWithCVException( 'CV could not render frame ' + HC.u( self._current_index ) + '.' )
self._current_index = ( self._current_index + 1 ) % num_frames
if self._current_index == 0 and self._last_index_rendered != 0:
if self._media.GetMime() == HC.IMAGE_GIF: self._RewindGIF()
else: self._cv_video.set( cv2.cv.CV_CAP_PROP_POS_FRAMES, 0.0 )
return cv_image
def _RenderCurrentFrame( self ):
cv_image = self._GetCurrentFrame()
cv_image = EfficientlyResizeCVImage( cv_image, self._target_resolution )
cv_image = cv2.cvtColor( cv_image, cv2.COLOR_BGR2RGB )
return GenerateHydrusBitmapFromCVImage( cv_image )
def _RenderFrames( self ):
no_frames_yet = True
while True:
( retval, cv_image ) = cv_video.read()
if not retval:
try:
if no_frames_yet: raise HydrusExceptions.CantRenderWithCVException()
else: break
else:
yield self._RenderCurrentFrame()
no_frames_yet = False
cv_image = EfficientlyResizeCVImage( cv_image, self._target_resolution )
except HydrusExceptions.CantRenderWithCVException:
cv_image = cv2.cvtColor( cv_image, cv2.COLOR_BGR2RGB )
try: duration = frame_durations.pop( 0 )
except: duration = 40
yield ( GenerateHydrusBitmapFromCVImage( cv_image ), duration )
if no_frames_yet: raise
else: break
def _GetFramesPIL( self ):
def _RewindGIF( self ):
pil_image = GeneratePILImage( self._path )
self._cv_video.release()
self._cv_video.open( self._path )
canvas = None
self._current_index = 0
global_palette = pil_image.palette
def SetRenderToPosition( self, index ):
dirty = pil_image.palette.dirty
mode = pil_image.palette.mode
rawmode = pil_image.palette.rawmode
with self._lock:
if self._render_to_index != index:
self._render_to_index = index
HydrusThreading.CallToThread( self.THREADDoWork )
# believe it or not, doing this actually fixed a couple of gifs!
pil_image.seek( 1 )
pil_image.seek( 0 )
def SetPosition( self, index ):
with self._lock:
if self._media.GetMime() == HC.IMAGE_GIF:
if index == self._current_index: return
elif index < self._current_index: self._RewindGIF()
while self._current_index < index: self._GetCurrentFrame()
else:
self._cv_video.set( cv2.cv.CV_CAP_PROP_POS_FRAMES, index )
self._render_to_index = index
def THREADDoWork( self ):
while True:
( canvas, duration ) = GenerateAnimatedFrame( pil_image, self._target_resolution, canvas )
time.sleep( 0.00001 ) # thread yield
yield ( GenerateHydrusBitmapFromPILImage( canvas ), duration )
try:
with self._lock:
pil_image.seek( pil_image.tell() + 1 )
if pil_image.palette == global_palette: # for some reason, when we fall back to global palette (no local-frame palette), we reset bunch of important variables!
if self._last_index_rendered != self._render_to_index:
pil_image.palette.dirty = dirty
pil_image.palette.mode = mode
pil_image.palette.rawmode = rawmode
index = self._current_index
except: break
frame = self._RenderCurrentFrame()
wx.CallAfter( self._image_container.AddFrame, index, frame )
else: break
def GetFrames( self ):
try:
for ( frame, duration ) in self._GetFramesCV(): yield ( frame, duration )
except HydrusExceptions.CantRenderWithCVException:
for ( frame, duration ) in self._GetFramesPIL(): yield ( frame, duration )
def Render( self ):
for ( frame, duration ) in self.GetFrames(): self._image_container.AddFrame( frame, duration )
def THREADRender( self ):
time.sleep( 0.00001 ) # thread yield
for ( frame, duration ) in self.GetFrames(): wx.CallAfter( self._image_container.AddFrame, frame, duration )
HC.pubsub.pub( 'finished_rendering', self._image_container.GetKey() )
class StaticFrameRenderer( FrameRenderer ):
def _GetFrame( self ):
try: frame = self._GetFrameCV()
except: frame = self._GetFramePIL()
return frame
try:
cv_image = GenerateCVImage( self._path )
resized_cv_image = EfficientlyResizeCVImage( cv_image, self._target_resolution )
return GenerateHydrusBitmapFromCVImage( resized_cv_image )
except:
pil_image = GeneratePILImage( self._path )
resized_pil_image = EfficientlyResizePILImage( pil_image, self._target_resolution )
return GenerateHydrusBitmapFromPILImage( resized_pil_image )
def _GetFrameCV( self ):
cv_image = GenerateCVImage( self._path )
return GenerateHydrusBitmapFromCVImage( EfficientlyResizeCVImage( cv_image, self._target_resolution ) )
def _GetFramePIL( self ):
pil_image = GeneratePILImage( self._path )
return GenerateHydrusBitmapFromPILImage( EfficientlyResizePILImage( pil_image, self._target_resolution ) )
def Render( self ): self._image_container.AddFrame( self._GetFrame() )
def Render( self ): self._image_container.SetFrame( self._GetFrame() )
def THREADRender( self ):
time.sleep( 0.00001 ) # thread yield
wx.CallAfter( self._image_container.AddFrame, self._GetFrame() )
wx.CallAfter( self._image_container.SetFrame, self._GetFrame() )
HC.pubsub.pub( 'finished_rendering', self._image_container.GetKey() )
@ -553,11 +609,18 @@ class HydrusBitmap():
class ImageContainer():
def __init__( self, media, target_resolution ):
def __init__( self, media, target_resolution = None ):
if target_resolution is None: target_resolution = media.GetResolution()
self._media = media
self._target_resolution = target_resolution
hash = self._media.GetHash()
mime = self._media.GetMime()
self._path = CC.GetFilePath( hash, mime )
( original_width, original_height ) = self._media.GetResolution()
( my_width, my_height ) = target_resolution
@ -574,29 +637,53 @@ class ImageContainer():
class ImageContainerAnimated( ImageContainer ):
def __init__( self, media, target_resolution ):
NUM_FRAMES_BACKWARDS = 30
NUM_FRAMES_FORWARDS = 15
def __init__( self, media, target_resolution = None, init_position = 0 ):
ImageContainer.__init__( self, media, target_resolution )
self._frames = []
self._durations = []
self._frames = {}
self._last_index_asked_for = 0
self._durations = GetFrameDurations( self._path )
self._renderer = AnimatedFrameRenderer( self, self._media, self._target_resolution )
self.SetPosition( init_position )
def AddFrame( self, frame, duration = None ):
def _MaintainBuffer( self ):
self._frames.append( frame )
num_frames = self.GetNumFrames()
if duration is not None: self._durations.append( duration )
if num_frames < self.NUM_FRAMES_BACKWARDS + 1 + self.NUM_FRAMES_FORWARDS: render_to_index = num_frames - 1
else: render_to_index = self._last_index_asked_for + self.NUM_FRAMES_FORWARDS % num_frames
self._renderer.SetRenderToPosition( render_to_index )
indices_i_want = { ( self._last_index_asked_for + i ) % num_frames for i in range( -self.NUM_FRAMES_BACKWARDS, self.NUM_FRAMES_FORWARDS + 1 ) }
current_indices = set( self._frames.keys() )
deletees = current_indices.difference( indices_i_want )
for i in deletees: del self._frames[ i ]
def AddFrame( self, index, frame ): self._frames[ index ] = frame
def GetDuration( self, index ): return self._durations[ index ]
def GetEstimatedMemoryFootprint( self ): return sum( [ frame.GetEstimatedMemoryFootprint() for frame in self._frames ] )
def GetFrame( self, index = None ):
def GetFrame( self, index ):
if index is None: return self._frames[ 0 ]
else: return self._frames[ index ]
frame = self._frames[ index ]
self._last_index_asked_for = index
self._MaintainBuffer()
return frame
def GetHash( self ): return self._media.GetHash()
@ -605,8 +692,6 @@ class ImageContainerAnimated( ImageContainer ):
def GetNumFrames( self ): return self._media.GetNumFrames()
def GetNumFramesRendered( self ): return len( self._frames )
def GetResolution( self ): return self._media.GetResolution()
def GetSize( self ): return self._target_resolution
@ -615,33 +700,39 @@ class ImageContainerAnimated( ImageContainer ):
def GetZoom( self ): return self._zoom
def HasFrame( self, index = None ):
if index is None: index = 0
return len( self._frames ) > index
def IsAnimated( self ): return True
def IsFinishedRendering( self ): return len( self._frames ) == self.GetNumFrames()
def HasFrame( self, index ): return index in self._frames
def IsScaled( self ): return self._zoom != 1.0
def SetPosition( self, index ):
num_frames = self.GetNumFrames()
self._last_index_asked_for = index
pre_index = max( 0, index - self.NUM_FRAMES_BACKWARDS ) % num_frames
self._renderer.SetPosition( pre_index )
self._MaintainBuffer()
class ImageContainerStatic( ImageContainer ):
def __init__( self, media, target_resolution ):
def __init__( self, media, target_resolution = None ):
ImageContainer.__init__( self, media, target_resolution )
self._frame = None
def AddFrame( self, frame, duration = None ): self._frame = frame
renderer = StaticFrameRenderer( self, self._media, self._target_resolution )
HydrusThreading.CallToThread( renderer.THREADRender )
def GetEstimatedMemoryFootprint( self ): return self._frame.GetEstimatedMemoryFootprint()
def GetFrame( self, index = None ): return self._frame
def GetFrame( self ): return self._frame
def GetHash( self ): return self._media.GetHash()
@ -655,11 +746,9 @@ class ImageContainerStatic( ImageContainer ):
def GetZoom( self ): return self._zoom
def HasFrame( self, index = None ): return self._frame is not None
def IsAnimated( self ): return False
def IsFinishedRendering( self ): return len( self._frames ) == 1
def IsRendered( self ): return self._frame is not None
def IsScaled( self ): return self._zoom != 1.0
def SetFrame( self, frame ): self._frame = frame

View File

@ -129,7 +129,7 @@ class HTTPConnectionManager():
self._lock = threading.Lock()
threading.Thread( target = self.MaintainConnections, name = 'Maintain Connections' ).start()
threading.Thread( target = self.DAEMONMaintainConnections, name = 'Maintain Connections' ).start()
def _DoRequest( self, location, method, path_and_query, request_headers, body, follow_redirects = True, report_hooks = [], response_to_path = False, num_redirects_permitted = 4, long_timeout = False ):
@ -203,7 +203,7 @@ class HTTPConnectionManager():
else: return response
def MaintainConnections( self ):
def DAEMONMaintainConnections( self ):
while True:

View File

@ -259,7 +259,7 @@ def ParseFileArguments( path ):
if mime in HC.IMAGES:
try: thumbnail = HydrusImageHandling.GenerateThumbnail( path )
try: thumbnail = HydrusFileHandling.GenerateThumbnail( path )
except: raise HydrusExceptions.ForbiddenException( 'Could not generate thumbnail from that file.' )
args[ 'thumbnail' ] = thumbnail

153
include/HydrusThreading.py Normal file
View File

@ -0,0 +1,153 @@
import collections
import HydrusConstants as HC
import itertools
import os
import Queue
import random
import threading
import time
import traceback
import wx
class DAEMON( threading.Thread ):
def __init__( self, name, period = 1200 ):
threading.Thread.__init__( self, name = name )
self._name = name
self._event = threading.Event()
HC.pubsub.sub( self, 'shutdown', 'shutdown' )
def shutdown( self ): self._event.set()
class DAEMONQueue( DAEMON ):
def __init__( self, name, callable, queue_topic, period = 10 ):
DAEMON.__init__( self, name )
self._callable = callable
self._queue = Queue.Queue()
self._queue_topic = queue_topic
self._period = period
HC.pubsub.sub( self, 'put', queue_topic )
self.start()
def put( self, data ): self._queue.put( data )
def run( self ):
time.sleep( 3 )
while True:
while self._queue.empty():
if HC.shutdown: return
self._event.wait( self._period )
self._event.clear()
items = []
while not self._queue.empty(): items.append( self._queue.get() )
try: self._callable( items )
except Exception as e: HC.ShowException( e )
class DAEMONWorker( DAEMON ):
def __init__( self, name, callable, topics = [], period = 1200, init_wait = 3 ):
DAEMON.__init__( self, name )
self._callable = callable
self._topics = topics
self._period = period
self._init_wait = init_wait
for topic in topics: HC.pubsub.sub( self, 'set', topic )
self.start()
def run( self ):
self._event.wait( self._init_wait )
while True:
if HC.shutdown: return
try: self._callable()
except Exception as e: HC.ShowException( e )
if HC.shutdown: return
self._event.wait( self._period )
self._event.clear()
def set( self, *args, **kwargs ): self._event.set()
class DAEMONCallToThread( DAEMON ):
def __init__( self ):
DAEMON.__init__( self, 'CallToThread' )
self._queue = Queue.Queue()
self.start()
def put( self, callable, *args, **kwargs ):
self._queue.put( ( callable, args, kwargs ) )
self._event.set()
def run( self ):
while True:
while self._queue.empty():
if HC.shutdown: return
self._event.wait( 1200 )
self._event.clear()
( callable, args, kwargs ) = self._queue.get()
try: callable( *args, **kwargs )
except Exception as e: HC.ShowException( e )
call_to_threads = [ DAEMONCallToThread() for i in range( 10 ) ]
def CallToThread( callable, *args, **kwargs ):
call_to_thread = random.choice( call_to_threads )
while call_to_thread == threading.current_thread: call_to_thread = random.choice( call_to_threads )
call_to_thread.put( callable, *args, **kwargs )

View File

@ -2,6 +2,7 @@ import httplib
import HydrusConstants as HC
import HydrusServer
import HydrusSessions
import HydrusThreading
import ServerConstants as SC
import ServerDB
import os
@ -180,14 +181,14 @@ class Controller( wx.App ):
def StartDaemons( self ):
HC.DAEMONQueue( 'FlushRequestsMade', ServerDB.DAEMONFlushRequestsMade, 'request_made', period = 60 )
HydrusThreading.DAEMONQueue( 'FlushRequestsMade', ServerDB.DAEMONFlushRequestsMade, 'request_made', period = 60 )
HC.DAEMONWorker( 'CheckMonthlyData', ServerDB.DAEMONCheckMonthlyData, period = 3600 )
HC.DAEMONWorker( 'ClearBans', ServerDB.DAEMONClearBans, period = 3600 )
HC.DAEMONWorker( 'DeleteOrphans', ServerDB.DAEMONDeleteOrphans, period = 86400 )
HC.DAEMONWorker( 'GenerateUpdates', ServerDB.DAEMONGenerateUpdates, period = 1200 )
HC.DAEMONWorker( 'CheckDataUsage', ServerDB.DAEMONCheckDataUsage, period = 86400 )
HC.DAEMONWorker( 'UPnP', ServerDB.DAEMONUPnP, ( 'notify_new_options', ), period = 43200 )
HydrusThreading.DAEMONWorker( 'CheckMonthlyData', ServerDB.DAEMONCheckMonthlyData, period = 3600 )
HydrusThreading.DAEMONWorker( 'ClearBans', ServerDB.DAEMONClearBans, period = 3600 )
HydrusThreading.DAEMONWorker( 'DeleteOrphans', ServerDB.DAEMONDeleteOrphans, period = 86400 )
HydrusThreading.DAEMONWorker( 'GenerateUpdates', ServerDB.DAEMONGenerateUpdates, period = 1200 )
HydrusThreading.DAEMONWorker( 'CheckDataUsage', ServerDB.DAEMONCheckDataUsage, period = 86400 )
HydrusThreading.DAEMONWorker( 'UPnP', ServerDB.DAEMONUPnP, ( 'notify_new_options', ), period = 43200 )
def Write( self, action, *args, **kwargs ):

View File

@ -152,8 +152,9 @@ if __name__ == '__main__':
app = App()
raw_input()
HC.shutdown = True
HC.pubsub.WXpubimmediate( 'shutdown' )
raw_input()