diff options
author | Score_Under | 2015-06-11 18:00:28 +0100 |
---|---|---|
committer | Score_Under | 2015-06-11 18:00:28 +0100 |
commit | fbcafe875523c82a7ce6539a3e3048be33502d8d (patch) | |
tree | d14eeb50108dc94c48a3672a17674238b39f3714 | |
download | aur-fbcafe875523c82a7ce6539a3e3048be33502d8d.tar.gz |
Initial import from AUR 3
-rw-r--r-- | .SRCINFO | 44 | ||||
-rw-r--r-- | PKGBUILD | 120 | ||||
-rw-r--r-- | cython-workarounds.patch | 2483 | ||||
-rw-r--r-- | hydrus-client | 2 | ||||
-rw-r--r-- | hydrus-server | 2 | ||||
-rw-r--r-- | hydrus.desktop | 9 | ||||
-rw-r--r-- | hydrus.install | 11 | ||||
-rw-r--r-- | paths-in-opt.patch | 121 | ||||
-rw-r--r-- | running-the-server.patch | 26 |
9 files changed, 2818 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO new file mode 100644 index 000000000000..b4b392f10ddf --- /dev/null +++ b/.SRCINFO @@ -0,0 +1,44 @@ +pkgbase = hydrus + pkgdesc = Danbooru-like image tagging and searching system for the desktop + pkgver = 160 + pkgrel = 1 + url = http://hydrusnetwork.github.io/hydrus/ + install = hydrus.install + arch = any + license = WTFPL + makedepends = git + depends = python2 + depends = wxpython + depends = opencv + depends = python2-beautifulsoup4 + depends = python2-yaml + depends = hsaudiotag + depends = python2-pypdf2 + depends = python2-pafy + depends = python2-lz4 + depends = python2-numpy + depends = python2-twisted + depends = python2-pillow + depends = python2-potr + depends = python2-flvlib + depends = python2-socks + optdepends = ffmpeg: show duration and other information on video thumbnails + optdepends = miniupnpc: automatic port forwarding + options = !strip + source = hydrus::git+https://github.com/hydrusnetwork/hydrus.git#commit=54238debc3004889ae5b5777076838bece5a0d57 + source = paths-in-opt.patch + source = running-the-server.patch + source = cython-workarounds.patch + source = hydrus-client + source = hydrus-server + source = hydrus.desktop + sha256sums = SKIP + sha256sums = 039fc987703dd6ea37cbef0831112d03ebf3d1e55fa38271f4d13b6fa686c5b0 + sha256sums = 9218f8b48ecf91075132914693dbc3afbd8bf0bcd69989be1c044eacb9191da3 + sha256sums = 5ee65187005e2807c98e385da122ff63c9ac856c35b4875989aad29d0f314d25 + sha256sums = b2bf66b1068969e9598742d5c128cb04fd609512b0cff0ad5e25ecb6cdd35678 + sha256sums = ac7254e3cdb359ebae302655b72b9f74b85d9e817c326fa28173791b3fb4f114 + sha256sums = 9ba3942ac1a37f6b39c98ae6592573402bf08d8376f64554d0696c0fed6fd0e2 + +pkgname = hydrus + diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 000000000000..86b5d9f26a2a --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,120 @@ +# Maintainer: Score_Under <seejay 11@gmail com> +# Configuration: +##### + +build_pyc=false +build_pyo=true +build_cython=false +remove_py=false +remove_help=false + +##### +$build_cython || options=(!strip) # Don't strip libs because there aren't any; this wastes time +DOC_DIRS=(opt/hydrus/help) + +pkgname=hydrus +pkgver=160 +pkgrel=1 +pkgdesc="Danbooru-like image tagging and searching system for the desktop" +arch=(any) +license=(WTFPL) +url=http://hydrusnetwork.github.io/hydrus/ +depends=(python2 wxpython opencv python2-beautifulsoup4 python2-yaml + hsaudiotag python2-pypdf2 python2-pafy python2-lz4 python2-numpy + python2-twisted python2-pillow python2-potr python2-flvlib python2-socks) +makedepends=(git) +$build_cython && makedepends+=(cython2 parallel) +optdepends=('ffmpeg: show duration and other information on video thumbnails' + 'miniupnpc: automatic port forwarding') +source=("${pkgname}::git+https://github.com/hydrusnetwork/${pkgname}.git#commit=54238debc3004889ae5b5777076838bece5a0d57" + paths-in-opt.patch + running-the-server.patch + cython-workarounds.patch + hydrus-client + hydrus-server + hydrus.desktop) +sha256sums=('SKIP' + '039fc987703dd6ea37cbef0831112d03ebf3d1e55fa38271f4d13b6fa686c5b0' + '9218f8b48ecf91075132914693dbc3afbd8bf0bcd69989be1c044eacb9191da3' + '5ee65187005e2807c98e385da122ff63c9ac856c35b4875989aad29d0f314d25' + 'b2bf66b1068969e9598742d5c128cb04fd609512b0cff0ad5e25ecb6cdd35678' + 'ac7254e3cdb359ebae302655b72b9f74b85d9e817c326fa28173791b3fb4f114' + '9ba3942ac1a37f6b39c98ae6592573402bf08d8376f64554d0696c0fed6fd0e2') +install=hydrus.install + +prepare() { + cd "$pkgname" + patch -Np1 -i ../paths-in-opt.patch + patch -Np1 -i ../running-the-server.patch + + # Cython patches if applicable + $build_cython && patch -Np1 -i ../cython-workarounds.patch + + # Fix permissions + chmod a-x include/*.py + + # Remove strange file + rm -f "include/pyconfig.h" + + # Remove unit tests + rm -f "include/Test"*.py + rm -rf "static/testing" +} + +build() { + cd "$pkgname" + + # Compile .py files + $build_pyc && python2 -m compileall . + $build_pyo && python2 -OO -m compileall . + + if $build_cython; then + cd include + local -a files_to_compile + files_to_compile=() + for file in *.py; do + # ClientGUICommon.py and ClientController.py have problems when built under Cython + [ "$file" == ClientGUICommon.py -o "$file" == ClientController.py ] && continue + files_to_compile+=("${file%.py}") + done + + cython2 -2 --fast-fail -Werror "${files_to_compile[@]/%/.py}" + parallel --bar '${CCLD:-gcc} -Os -s -fpic -shared -o {}.so {}.c $(python2-config --libs --includes) $LDFLAGS' ::: "${files_to_compile[@]}" + rm -f -- "${files_to_compile/%/.c}" + fi +} + +package() { + cd "$pkgname" + + # Create /opt/hydrus and copy hydrus sources to there + install -m755 -d "${pkgdir}/opt/hydrus" + cp -r help include static client.pyw server.pyw "${pkgdir}/opt/hydrus/" + + # Remove .py files + $remove_py && find "${pkgdir}/opt/hydrus" -name '*.py' -delete + + # Remove help + $remove_help && rm -rf "${pkgdir}/opt/hydrus/help" + + # Fix permissions + chown -R root:root "${pkgdir}/opt/hydrus" + + # Create and populate /opt/hydrus/bin + install -d -m755 "${pkgdir}/opt/hydrus/bin" + ln -s /usr/bin/upnpc "${pkgdir}/opt/hydrus/bin/upnpc_linux" + ln -s /usr/bin/ffmpeg "${pkgdir}/opt/hydrus/bin/ffmpeg" + + # Install hydrus-client and hydrus-server executables + install -d -m755 "${pkgdir}/usr/bin" + install -m755 ../hydrus-{client,server} "${pkgdir}/usr/bin/" + + # Install license files + install -d -m755 "${pkgdir}/usr/share/licenses/${pkgname}" + install -m644 COPYING "${pkgdir}/usr/share/licenses/${pkgname}/" + install -m644 license.txt "${pkgdir}/usr/share/licenses/${pkgname}/" + + # Install .desktop shortcut + install -d -m755 "${pkgdir}/usr/share/applications" + install -m644 ../hydrus.desktop "${pkgdir}/usr/share/applications/${pkgname}.desktop" +} diff --git a/cython-workarounds.patch b/cython-workarounds.patch new file mode 100644 index 000000000000..5d604946c795 --- /dev/null +++ b/cython-workarounds.patch @@ -0,0 +1,2483 @@ +diff --git a/include/ClientDB.py b/include/ClientDB.py +index 216de51..b0c2b88 100755 +--- a/include/ClientDB.py ++++ b/include/ClientDB.py +@@ -2279,7 +2279,7 @@ class DB( HydrusDB.HydrusDB ): + if tag_service_key == CC.COMBINED_TAG_SERVICE_KEY: service_phrase = '' + else: service_phrase = 'service_id = ' + HydrusData.ToString( tag_service_id ) + ' AND ' + +- query_hash_ids = { id for ( id, count ) in self._c.execute( 'SELECT hash_id, COUNT( DISTINCT tag_id ) FROM mappings WHERE ' + service_phrase + 'hash_id IN ' + HydrusData.SplayListForDB( query_hash_ids ) + ' AND status IN ' + HydrusData.SplayListForDB( statuses ) + ' GROUP BY hash_id;' ) if False not in ( pred( count ) for pred in tag_predicates ) } ++ query_hash_ids = { id for ( id, count ) in self._c.execute( 'SELECT hash_id, COUNT( DISTINCT tag_id ) FROM mappings WHERE ' + service_phrase + 'hash_id IN ' + HydrusData.SplayListForDB( query_hash_ids ) + ' AND status IN ' + HydrusData.SplayListForDB( statuses ) + ' GROUP BY hash_id;' ) if False not in (lambda count:( pred( count ) for pred in tag_predicates ))(count) } + + + # +diff --git a/include/ClientGUICanvas.py b/include/ClientGUICanvas.py +index 732f8e0..e801153 100755 +--- a/include/ClientGUICanvas.py ++++ b/include/ClientGUICanvas.py +@@ -50,7 +50,8 @@ NON_LARGABLY_ZOOMABLE_MIMES = [ mime for mime in HC.VIDEO if mime != HC.VIDEO_WE + + EMBED_BUTTON_MIMES = [ HC.VIDEO_FLV, HC.APPLICATION_FLASH ] + +-def CalculateCanvasZoom( media, ( canvas_width, canvas_height ) ): ++def CalculateCanvasZoom( media, canvas_dims ): ++ ( canvas_width, canvas_height ) = canvas_dims + + ( media_width, media_height ) = media.GetResolution() + +diff --git a/include/ClientGUIDialogs.py b/include/ClientGUIDialogs.py +index 3f5efb5..779be78 100755 +--- a/include/ClientGUIDialogs.py ++++ b/include/ClientGUIDialogs.py +@@ -244,7 +244,8 @@ class Dialog( wx.Dialog ): + + def EventDialogButton( self, event ): self.EndModal( event.GetId() ) + +- def SetInitialSize( self, ( width, height ) ): ++ def SetInitialSize( self, dims ): ++ ( width, height ) = dims + + wx.Dialog.SetInitialSize( self, ( width, height ) ) + +diff --git a/include/ClientGUIMessages.py b/include/ClientGUIMessages.py +deleted file mode 100755 +index 6a1d8c5..0000000 +--- a/include/ClientGUIMessages.py ++++ /dev/null +@@ -1,1467 +0,0 @@ +-import HydrusConstants as HC +-import ClientConstants as CC +-import ClientCaches +-import ClientGUICommon +-import ClientGUIDialogs +-import ClientGUIMedia +-import cStringIO +-import hashlib +-import os +-import random +-import threading +-import traceback +-import webbrowser +-import wx +-import wx.html +-import wx.richtext +-import wx.lib.scrolledpanel +-import yaml +-from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin +-from wx.lib.mixins.listctrl import ColumnSorterMixin +-import HydrusData +-import HydrusGlobals +-''' +-class ConversationsListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ): +- +- def __init__( self, parent, page_key, identity, conversations ): +- +- wx.ListCtrl.__init__( self, parent, style = wx.LC_REPORT | wx.LC_SINGLE_SEL ) +- ListCtrlAutoWidthMixin.__init__( self ) +- ColumnSorterMixin.__init__( self, 8 ) +- +- self._page_key = page_key +- self._identity = identity +- +- image_list = wx.ImageList( 16, 16, True, 2 ) +- +- image_list.Add( CC.GlobalBMPs.transparent_bmp ) +- image_list.Add( CC.GlobalBMPs.inbox_bmp ) +- +- self.AssignImageList( image_list, wx.IMAGE_LIST_SMALL ) +- +- self.InsertColumn( 0, 'inbox', width = 30 ) +- self.InsertColumn( 1, 'subject' ) +- self.InsertColumn( 2, 'creator', width = 90 ) +- self.InsertColumn( 3, 'to', width = 100 ) +- self.InsertColumn( 4, 'messages', width = 60 ) +- self.InsertColumn( 5, 'unread', width = 60 ) +- self.InsertColumn( 6, 'created', width = 130 ) +- self.InsertColumn( 7, 'updated', width = 130 ) +- +- self.setResizeColumn( 2 ) # subject +- +- self._SetConversations( conversations ) +- +- self.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventSelected ) +- self.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventSelected ) +- self.Bind( wx.EVT_LIST_ITEM_RIGHT_CLICK, self.EventShowMenu ) +- self.Bind( wx.EVT_MENU, self.EventMenu ) +- +- self.RefreshAcceleratorTable() +- +- HydrusGlobals.pubsub.sub( self, 'SetConversations', 'set_conversations' ) +- HydrusGlobals.pubsub.sub( self, 'ArchiveConversation', 'archive_conversation_gui' ) +- HydrusGlobals.pubsub.sub( self, 'InboxConversation', 'inbox_conversation_gui' ) +- HydrusGlobals.pubsub.sub( self, 'DeleteConversation', 'delete_conversation_gui' ) +- HydrusGlobals.pubsub.sub( self, 'UpdateMessageStatuses', 'message_statuses_gui' ) +- HydrusGlobals.pubsub.sub( self, 'RefreshAcceleratorTable', 'notify_new_options' ) +- +- +- def RefreshAcceleratorTable( self ): +- +- entries = [ +- ( wx.ACCEL_NORMAL, wx.WXK_DELETE, ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete' ) ), +- ( wx.ACCEL_NORMAL, wx.WXK_NUMPAD_DELETE, ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete' ) ) +- ] +- +- for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() ] ) +- +- self.SetAcceleratorTable( wx.AcceleratorTable( entries ) ) +- +- +- def _GetIndexFromConversationKey( self, conversation_key ): +- +- for i in range( self.GetItemCount() ): +- +- data_index = self.GetItemData( i ) +- +- conversation = self._data_indices_to_conversations[ data_index ] +- +- if conversation.GetConversationKey() == conversation_key: return i +- +- +- return None +- +- +- def _GetPrettyStatus( self ): +- +- if len( self._conversations ) == 1: return '1 conversation' +- else: return HydrusData.ToString( len( self._conversations ) ) + ' conversations' +- +- +- def _SetConversations( self, conversations ): +- +- self._conversations = list( conversations ) +- +- self.DeleteAllItems() +- +- self.itemDataMap = {} +- self._data_indices_to_conversations = {} +- +- i = 0 +- +- cmp_conversations = lambda c1, c2: cmp( c1.GetUpdated(), c2.GetUpdated() ) +- +- self._conversations.sort( cmp = cmp_conversations, reverse = True ) # order by newest change first +- +- for conversation in self._conversations: +- +- ( conversation_key, inbox, subject, name_from, participants, message_count, unread_count, created, updated ) = conversation.GetListCtrlTuple() +- +- if created is None: +- +- created_string = '' +- updated_string = '' +- +- else: +- +- created_string = HydrusData.ConvertTimestampToHumanPrettyTime( created ) +- updated_string = HydrusData.ConvertTimestampToHumanPrettyTime( updated ) +- +- +- self.Append( ( '', subject, name_from, ', '.join( [ contact.GetName() for contact in participants if contact.GetName() != name_from ] ), HydrusData.ToString( message_count ), HydrusData.ToString( unread_count ), created_string, updated_string ) ) +- +- data_index = i +- +- self.SetItemData( i, data_index ) +- +- if inbox: self.SetItemImage( i, 1 ) # inbox +- else: self.SetItemImage( i, 0 ) # transparent +- +- self.itemDataMap[ data_index ] = ( inbox, subject, name_from, len( participants ), message_count, unread_count, created, updated ) +- +- self._data_indices_to_conversations[ data_index ] = conversation +- +- i += 1 +- +- +- HydrusGlobals.pubsub.pub( 'conversation_focus', self._page_key, None ) +- HydrusGlobals.pubsub.pub( 'new_page_status', self._page_key, self._GetPrettyStatus() ) +- +- +- def _UpdateConversationItem( self, conversation_key ): +- +- selection = self._GetIndexFromConversationKey( conversation_key ) +- +- if selection is not None: +- +- conversation = self._data_indices_to_conversations[ self.GetItemData( selection ) ] +- +- ( conversation_key, inbox, subject, name_from, participants, message_count, unread_count, created, updated ) = conversation.GetListCtrlTuple() +- +- selection = self._GetIndexFromConversationKey( conversation_key ) +- +- data_index = self.GetItemData( selection ) +- +- self.itemDataMap[ data_index ] = ( inbox, subject, name_from, len( participants ), message_count, unread_count, created, updated ) +- +- if inbox: self.SetItemImage( selection, 1 ) +- else: self.SetItemImage( selection, 0 ) +- +- self.SetStringItem( selection, 4, HydrusData.ToString( message_count ) ) +- self.SetStringItem( selection, 5, HydrusData.ToString( unread_count ) ) +- +- if created is None: +- +- created_string = '' +- updated_string = '' +- +- else: +- +- created_string = HydrusData.ConvertTimestampToHumanPrettyTime( created ) +- +- updated_string = HydrusData.ConvertTimestampToHumanPrettyTime( updated ) +- +- +- self.SetStringItem( selection, 6, created_string ) +- self.SetStringItem( selection, 7, updated_string ) +- +- +- +- def ArchiveConversation( self, conversation_key ): self._UpdateConversationItem( conversation_key ) +- +- def DeleteConversation( self, conversation_key ): +- +- selection = self._GetIndexFromConversationKey( conversation_key ) +- +- if selection is not None: +- +- conversation = self._data_indices_to_conversations[ self.GetItemData( selection ) ] +- +- self._conversations.remove( conversation ) +- +- self.DeleteItem( selection ) +- +- +- +- def EventMenu( self, event ): +- +- action = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() ) +- +- if action is not None: +- +- ( command, data ) = action +- +- selection = self.GetFirstSelected() +- +- conversation = self._data_indices_to_conversations[ self.GetItemData( selection ) ] +- +- conversation_key = conversation.GetConversationKey() +- +- identity_contact_key = self._identity.GetContactKey() +- +- if command == 'archive': wx.GetApp().Write( 'archive_conversation', conversation_key ) +- elif command == 'inbox': wx.GetApp().Write( 'inbox_conversation', conversation_key ) +- elif command == 'read': +- +- message_keys = conversation.GetMessageKeysWithDestination( ( self._identity, 'sent' ) ) +- +- for message_key in message_keys: wx.GetApp().Write( 'message_statuses', message_key, [ ( identity_contact_key, 'read' ) ] ) +- +- elif command == 'unread': +- +- message_keys = conversation.GetMessageKeysWithDestination( ( self._identity, 'read' ) ) +- +- for message_key in message_keys: wx.GetApp().Write( 'message_statuses', message_key, [ ( identity_contact_key, 'sent' ) ] ) +- +- elif command == 'delete': +- +- with ClientGUIDialogs.DialogYesNo( self, 'Are you sure you want to delete this conversation?' ) as dlg: +- +- if dlg.ShowModal() == wx.ID_YES: wx.GetApp().Write( 'delete_conversation', conversation_key ) +- +- +- else: event.Skip() +- +- +- +- def EventSelected( self, event ): +- +- selection = self.GetFirstSelected() +- +- if selection == wx.NOT_FOUND: HydrusGlobals.pubsub.pub( 'conversation_focus', self._page_key, None ) +- else: HydrusGlobals.pubsub.pub( 'conversation_focus', self._page_key, self._data_indices_to_conversations[ self.GetItemData( selection ) ] ) +- +- +- def EventShowMenu( self, event ): +- +- conversation = self._data_indices_to_conversations[ self.GetItemData( event.GetIndex() ) ] +- +- menu = wx.Menu() +- +- if conversation.IsInbox(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'archive' ), 'archive' ) +- else: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'inbox' ), 'return to inbox' ) +- +- if conversation.HasUnread(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'read' ), 'set all as read' ) +- if conversation.HasRead(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'unread' ), 'set all as unread' ) +- +- menu.AppendSeparator() +- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete' ), 'delete' ) +- +- self.PopupMenu( menu ) +- +- wx.CallAfter( menu.Destroy ) +- +- +- def GetListCtrl( self ): return self +- +- def InboxConversation( self, conversation_key ): self._UpdateConversationItem( conversation_key ) +- +- def SetConversations( self, page_key, conversations ): +- +- if page_key == self._page_key: +- +- try: self._SetConversations( conversations ) +- except: +- +- wx.MessageBox( traceback.format_exc() ) +- +- +- +- +- def UpdateMessageStatuses( self, message_key, updates ): +- +- for conversation in self._data_indices_to_conversations.values(): +- +- if conversation.HasMessageKey( message_key ): +- +- conversation_key = conversation.GetConversationKey() +- +- self._UpdateConversationItem( conversation_key ) +- +- +- +- +-class ConversationPanel( wx.Panel ): +- +- def __init__( self, parent, page_key, identity, conversation ): +- +- wx.Panel.__init__( self, parent, style = wx.SIMPLE_BORDER ) +- +- self.SetBackgroundColour( wx.WHITE ) +- +- self._identity = identity +- self._page_key = page_key +- self._conversation = conversation +- +- self._vbox = wx.BoxSizer( wx.VERTICAL ) +- +- self._message_keys_to_message_panels = {} +- self._draft_keys_to_draft_panels = {} +- +- self._scrolling_messages_window = wx.lib.scrolledpanel.ScrolledPanel( self ) +- self._scrolling_messages_window.SetupScrolling() +- self._scrolling_messages_window.SetScrollRate( 0, 50 ) +- +- self._window_vbox = wx.BoxSizer( wx.VERTICAL ) +- +- self._messages_vbox = wx.BoxSizer( wx.VERTICAL ) +- self._drafts_vbox = wx.BoxSizer( wx.VERTICAL ) +- +- self._window_vbox.AddF( self._messages_vbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) +- self._window_vbox.AddF( self._drafts_vbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) +- +- self._DrawConversation() +- +- self.SetSizer( self._vbox ) +- +- HydrusGlobals.pubsub.sub( self, 'DeleteDraft', 'delete_draft_gui' ) +- HydrusGlobals.pubsub.sub( self, 'NewMessage', 'new_message' ) +- +- +- def _DrawConversation( self ): +- +- # fix it so this stuff is reusable? +- +- self._messages_vbox.DeleteWindows() +- self._drafts_vbox.DeleteWindows() +- +- self._convo_frame = wx.Panel( self ) +- +- convo_vbox = wx.BoxSizer( wx.VERTICAL ) +- +- subject_static_text = wx.StaticText( self._convo_frame, label = self._conversation.GetSubject() ) +- +- f = subject_static_text.GetFont() +- +- f.SetWeight( wx.BOLD ) +- +- subject_static_text.SetFont( f ) +- +- convo_vbox.AddF( subject_static_text, CC.FLAGS_EXPAND_PERPENDICULAR ) +- convo_vbox.AddF( wx.StaticText( self._convo_frame, label = ', '.join( contact.GetName() for contact in self._conversation.GetParticipants() ) ), CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- self._convo_frame.SetSizer( convo_vbox ) +- +- # archive_all +- # set all as read +- # delete all button, eventually +- +- ( messages, drafts ) = self._conversation.GetMessages() +- +- for message in messages: +- +- message_panel = MessagePanel( self._scrolling_messages_window, message, self._identity ) +- +- self._message_keys_to_message_panels[ message.GetMessageKey() ] = message_panel +- +- self._messages_vbox.AddF( message_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- +- for draft in drafts: +- +- draft_panel = DraftPanel( self._scrolling_messages_window, draft ) +- +- self._draft_keys_to_draft_panels[ draft.GetDraftKey() ] = draft_panel +- +- self._drafts_vbox.AddF( draft_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- +- self._reply_button = wx.Button( self._scrolling_messages_window, label = 'reply' ) +- self._reply_button.Bind( wx.EVT_BUTTON, self.EventReply ) +- self._reply_button.Disable() +- +- if len( messages ) > 0 and self._conversation.GetStartedBy().GetName() != 'Anonymous': self._reply_button.Enable() +- +- if self._conversation.GetStartedBy() == self._identity: self._reply_button.Enable() +- +- self._window_vbox.AddF( self._reply_button, CC.FLAGS_LONE_BUTTON ) +- +- self._scrolling_messages_window.SetSizer( self._window_vbox ) +- +- self._vbox.AddF( self._convo_frame, CC.FLAGS_EXPAND_PERPENDICULAR ) +- self._vbox.AddF( self._scrolling_messages_window, CC.FLAGS_EXPAND_BOTH_WAYS ) +- +- self.SetSizer( self._vbox ) +- +- +- def DeleteDraft( self, draft_key ): +- +- if draft_key in self._draft_keys_to_draft_panels: +- +- draft_panel = self._draft_keys_to_draft_panels[ draft_key ] +- +- del self._draft_keys_to_draft_panels[ draft_key ] +- +- self._drafts_vbox.Detach( draft_panel ) +- +- wx.CallAfter( draft_panel.Destroy ) +- +- self._scrolling_messages_window.FitInside() +- +- +- +- def EventReply( self, event ): +- +- draft_key = os.urandom( 32 ) +- conversation_key = self._conversation.GetConversationKey() +- subject = self._conversation.GetSubject() +- contact_from = self._identity +- participants = self._conversation.GetParticipants() +- contact_names_to = [ contact.GetName() for contact in participants if contact is not None and contact.GetName() != 'Anonymous' and contact != contact_from ] +- recipients_visible = True +- body = '' +- attachment_hashes = [] +- +- draft = ClientConstantsMessages.DraftMessage( draft_key, conversation_key, subject, contact_from, contact_names_to, recipients_visible, body, attachment_hashes, is_new = True ) +- +- self._conversation.AddDraft( draft ) +- +- draft_panel = DraftPanel( self._scrolling_messages_window, draft ) +- +- self._draft_keys_to_draft_panels[ draft_key ] = draft_panel +- +- self._drafts_vbox.AddF( draft_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- self._scrolling_messages_window.FitInside() +- +- +- def NewMessage( self, conversation_key, message ): +- +- if self._conversation is not None and conversation_key == self._conversation.GetConversationKey(): +- +- message_key = message.GetMessageKey() +- +- if message_key not in self._message_keys_to_message_panels: # if not already here! +- +- message_panel = MessagePanel( self._scrolling_messages_window, message, self._identity ) +- +- self._message_keys_to_message_panels[ message_key ] = message_panel +- +- self._messages_vbox.AddF( message_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- self._conversation.AddMessage( message ) +- +- self._scrolling_messages_window.FitInside() +- +- +- +- +-class ConversationSplitter( wx.SplitterWindow ): +- +- def __init__( self, parent, page_key, identity, conversations = None ): +- +- if conversations is None: conversations = [] +- +- wx.SplitterWindow.__init__( self, parent ) +- +- self._page_key = page_key +- self._identity = identity +- self._conversations = conversations +- +- self.SetMinimumPaneSize( 180 ) +- self.SetSashGravity( 0.0 ) +- +- self._InitConversationsPanel() +- self._InitConversationPanel() +- +- wx.CallAfter( self.SplitHorizontally, self._conversations_panel, self._conversation_panel, 180 ) +- wx.CallAfter( self._conversation_panel.Refresh ) +- +- HydrusGlobals.pubsub.sub( self, 'SetConversationFocus', 'conversation_focus' ) +- +- +- def _InitConversationsPanel( self ): self._conversations_panel = ConversationsListCtrl( self, self._page_key, self._identity, self._conversations ) +- +- def _InitConversationPanel( self ): self._conversation_panel = wx.Window( self ) +- +- def SetConversationFocus( self, page_key, conversation ): +- +- if page_key == self._page_key: +- +- with wx.FrozenWindow( self ): +- +- if conversation is None: new_panel = wx.Window( self ) +- else: new_panel = ConversationPanel( self, self._page_key, self._identity, conversation ) +- +- self.ReplaceWindow( self._conversation_panel, new_panel ) +- +- self._conversation_panel.Close() +- +- self._conversation_panel = new_panel +- +- +- +- +-class DestinationPanel( wx.Panel ): +- +- def __init__( self, parent, message_key, contact, status, identity ): +- +- wx.Panel.__init__( self, parent ) +- +- self.SetBackgroundColour( CC.COLOUR_MESSAGE ) +- +- self._message_key = message_key +- self._contact = contact +- self._contact_key = contact.GetContactKey() +- self._identity = identity +- self._status = status +- +- name = contact.GetName() +- +- name_static_text = wx.StaticText( self, label = name ) +- +- if self._contact == self._identity: +- +- f = name_static_text.GetFont() +- +- f.SetWeight( wx.BOLD ) +- +- name_static_text.SetFont( f ) +- +- if self._status == 'sent': self._status = 'unread' +- +- +- self._status_panel = self._CreateStatusPanel() +- +- self._hbox = wx.BoxSizer( wx.HORIZONTAL ) +- +- self._hbox.AddF( name_static_text, CC.FLAGS_MIXED ) +- self._hbox.AddF( self._status_panel, CC.FLAGS_MIXED ) +- +- self.SetSizer( self._hbox ) +- +- self.Bind( wx.EVT_MENU, self.EventMenu ) +- +- +- def _CreateStatusPanel( self ): +- +- if self._status == 'failed': +- +- status_text = wx.StaticText( self, label = self._status ) +- +- status_text.SetForegroundColour( ( 128, 0, 0 ) ) +- +- status_text.SetCursor( wx.StockCursor( wx.CURSOR_HAND ) ) +- +- status_text.Bind( wx.EVT_LEFT_DOWN, self.EventRetryMenu ) +- +- elif self._status == 'unread': +- +- status_text = wx.StaticText( self, label = self._status ) +- +- status_text.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_HIGHLIGHT ) ) +- +- status_text.SetCursor( wx.StockCursor( wx.CURSOR_HAND ) ) +- +- status_text.Bind( wx.EVT_LEFT_DOWN, self.EventReadMenu ) +- +- elif self._status == 'read': +- +- status_text = wx.StaticText( self, label = self._status ) +- +- status_text.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_HIGHLIGHT ) ) +- +- status_text.SetCursor( wx.StockCursor( wx.CURSOR_HAND ) ) +- +- status_text.Bind( wx.EVT_LEFT_DOWN, self.EventUnreadMenu ) +- +- else: +- +- status_text = wx.StaticText( self, label = self._status ) +- +- status_text.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_HIGHLIGHT ) ) +- +- +- return status_text +- +- +- def EventMenu( self, event ): +- +- action = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() ) +- +- if action is not None: +- +- ( command, data ) = action +- +- if command in ( 'retry', 'read', 'unread' ): +- +- if command == 'retry': status = 'pending' +- elif command == 'read': status = 'read' +- elif command == 'unread': status = 'sent' +- +- my_message_depot = wx.GetApp().GetManager( 'services' ).GetService( self._identity.GetServiceKey() ) +- +- connection = my_message_depot.GetConnection() +- +- my_public_key = self._identity.GetPublicKey() +- my_contact_key = self._identity.GetContactKey() +- +- contacts_contact_key = self._contact.GetContactKey() +- +- status_updates = [] +- +- status_key = hashlib.sha256( contacts_contact_key + self._message_key ).digest() +- +- packaged_status = HydrusMessageHandling.PackageStatusForDelivery( ( self._message_key, contacts_contact_key, status ), my_public_key ) +- +- status_updates = ( ( status_key, packaged_status ), ) +- +- connection.Post( 'message_statuses', contact_key = my_contact_key, statuses = status_updates ) +- +- wx.GetApp().Write( 'message_statuses', self._message_key, [ ( self._contact_key, status ) ] ) +- +- else: event.Skip() +- +- +- +- def EventReadMenu( self, event ): +- +- menu = wx.Menu() +- +- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'read' ), 'read' ) +- +- self.PopupMenu( menu ) +- +- wx.CallAfter( menu.Destroy ) +- +- +- def EventRetryMenu( self, event ): +- +- menu = wx.Menu() +- +- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'retry' ), 'retry' ) +- +- self.PopupMenu( menu ) +- +- wx.CallAfter( menu.Destroy ) +- +- +- def EventUnreadMenu( self, event ): +- +- menu = wx.Menu() +- +- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'unread' ), 'unread' ) +- +- self.PopupMenu( menu ) +- +- wx.CallAfter( menu.Destroy ) +- +- +- def SetStatus( self, status ): +- +- if self._contact == self._identity and status == 'sent': status = 'unread' +- +- self._status = status +- +- new_status_panel = self._CreateStatusPanel() +- +- self._hbox.Replace( self._status_panel, new_status_panel ) +- +- self._status_panel.Close() +- +- self._status_panel = new_status_panel +- +- +-class DestinationsPanel( wx.Panel ): +- +- def __init__( self, parent, message_key, destinations, identity ): +- +- wx.Panel.__init__( self, parent ) +- +- self.SetBackgroundColour( CC.COLOUR_MESSAGE ) +- +- self._message_key = message_key +- self._my_panels = {} +- +- vbox = wx.BoxSizer( wx.VERTICAL ) +- +- for ( contact, status ) in destinations: +- +- destination_panel = DestinationPanel( self, message_key, contact, status, identity ) +- +- vbox.AddF( destination_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- self._my_panels[ contact.GetContactKey() ] = destination_panel +- +- +- self.SetSizer( vbox ) +- +- HydrusGlobals.pubsub.sub( self, 'UpdateMessageStatuses', 'message_statuses_gui' ) +- +- +- def UpdateMessageStatuses( self, message_key, updates ): +- +- if message_key == self._message_key: +- +- with wx.FrozenWindow( self ): +- +- for ( contact_key, status ) in updates: +- +- if contact_key in self._my_panels: self._my_panels[ contact_key ].SetStatus( status ) +- +- +- # doing replace on the destpanels' tricky sizer is a huge pain, hence the size event +- # has to be postevent, not processevent +- wx.PostEvent( self.GetParent(), wx.SizeEvent() ) +- +- +- +- +-# A whole bunch of this is cribbed from/inspired by the excellent rtc example in the wxPython Demo +-class DraftBodyPanel( wx.Panel ): +- +- ID_BOLD = 0 +- ID_ITALIC = 1 +- ID_UNDERLINE = 2 +- +- ID_ALIGN_LEFT = 3 +- ID_ALIGN_CENTER = 4 +- ID_ALIGN_RIGHT = 5 +- ID_ALIGN_JUSTIFY = 6 # rtc doesn't yet support this, sadly +- +- ID_INDENT_LESS = 7 +- ID_INDENT_MORE = 8 +- +- ID_FONT = 9 +- ID_FONT_COLOUR = 10 +- +- ID_LINK = 11 +- ID_LINK_BREAK = 12 +- +- def __init__( self, parent, xml ): +- +- wx.Panel.__init__( self, parent ) +- +- self._CreateToolBar() +- +- self._CreateRTC( xml ) +- +- vbox = wx.BoxSizer( wx.VERTICAL ) +- +- vbox.AddF( self._toolbar, CC.FLAGS_EXPAND_PERPENDICULAR ) +- vbox.AddF( self._rtc, CC.FLAGS_EXPAND_BOTH_WAYS ) +- +- self.SetSizer( vbox ) +- +- self.SetAcceleratorTable( wx.AcceleratorTable( [ +- ( wx.ACCEL_CTRL, ord( 'b' ), self.ID_BOLD ), +- ( wx.ACCEL_CTRL, ord( 'i' ), self.ID_ITALIC ), +- ( wx.ACCEL_CTRL, ord( 'u' ), self.ID_UNDERLINE ) +- ] ) ) +- +- self.Bind( wx.EVT_TOOL, self.EventToolBar ) +- +- self.Bind( wx.EVT_UPDATE_UI, self.EventUpdateUI ) +- +- +- def _CreateToolBar( self ): +- +- self._toolbar = wx.ToolBar( self ) +- +- self._toolbar.SetToolBitmapSize( ( 16, 16 ) ) +- +- self._toolbar.AddCheckTool( self.ID_BOLD, CC.GlobalBMPs.bold_bmp ) +- self._toolbar.AddCheckTool( self.ID_ITALIC, CC.GlobalBMPs.italic_bmp ) +- self._toolbar.AddCheckTool( self.ID_UNDERLINE, CC.GlobalBMPs.underline_bmp ) +- +- self._toolbar.AddSeparator() +- +- self._toolbar.AddRadioTool( self.ID_ALIGN_LEFT, CC.GlobalBMPs.align_left_bmp ) +- self._toolbar.AddRadioTool( self.ID_ALIGN_CENTER, CC.GlobalBMPs.align_center_bmp ) +- self._toolbar.AddRadioTool( self.ID_ALIGN_RIGHT, CC.GlobalBMPs.align_right_bmp ) +- +- self._toolbar.AddSeparator() +- +- self._toolbar.AddLabelTool( self.ID_INDENT_LESS, 'indent less', CC.GlobalBMPs.indent_less_bmp ) +- self._toolbar.AddLabelTool( self.ID_INDENT_MORE, 'indent more', CC.GlobalBMPs.indent_more_bmp ) +- +- self._toolbar.AddSeparator() +- +- self._toolbar.AddLabelTool( self.ID_FONT, 'font', CC.GlobalBMPs.font_bmp ) +- self._toolbar.AddLabelTool( self.ID_FONT_COLOUR, 'font colour', CC.GlobalBMPs.colour_bmp, shortHelp = 'font colour' ) +- +- # font background +- # message background? +- +- self._toolbar.AddSeparator() +- +- self._toolbar.AddLabelTool( self.ID_LINK, 'link', CC.GlobalBMPs.link_bmp ) +- self._toolbar.AddLabelTool( self.ID_LINK_BREAK, 'break link', CC.GlobalBMPs.link_break_bmp ) +- +- self._toolbar.Realize() +- +- +- def _CreateRTC( self, xml ): +- +- self._rtc = wx.richtext.RichTextCtrl( self, size = ( -1, 300 ), style = wx.WANTS_CHARS | wx.richtext.RE_MULTILINE ) +- +- if len( xml ) > 0: +- +- xml_handler = wx.richtext.RichTextXMLHandler() +- +- stream = cStringIO.StringIO( xml ) +- +- xml_handler.LoadStream( self._rtc.GetBuffer(), stream ) +- +- +- +- def EventUpdateUI( self, event ): +- +- self._toolbar.ToggleTool( self.ID_BOLD, self._rtc.IsSelectionBold() ) +- self._toolbar.ToggleTool( self.ID_ITALIC, self._rtc.IsSelectionItalics() ) +- self._toolbar.ToggleTool( self.ID_UNDERLINE, self._rtc.IsSelectionUnderlined() ) +- +- if self._rtc.IsSelectionAligned( wx.TEXT_ALIGNMENT_LEFT ): self._toolbar.ToggleTool( self.ID_ALIGN_LEFT, True ) +- elif self._rtc.IsSelectionAligned( wx.TEXT_ALIGNMENT_CENTER ): self._toolbar.ToggleTool( self.ID_ALIGN_CENTER, True ) +- elif self._rtc.IsSelectionAligned( wx.TEXT_ALIGNMENT_RIGHT ): self._toolbar.ToggleTool( self.ID_ALIGN_RIGHT, True ) +- +- event.Skip() +- +- +- def EventToolBar( self, event ): +- +- id = event.GetId() +- +- if id == self.ID_BOLD: self._rtc.ApplyBoldToSelection() +- elif id == self.ID_ITALIC: self._rtc.ApplyItalicToSelection() +- elif id == self.ID_UNDERLINE: self._rtc.ApplyUnderlineToSelection() +- elif id == self.ID_ALIGN_LEFT: self._rtc.ApplyAlignmentToSelection( wx.TEXT_ALIGNMENT_LEFT ) +- elif id == self.ID_ALIGN_CENTER: self._rtc.ApplyAlignmentToSelection( wx.TEXT_ALIGNMENT_CENTRE ) +- elif id == self.ID_ALIGN_RIGHT: self._rtc.ApplyAlignmentToSelection( wx.TEXT_ALIGNMENT_RIGHT ) +- elif id == self.ID_INDENT_LESS: +- +- text_attribute = wx.TEXTAttrEx() +- +- text_attribute.SetFlags( wx.TEXT_ATTR_LEFT_INDENT ) +- +- ip = self._rtc.GetInsertionPoint() +- +- if self._rtc.GetStyle( ip, text_attribute ): # this copies the current style into text_attribute, returning true if successful +- +- if self._rtc.HasSelection(): selection_range = self._rtc.GetSelectionRange() +- else: selection_range = wx.richtext.RichTextRange( ip, ip ) +- +- if text_attribute.GetLeftIndent() >= 100: +- +- text_attribute.SetLeftIndent( text_attribute.GetLeftIndent() - 100 ) +- text_attribute.SetFlags( wx.TEXT_ATTR_LEFT_INDENT ) +- +- self._rtc.SetStyle( selection_range, text_attribute ) +- +- +- +- elif id == self.ID_INDENT_MORE: +- +- text_attribute = wx.richtext.TextAttrEx() +- +- text_attribute.SetFlags( wx.TEXT_ATTR_LEFT_INDENT ) +- +- ip = self._rtc.GetInsertionPoint() +- +- if self._rtc.GetStyle( ip, text_attribute ): # this copies the current style into text_attribute, returning true if successful +- +- if self._rtc.HasSelection(): selection_range = self._rtc.GetSelectionRange() +- else: selection_range = wx.richtext.RichTextRange( ip, ip ) +- +- text_attribute.SetLeftIndent( text_attribute.GetLeftIndent() + 100 ) +- text_attribute.SetFlags( wx.TEXT_ATTR_LEFT_INDENT ) +- +- self._rtc.SetStyle( selection_range, text_attribute ) +- +- +- elif id == self.ID_FONT: +- +- font_data = wx.FontData() +- font_data.EnableEffects( False ) +- +- text_attribute = wx.richtext.TextAttrEx() +- text_attribute.SetFlags( wx.TEXT_ATTR_FONT ) +- +- if self._rtc.GetStyle( self._rtc.GetInsertionPoint(), text_attribute ): font_data.SetInitialFont( text_attribute.GetFont() ) +- +- with wx.FontDialog( self, font_data ) as dlg: +- +- if dlg.ShowModal() == wx.ID_OK: +- +- font_data = dlg.GetFontData() +- +- font = font_data.GetChosenFont() +- +- if not self._rtc.HasSelection(): self._rtc.BeginFont( font ) +- else: +- +- selection_range = self._rtc.GetSelectionRange() +- +- text_attribute.SetFlags( wx.TEXT_ATTR_FONT ) +- text_attribute.SetFont( font ) +- +- self._rtc.SetStyle( selection_range, text_attribute ) +- +- +- +- +- elif id == self.ID_FONT_COLOUR: +- +- colour_data = wx.ColourData() +- +- text_attribute = wx.richtext.TextAttrEx() +- text_attribute.SetFlags( wx.TEXT_ATTR_TEXT_COLOUR ) +- +- if self._rtc.GetStyle( self._rtc.GetInsertionPoint(), text_attribute ): colour_data.SetColour( text_attribute.GetTextColour() ) +- +- with wx.ColourDialog( self, colour_data ) as dlg: +- +- if dlg.ShowModal() == wx.ID_OK: +- +- colour_data = dlg.GetColourData() +- colour = colour_data.GetColour() +- +- if colour: +- +- if not self._rtc.HasSelection(): self._rtc.BeginTextColour( colour ) +- else: +- +- selection_range = self._rtc.GetSelectionRange() +- +- text_attribute.SetFlags( wx.TEXT_ATTR_TEXT_COLOUR ) +- text_attribute.SetTextColour( colour ) +- +- self._rtc.SetStyle( selection_range, text_attribute ) +- +- +- +- +- +- elif id == self.ID_LINK: +- +- text_attribute = wx.richtext.TextAttrEx() +- +- text_attribute.SetFlags( wx.TEXT_ATTR_URL ) +- +- ip = self._rtc.GetInsertionPoint() +- +- self._rtc.GetStyle( self._rtc.GetInsertionPoint(), text_attribute ) +- +- if text_attribute.HasURL(): initial_url = text_attribute.GetURL() +- else: initial_url = 'http://' +- +- with ClientGUIDialogs.DialogTextEntry( self, 'Enter url.', default = initial_url ) as dlg: +- +- if dlg.ShowModal() == wx.ID_OK: +- +- url = dlg.GetValue() +- +- if self._rtc.HasSelection(): selection_range = self._rtc.GetSelectionRange() +- else: selection_range = wx.richtext.RichTextRange( ip, ip ) +- +- text_attribute.SetFlags( wx.TEXT_ATTR_TEXT_COLOUR ) +- text_attribute.SetTextColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_HIGHLIGHT ) ) +- +- text_attribute.SetFontUnderlined( True ) +- +- text_attribute.SetURL( url ) +- +- self._rtc.SetStyle( selection_range, text_attribute ) +- +- +- +- elif id == self.ID_LINK_BREAK: +- +- if self._rtc.HasSelection(): selection_range = self._rtc.GetSelectionRange() +- else: selection_range = wx.richtext.RichTextRange( ip, ip ) +- +- text_attribute = wx.richtext.TextAttrEx() +- +- text_attribute.SetFlags( wx.TEXT_ATTR_TEXT_COLOUR ) +- text_attribute.SetTextColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOWTEXT ) ) +- +- text_attribute.SetFontUnderlined( False ) +- +- self._rtc.SetStyle( selection_range, text_attribute ) +- +- text_attribute = wx.richtext.TextAttrEx() +- +- text_attribute.SetFlags( wx.TEXT_ATTR_URL ) +- +- self._rtc.SetStyleEx( selection_range, text_attribute, wx.richtext.RICHTEXT_SETSTYLE_REMOVE ) +- +- +- +- def GetXMLHTML( self ): +- +- xml_handler = wx.richtext.RichTextXMLHandler() +- +- stream = cStringIO.StringIO() +- +- xml_handler.SaveStream( self._rtc.GetBuffer(), stream ) +- +- stream.seek( 0 ) +- +- xml = stream.read() +- +- html_handler = wx.richtext.RichTextHTMLHandler() +- html_handler.SetFlags( wx.richtext.RICHTEXT_HANDLER_SAVE_IMAGES_TO_MEMORY ) +- html_handler.SetFontSizeMapping( [7,9,11,12,14,22,100] ) +- +- stream = cStringIO.StringIO() +- +- html_handler.SaveStream( self._rtc.GetBuffer(), stream ) +- +- stream.seek( 0 ) +- +- html = stream.read() +- +- return yaml.safe_dump( ( xml, html ) ) +- +- +- +-class DraftPanel( wx.Panel ): +- +- def __init__( self, parent, draft_message ): +- +- wx.Panel.__init__( self, parent ) +- +- self.SetBackgroundColour( CC.COLOUR_MESSAGE ) +- +- self._compose_key = os.urandom( 32 ) +- +- self._draft_message = draft_message +- +- ( self._draft_key, self._conversation_key, subject, self._contact_from, contacts_to, recipients_visible, body, attachment_hashes ) = self._draft_message.GetInfo() +- +- is_new = self._draft_message.IsNew() +- +- self._from = wx.StaticText( self, label = self._contact_from.GetName() ) +- +- if not self._draft_message.IsReply(): +- +- self._to_panel = ClientGUICommon.StaticBox( self, 'to' ) +- +- self._recipients_list = wx.ListCtrl( self._to_panel, style = wx.LC_LIST | wx.LC_NO_HEADER | wx.LC_SINGLE_SEL ) +- self._recipients_list.InsertColumn( 0, 'contacts' ) +- for name in contacts_to: self._recipients_list.Append( ( name, ) ) +- self._recipients_list.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.EventRemove ) +- +- self._new_recipient = ClientGUICommon.AutoCompleteDropdownContacts( self._to_panel, self._compose_key, self._contact_from ) +- +- self._recipients_visible = wx.CheckBox( self._to_panel ) +- self._recipients_visible.SetValue( recipients_visible ) +- self._recipients_visible.Bind( wx.EVT_CHECKBOX, self.EventChanged ) +- +- self._subject_panel = ClientGUICommon.StaticBox( self, 'subject' ) +- +- self._subject = wx.TextCtrl( self._subject_panel, value = subject ) +- self._subject.Bind( wx.EVT_KEY_DOWN, self.EventChanged ) +- +- +- if body == '': xml = '' +- else: ( xml, html ) = yaml.safe_load( body ) +- +- self._body = DraftBodyPanel( self, xml ) +- self.Bind( wx.richtext.EVT_RICHTEXT_STYLE_CHANGED, self.EventChanged ) +- self.Bind( wx.richtext.EVT_RICHTEXT_CHARACTER, self.EventChanged ) +- self.Bind( wx.richtext.EVT_RICHTEXT_RETURN, self.EventChanged ) +- self.Bind( wx.richtext.EVT_RICHTEXT_DELETE, self.EventChanged ) +- +- self._attachments = wx.TextCtrl( self, value = os.linesep.join( [ hash.encode( 'hex' ) for hash in attachment_hashes ] ), style = wx.TE_MULTILINE ) +- self._attachments.Bind( wx.EVT_KEY_DOWN, self.EventChanged ) +- # do thumbnails later! for now, do a listbox or whatever +- +- self._send = wx.Button( self, label = 'send' ) +- self._send.Bind( wx.EVT_BUTTON, self.EventSend ) +- self._send.SetForegroundColour( ( 0, 128, 0 ) ) +- if len( contacts_to ) == 0: self._send.Disable() +- +- self._delete_draft = wx.Button( self, label = 'delete' ) +- self._delete_draft.Bind( wx.EVT_BUTTON, self.EventDeleteDraft ) +- self._delete_draft.SetForegroundColour( ( 128, 0, 0 ) ) +- +- self._save_draft = wx.Button( self, label = 'save' ) +- self._save_draft.Bind( wx.EVT_BUTTON, self.EventSaveDraft ) +- +- if is_new: +- +- self._draft_changed = True +- self._delete_draft.SetLabel( 'discard' ) +- +- else: +- +- self._draft_changed = False +- self._save_draft.SetLabel( 'saved' ) +- self._save_draft.Disable() +- +- +- vbox = wx.BoxSizer( wx.VERTICAL ) +- +- vbox.AddF( self._from, CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- if not self._draft_message.IsReply(): +- +- recipients_hbox = wx.BoxSizer( wx.HORIZONTAL ) +- +- recipients_hbox.AddF( wx.StaticText( self._to_panel, label = 'recipients can see each other' ), CC.FLAGS_MIXED ) +- recipients_hbox.AddF( self._recipients_visible, CC.FLAGS_MIXED ) +- +- self._to_panel.AddF( self._recipients_list, CC.FLAGS_EXPAND_PERPENDICULAR ) +- self._to_panel.AddF( self._new_recipient, CC.FLAGS_LONE_BUTTON ) +- self._to_panel.AddF( recipients_hbox, CC.FLAGS_BUTTON_SIZER ) +- +- self._subject_panel.AddF( self._subject, CC.FLAGS_EXPAND_BOTH_WAYS ) +- +- vbox.AddF( self._to_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) +- vbox.AddF( self._subject_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- +- vbox.AddF( self._body, CC.FLAGS_EXPAND_BOTH_WAYS ) +- #vbox.AddF( wx.StaticText( self, label = 'attachment hashes:' ), CC.FLAGS_MIXED ) +- #vbox.AddF( self._attachments, CC.FLAGS_EXPAND_PERPENDICULAR ) +- self._attachments.Hide() +- button_hbox = wx.BoxSizer( wx.HORIZONTAL ) +- button_hbox.AddF( self._send, CC.FLAGS_MIXED ) +- button_hbox.AddF( self._delete_draft, CC.FLAGS_MIXED ) +- button_hbox.AddF( self._save_draft, CC.FLAGS_MIXED ) +- +- vbox.AddF( button_hbox, CC.FLAGS_BUTTON_SIZER ) +- +- self.SetSizer( vbox ) +- +- HydrusGlobals.pubsub.sub( self, 'AddContact', 'add_contact' ) +- HydrusGlobals.pubsub.sub( self, 'DraftSaved', 'draft_saved' ) +- +- if not self._draft_message.IsReply(): wx.CallAfter( self._new_recipient.SetFocus ) +- +- +- def _GetDraftMessage( self ): +- +- ( self._draft_key, self._conversation_key, subject, self._contact_from, contacts_to, recipients_visible, body, attachment_hashes ) = self._draft_message.GetInfo() +- +- if not self._draft_message.IsReply(): +- +- subject = self._subject.GetValue() +- contacts_to = [ self._recipients_list.GetItemText( i ) for i in range( self._recipients_list.GetItemCount() ) ] +- recipients_visible = self._recipients_visible.GetValue() +- +- +- body = self._body.GetXMLHTML() +- +- try: +- +- raw_attachments = self._attachments.GetValue() +- +- attachment_hashes = [ hash.decode( 'hex' ) for hash in raw_attachments.split( os.linesep ) if hash != '' ] +- +- except: +- +- attachment_hashes = [] +- +- wx.MessageBox( 'Could not parse attachments!' ) +- +- +- return ClientConstantsMessages.DraftMessage( self._draft_key, self._conversation_key, subject, self._contact_from, contacts_to, recipients_visible, body, attachment_hashes ) +- +- +- def AddContact( self, compose_key, name ): +- +- if compose_key == self._compose_key: +- +- index = self._recipients_list.FindItem( -1, name ) +- +- if index == -1: self._recipients_list.Append( ( name, ) ) +- else: self._recipients_list.DeleteItem( index ) +- +- self.EventChanged( None ) +- +- +- +- def DraftSaved( self, draft_key, draft_message ): +- +- if draft_key == self._draft_key: +- +- self._draft_changed = False +- +- self._save_draft.SetLabel( 'saved' ) +- self._save_draft.Disable() +- +- self._delete_draft.SetLabel( 'delete' ) +- +- self._draft_message.Saved() +- +- +- +- def EventChanged( self, event ): +- +- if not self._draft_changed: +- +- self._draft_changed = True +- +- self._send.Enable() +- self._save_draft.Enable() +- +- +- if event is not None: event.Skip() +- +- +- def EventDeleteDraft( self, event ): wx.GetApp().Write( 'delete_draft', self._draft_key ) +- +- def EventSend( self, event ): +- +- draft_message = self._GetDraftMessage() +- +- transport_messages = wx.GetApp().Read( 'transport_messages_from_draft', draft_message ) +- +- if self._contact_from.GetName() != 'Anonymous': +- +- try: +- +- my_message_depot = wx.GetApp().GetManager( 'services' ).GetService( self._contact_from.GetServiceKey() ) +- +- connection = my_message_depot.GetConnection() +- +- my_public_key = self._contact_from.GetPublicKey() +- my_contact_key = self._contact_from.GetContactKey() +- +- for transport_message in transport_messages: +- +- packaged_message = HydrusMessageHandling.PackageMessageForDelivery( transport_message, my_public_key ) +- +- connection.Post( 'message', contact_key = my_contact_key, message = packaged_message ) +- +- message_key = transport_message.GetMessageKey() +- +- status_updates = [] +- +- for contact_to in transport_message.GetContactsTo(): +- +- contact_to_key = contact_to.GetContactKey() +- +- status_key = hashlib.sha256( contact_to_key + message_key ).digest() +- +- status = HydrusMessageHandling.PackageStatusForDelivery( ( message_key, contact_to_key, 'pending' ), my_public_key ) +- +- status_updates.append( ( status_key, status ) ) +- +- +- connection.Post( 'message_statuses', contact_key = my_contact_key, statuses = status_updates ) +- +- +- except: +- +- HydrusData.ShowText( 'The hydrus client could not connect to your message depot, so the message could not be sent!' ) +- +- return +- +- +- +- for transport_message in transport_messages: wx.GetApp().Write( 'message', transport_message, forced_status = 'pending' ) +- +- draft_key = draft_message.GetDraftKey() +- +- wx.GetApp().Write( 'delete_draft', draft_key ) +- +- +- def EventSaveDraft( self, event ): +- +- draft_message = self._GetDraftMessage() +- +- wx.GetApp().Write( 'draft_message', draft_message ) +- +- +- def EventRemove( self, event ): +- +- selection = self._recipients_list.GetFirstSelected() +- +- if selection != wx.NOT_FOUND: +- +- self._recipients_list.DeleteItem( selection ) +- +- self.EventChanged( None ) +- +- +- +- def GetConversationKey( self ): return self._conversation_key +- +- def GetDraftKey( self ): return self._draft_key +- +-class MessageHTML( wx.html.HtmlWindow ): +- +- def __init__( self, *args, **kwargs ): +- +- kwargs[ 'style' ] = wx.html.HW_SCROLLBAR_NEVER +- +- wx.html.HtmlWindow.__init__( self, *args, **kwargs ) +- +- self.Bind( wx.EVT_MOUSEWHEEL, self.EventScroll ) +- +- self.SetRelatedFrame( wx.GetTopLevelParent( self ), '%s' ) +- self.SetRelatedStatusBar( 0 ) +- +- +- def EventScroll( self, event ): +- +- sw = self.GetParent().GetParent() +- +- sw.GetEventHandler().ProcessEvent( event ) +- +- +- def GetClientSize( self ): return self.GetSize() +- +- def OnLinkClicked( self, link ): webbrowser.open( link.GetHref() ) +- +- def OnOpeningURL( self, url_type, url, redirect ): return wx.html.HTML_BLOCK +- +-class MessagePanel( wx.Panel ): +- +- def __init__( self, parent, message, identity ): +- +- wx.Panel.__init__( self, parent ) +- +- self.SetBackgroundColour( CC.COLOUR_MESSAGE ) +- +- self._message = message +- self._identity = identity +- +- vbox = wx.BoxSizer( wx.VERTICAL ) +- +- contact_from = self._message.GetContactFrom() +- +- if contact_from is None: name = 'Anonymous' +- else: name = self._message.GetContactFrom().GetName() +- +- #vbox.AddF( wx.StaticText( self, label = name + ', ' + HC.ConvertTimestampToPrettyAgo( self._message.GetTimestamp() ) ), CC.FLAGS_EXPAND_PERPENDICULAR ) +- vbox.AddF( ClientGUICommon.AnimatedStaticTextTimestamp( self, name + ', ', HydrusData.ConvertTimestampToPrettyAgo, self._message.GetTimestamp(), '' ), CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- body = self._message.GetBody() +- +- display_body = not ( body is None or body == '' ) +- +- if display_body: +- +- self._body_panel = wx.Panel( self ) +- +- wx.CallAfter( self.SetBody, body ) +- +- else: self._body_panel = wx.StaticText( self, label = 'no body' ) +- +- self._message_key = self._message.GetMessageKey() +- destinations = self._message.GetDestinations() +- +- self._destinations_panel = DestinationsPanel( self, self._message_key, destinations, identity ) +- +- self._hbox = wx.BoxSizer( wx.HORIZONTAL ) +- +- self._hbox.AddF( self._body_panel, CC.FLAGS_EXPAND_BOTH_WAYS ) +- self._hbox.AddF( self._destinations_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- vbox.AddF( self._hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS ) +- +- # vbox.AddF( some kind of attachment window! ) +- +- self.SetSizer( vbox ) +- +- +- def SetBody( self, body ): +- +- with wx.FrozenWindow( self ): +- +- ( width, height ) = self._body_panel.GetClientSize() +- +- body_panel = MessageHTML( self, size = ( width, -1 ) ) +- body_panel.SetPage( body ) +- +- internal = body_panel.GetInternalRepresentation() +- +- body_panel.SetSize( ( -1, internal.GetHeight() ) ) +- +- self._hbox.Replace( self._body_panel, body_panel ) +- +- self._body_panel.Close() +- +- self._body_panel = body_panel +- +- +- self.Layout() +- self.GetParent().FitInside() +- +- +- +-# here starts the message reboot code +- +-class IMFrame( ClientGUICommon.Frame ): +- +- def __init__( self, parent, me_account, them_account, context ): +- +- def InitialiseControls(): +- +- self._me_label = MeLabel( self, me_account ) # maybe these two should be the same, and infer me/them status itself +- self._them_label = ThemLabel( self, them_account ) +- self._convo_box = ConvoBox( self, context_key ) # something like this +- self._text_input = ConvoTextInput( self, callable ) # callable should be private method of this, or similar! +- +- +- def PopulateControls(): +- +- # could introduce last convo here, or whatever. +- +- pass +- +- +- def ArrangeControls(): +- +- hbox = wx.BoxSizer( wx.HORIZONTAL ) +- +- hbox.AddF( self._me_label, CC.FLAGS_MIXED ) +- hbox.AddF( wx.StaticText( self, label = ' talking to ' ), CC.FLAGS_MIXED ) +- hbox.AddF( self._them_label, CC.FLAGS_MIXED ) +- +- vbox = wx.BoxSizer( wx.VERTICAL ) +- vbox.AddF( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) +- vbox.AddF( self._convo_box, CC.FLAGS_EXPAND_BOTH_WAYS ) +- vbox.AddF( self._text_input, CC.FLAGS_EXPAND_PERPENDICULAR ) +- +- self.SetSizer( vbox ) +- +- self.SetInitialSize( ( 400, 600 ) ) # this should be remembered, stuck in options +- +- +- me_name = me_account.GetNameBlah() +- them_name = them_account.GetNameBlah() +- +- ClientGUICommon.Frame.__init__( self, parent, title = me_name + ' talking to ' + them_name ) +- +- InitialiseControls() +- +- PopulateControls() +- +- ArrangeControls() +- +- self.Show( True ) +- +- +- def TextInputCallable( self, text ): +- +- pass +- +- # send it to the context, which will report it +- +- ''' +\ No newline at end of file +diff --git a/include/ClientGUIPages.py b/include/ClientGUIPages.py +index 8aae01e..8e8b36e 100755 +--- a/include/ClientGUIPages.py ++++ b/include/ClientGUIPages.py +@@ -90,59 +90,6 @@ class PageBase( object ): + + HydrusGlobals.pubsub.pub( 'resume', self._page_key ) + +- ''' +-class PageMessages( PageBase, wx.SplitterWindow ): +- +- def __init__( self, parent, identity, starting_from_session = False ): +- +- wx.SplitterWindow.__init__( self, parent ) +- PageBase.__init__( self, starting_from_session = starting_from_session ) +- +- self.SetMinimumPaneSize( 120 ) +- self.SetSashGravity( 0.0 ) +- +- self._identity = identity +- +- self._search_preview_split = wx.SplitterWindow( self, style=wx.SP_NOBORDER ) +- +- self._search_preview_split.SetMinimumPaneSize( 180 ) +- self._search_preview_split.SetSashGravity( 0.5 ) +- +- self._search_preview_split.Bind( wx.EVT_SPLITTER_DCLICK, self.EventPreviewUnsplit ) +- +- self._InitManagementPanel() +- self._preview_panel = ClientGUICanvas.CanvasPanel( self._search_preview_split, self._page_key, CC.LOCAL_FILE_SERVICE_KEY ) +- self._InitMessagesPanel() +- +- self.SplitVertically( self._search_preview_split, self._messages_panel, HC.options[ 'hpos' ] ) +- wx.CallAfter( self._search_preview_split.SplitHorizontally, self._management_panel, self._preview_panel, HC.options[ 'vpos' ] ) +- +- +- def _InitManagementPanel( self ): self._management_panel = ClientGUIManagement.ManagementPanelMessages( self._search_preview_split, self._page_key, self._identity, starting_from_session = self._starting_from_session ) +- +- def _InitMessagesPanel( self ): self._messages_panel = ClientGUIMessages.ConversationSplitter( self, self._page_key, self._identity ) +- +- def EventPreviewUnsplit( self, event ): self._search_preview_split.Unsplit( self._preview_panel ) +- +- def GetSashPositions( self ): +- +- if self.IsSplit(): x = self.GetSashPosition() +- else: x = HC.options[ 'hpos' ] +- +- if self._search_preview_split.IsSplit(): y = -1 * self._preview_panel.GetSize()[1] +- else: y = HC.options[ 'vpos' ] +- +- return ( x, y ) +- +- +- def ShowHideSplit( self ): +- +- if self._search_preview_split.IsSplit(): self._search_preview_split.Unsplit( self._preview_panel ) +- else: self._search_preview_split.SplitHorizontally( self._management_panel, self._preview_panel, HC.options[ 'vpos' ] ) +- +- +- def TestAbleToClose( self ): self._management_panel.TestAbleToClose() +- ''' + class PageWithMedia( PageBase, wx.SplitterWindow ): + + def __init__( self, parent, file_service_key = CC.LOCAL_FILE_SERVICE_KEY, initial_hashes = None, initial_media_results = None, starting_from_session = False ): +diff --git a/include/ClientImporting.py b/include/ClientImporting.py +deleted file mode 100644 +index c205af2..0000000 +--- a/include/ClientImporting.py ++++ /dev/null +@@ -1,553 +0,0 @@ +-import ClientConstants as CC +-import HydrusConstants as HC +-import HydrusData +-import HydrusSerialisable +-import threading +-import traceback +- +-class ImportController( HydrusSerialisable.SerialisableBase ): +- +- def __init__( self ): +- +- HydrusSerialisable.SerialisableBase.__init__( self ) +- +- # queues of stuff, where every kind of queue can be serialised +- # hence don't have __init__ for subclasses! nothing beyond temp vars +- # everything must fit inside what I declare here +- # subclasses should mostly fill in _ProcessQueue kind of stuff. +- # don't forget THREAD stuff, which should probably be explicitly started by the managementpanel? +- # also, what about page_key? how are we reporting new imports and so on? maybe that can be temp var in the daemonspawner +- +- # hence maybe make a 'queue' object representing a list of urls or whatever -- maybe a urlcache can do that job. +- +- # a number of queues +- # a thing that extends a queue using a search +- # a list of searches that will be built into queues +- +- # maybe some class variables saying what parts to engage, like HDD doesn't accept new queues and so on +- +- self._file_status_counts = {} +- +- # if I decide to link search_seeds to the import_seed_queues, then why not bundle the import_seed_queue into the search_seed_info? +- # yes, this is a good idea. +- +- self._import_seed_queues = [] +- self._importer_status = ( '', 0, 1 ) +- +- self._search_seeds = SeedQueue() +- self._searcher_status = ( '', 0, 1 ) +- +- self._options = {} +- +- self._lock = threading.Lock() +- self._import_status = '' +- +- +- def _GetSerialisableInfo( self ): +- +- # collapse file status counts into a list because of stupid int dict json thing +- +- serialisable_url_cache = HydrusSerialisable.DumpToTuple( self._url_cache ) +- +- serialisable_options = { name : HydrusSerialisable.DumpToTuple( options ) for ( name, options ) in self._options.items() } +- +- return ( self._site_type, self._query_type, self._query, self._get_tags_if_redundant, serialisable_url_cache, serialisable_options ) +- +- +- def _InitialiseFromSerialisableInfo( self, serialisable_info ): +- +- ( self._site_type, self._query_type, self._query, self._get_tags_if_redundant, serialisable_url_cache_tuple, serialisable_options_tuple ) = serialisable_info +- +- self._url_cache = HydrusSerialisable.CreateFromTuple( serialisable_url_cache_tuple ) +- +- self._options = { name : HydrusSerialisable.CreateFromTuple( serialisable_suboptions_tuple ) for ( name, serialisable_suboptions_tuple ) in serialisable_options_tuple.items() } +- +- +- def _ProcessImportSeed( self, seed, seed_info ): +- +- raise NotImplementedError() +- +- +- def _ProcessSearchSeed( self, seed, seed_info ): +- +- raise NotImplementedError() +- +- +- def _DAEMONProcessImportSeeds( self ): +- +- while True: +- +- # if importer paused +- +- with self._lock: +- +- result = None +- +- # determine paused/cancelled status via searchseedqueue or whatever +- +- for import_seed_queue in self._import_seed_queues: +- +- result = import_seed_queue.GetNextUnknownSeed() +- +- if result is not None: +- +- # remember current import_seed_queue so we can set seed status later +- +- break +- +- +- +- +- if result is not None: +- +- ( seed, seed_info ) = result +- +- self._ProcessImportSeed( import_seed, seed_info ) +- +- +- +- +- def _DAEMONProcessSearchSeeds( self ): +- +- while True: +- +- # if searcher paused +- +- with self._lock: +- +- result = import_seed_queue.GetNextUnknownSeed() +- +- +- +- if result is not None: +- +- ( seed, seed_info ) = result +- +- self._ProcessSearchSeed( seed, seed_info ) +- +- +- +- +- def GetOptions( self, name ): +- +- with self._lock: +- +- return self._options[ name ] +- +- +- +- def GetStatuses( self ): +- +- with self._lock: +- +- return ( dict( self._file_status_counts ), self._import_status, self._current_queue_status, self._searcher_status ) +- +- +- +- def PauseSearcher( self ): +- +- with self._lock: +- +- self._searcher_paused = True +- +- +- +- def ResumeCurrentQueue( self ): +- +- with self._lock: +- +- self._current_queue_paused = False +- +- +- +- def ResumeSearcher( self ): +- +- with self._lock: +- +- self._searcher_paused = False +- +- +- +- def SetOptions( self, name, options ): +- +- with self._lock: +- +- self._options[ name ] = options +- +- +- +-class ImportControllerHDD( HydrusSerialisable.SerialisableBase ): +- +- SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_HDD_IMPORT +- SERIALISABLE_VERSION = 1 +- +- def __init__( self ): +- +- HydrusSerialisable.SerialisableBase.__init__( self ) +- +- # this stuff is all moved to the search seed +- self._paths_info = None +- self._paths_to_tags = None +- self._delete_file_after_import = None +- self._import_file_options = None +- +- self._lock = threading.Lock() +- +- +- def _GetSerialisableInfo( self ): +- +- serialisable_url_cache = HydrusSerialisable.DumpToTuple( self._url_cache ) +- +- serialisable_options = { name : HydrusSerialisable.DumpToTuple( options ) for ( name, options ) in self._options.items() } +- +- return ( self._site_type, self._query_type, self._query, self._get_tags_if_redundant, serialisable_url_cache, serialisable_options ) +- +- +- def _InitialiseFromSerialisableInfo( self, serialisable_info ): +- +- ( self._site_type, self._query_type, self._query, self._get_tags_if_redundant, serialisable_url_cache_tuple, serialisable_options_tuple ) = serialisable_info +- +- self._url_cache = HydrusSerialisable.CreateFromTuple( serialisable_url_cache_tuple ) +- +- self._options = { name : HydrusSerialisable.CreateFromTuple( serialisable_suboptions_tuple ) for ( name, serialisable_suboptions_tuple ) in serialisable_options_tuple.items() } +- +- +- def GetImportStatus( self ): +- +- with self._lock: +- +- return self._import_status +- +- +- +- def GetQueueStatus( self ): +- +- with self._lock: +- +- gauge_value = self._current_position +- gauge_range = len( self._paths_info ) +- +- # return progress string +- # also return string for num_successful and so on +- +- pass +- +- +- +- def MainLoop( self ): +- +- # use the lock sparingly, remember +- # obey pause and hc.shutdown +- # maybe also an internal shutdown, on managementpanel cleanupbeforedestroy +- # update file_status_counts +- # increment current_position +- +- pass +- +- +- def Pause( self ): +- +- with self._lock: +- +- self._paused = True +- +- +- +- def Resume( self ): +- +- with self._lock: +- +- self._paused = False +- +- +- +- def SetTuple( self, paths_info, paths_to_tags, delete_file_after_import, import_file_options ): +- +- self._paths_info = paths_info +- self._paths_to_tags = paths_to_tags +- self._delete_file_after_import = delete_file_after_import +- self._import_file_options = import_file_options +- +- +- def Start( self ): +- +- # init a daemon to work through the list +- +- pass +- +- +-HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_HDD_IMPORT ] = HDDImport +- +-class GalleryQuery( HydrusSerialisable.SerialisableBase ): +- +- SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_GALLERY_QUERY +- SERIALISABLE_VERSION = 1 +- +- def __init__( self, name ): +- +- HydrusSerialisable.SerialisableBase.__init__( self ) +- +- self._site_type = None +- self._query_type = None +- self._query = None +- self._get_tags_if_redundant = False +- self._file_limit = 500 +- self._paused = False +- self._page_index = 0 +- self._url_cache = None +- self._options = {} +- +- +- def _GetSerialisableInfo( self ): +- +- serialisable_url_cache = HydrusSerialisable.DumpToTuple( self._url_cache ) +- +- serialisable_options = { name : HydrusSerialisable.DumpToTuple( options ) for ( name, options ) in self._options.items() } +- +- return ( self._site_type, self._query_type, self._query, self._get_tags_if_redundant, self._file_limit, serialisable_url_cache, serialisable_options ) +- +- +- def _InitialiseFromSerialisableInfo( self, serialisable_info ): +- +- ( self._site_type, self._query_type, self._query, self._get_tags_if_redundant, serialisable_url_cache_tuple, serialisable_options_tuple ) = serialisable_info +- +- self._url_cache = HydrusSerialisable.CreateFromTuple( serialisable_url_cache_tuple ) +- +- self._options = { name : HydrusSerialisable.CreateFromTuple( serialisable_suboptions_tuple ) for ( name, serialisable_suboptions_tuple ) in serialisable_options_tuple.items() } +- +- +- def GetQuery( self ): +- +- return self._query +- +- +- def SetTuple( self, site_type, query_type, query, get_tags_if_redundant, file_limit, options ): +- +- self._site_type = site_type +- self._query_type = query_type +- self._query = query +- self._get_tags_if_redundant = get_tags_if_redundant +- self._file_limit = file_limit +- self._url_cache = URLCache() +- self._options = options +- +- +-HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_GALLERY_QUERY ] = GalleryQuery +- +-class SubscriptionController( HydrusSerialisable.SerialisableBaseNamed ): +- +- SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION +- SERIALISABLE_VERSION = 1 +- +- def __init__( self, name ): +- +- HydrusSerialisable.SerialisableBaseNamed.__init__( self, name ) +- +- self._site_type = None +- self._query_type = None +- self._query = None +- self._get_tags_if_redundant = False +- self._file_limit = 500 +- self._periodic = None +- self._page_index = 0 +- self._url_cache = None +- self._options = {} +- +- +- def _GetSerialisableInfo( self ): +- +- return ( HydrusSerialisable.DumpToTuple( self._gallery_query ), HydrusSerialisable.DumpToTuple( self._periodic ) ) +- +- +- def _InitialiseFromSerialisableInfo( self, serialisable_info ): +- +- ( serialised_gallery_query_tuple, serialised_periodic_tuple ) = serialisable_info +- +- self._gallery_query = HydrusSerialisable.CreateFromTuple( serialised_gallery_query_tuple ) +- +- self._periodic = HydrusSerialisable.CreateFromTuple( serialised_periodic_tuple ) +- +- +-HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION ] = Subscription +- +-class SeedQueue( HydrusSerialisable.SerialisableBase ): +- +- SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SEED_QUEUE +- SERIALISABLE_VERSION = 1 +- +- def __init__( self ): +- +- HydrusSerialisable.SerialisableBase.__init__( self ) +- +- self._seeds_ordered = [] +- self._seeds_to_info = {} +- +- self._lock = threading.Lock() +- +- +- def _GetSerialisableInfo( self ): +- +- with self._lock: +- +- serialisable_info = [] +- +- for seed in self._seeds_ordered: +- +- seed_info = self._seeds_to_info[ seed ] +- +- serialisable_info.append( ( seed, seed_info ) ) +- +- +- return serialisable_info +- +- +- +- def _InitialiseFromSerialisableInfo( self, serialisable_info ): +- +- with self._lock: +- +- for ( seed, seed_info ) in serialisable_info: +- +- self._seeds_ordered.append( seed ) +- +- self._seeds_to_info[ seed ] = seed_info +- +- +- +- +- def AddSeed( self, seed, additional_info = None ): +- +- with self._lock: +- +- if seed in self._seeds_to_info: +- +- self._seeds_ordered.remove( seed ) +- +- +- self._seeds_ordered.append( seed ) +- +- seed_info = {} +- +- seed_info[ 'status' ] = CC.STATUS_UNKNOWN +- seed_info[ 'timestamp' ] = HydrusData.GetNow() +- seed_info[ 'note' ] = '' +- +- if additional_info is not None: +- +- seed_info.update( additional_info ) +- +- +- self._seeds_to_info[ seed ] = seed_info +- +- +- +- +- def AdvanceSeed( self, seed ): +- +- with self._lock: +- +- if seed in self._seeds_to_info: +- +- index = self._seeds_ordered.index( seed ) +- +- if index > 0: +- +- self._seeds_ordered.remove( seed ) +- +- self._seeds_ordered.insert( index - 1, seed ) +- +- +- +- +- +- def DelaySeed( self, seed ): +- +- with self._lock: +- +- if seed in self._seeds_to_info: +- +- index = self._seeds_ordered.index( seed ) +- +- if index < len( self._seeds_ordered ) - 1: +- +- self._seeds_ordered.remove( seed ) +- +- self._seeds_ordered.insert( index + 1, seed ) +- +- +- +- +- +- def GetNextUnknownSeed( self ): +- +- with self._lock: +- +- for seed in self._seeds_ordered: +- +- seed_info = self._seeds_to_info[ seed ] +- +- if seed_info[ 'status' ] == CC.STATUS_UNKNOWN: +- +- return ( seed, seed_info ) +- +- +- +- +- return None +- +- +- def GetSeeds( self ): +- +- with self._lock: +- +- return list( self._seeds_ordered ) +- +- +- +- def GetSeedsDisplayInfo( self ): +- +- with self._lock: +- +- all_info = [] +- +- for seed in self._seeds_ordered: +- +- seed_info = self._seeds_to_info[ seed ] +- +- timestamp = seed_info[ 'timestamp' ] +- status = seed_info[ 'status' ] +- note = seed_info[ 'note' ] +- +- all_info.append( ( seed, status, timestamp, note ) ) +- +- +- return all_info +- +- +- +- def RemoveSeed( self, seed ): +- +- with self._lock: +- +- if seed in self._seeds_to_info: +- +- del self._seeds_to_info[ seed ] +- +- self._seeds_ordered.remove( seed ) +- +- +- +- +- def SetSeedStatus( self, seed, status, note = '' ): +- +- with self._lock: +- +- seed_info = self._seeds_to_info[ seed ] +- +- seed_info[ 'status' ] = status +- seed_info[ 'timestamp' ] = HydrusData.GetNow() +- seed_info[ 'note' ] = note +- +- +- +-HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SEED_QUEUE ] = SeedQueue +diff --git a/include/HydrusData.py b/include/HydrusData.py +index 4ab9f67..259f478 100644 +--- a/include/HydrusData.py ++++ b/include/HydrusData.py +@@ -963,15 +963,15 @@ class ClientToServerContentUpdatePackage( HydrusYAMLBase ): + + if data_type in ( HC.CONTENT_DATA_TYPE_TAG_SIBLINGS, HC.CONTENT_DATA_TYPE_TAG_PARENTS ) and action in ( HC.CONTENT_UPDATE_PENDING, HC.CONTENT_UPDATE_PETITION ): + +- munge_row = lambda ( pair, reason ): pair ++ munge_row = lambda tup: tup[0] + + elif data_type == HC.CONTENT_DATA_TYPE_FILES and action == HC.CONTENT_UPDATE_PETITION: + +- munge_row = lambda ( hashes, reason ): hashes ++ munge_row = lambda tup: tup[0] + + elif data_type == HC.CONTENT_DATA_TYPE_MAPPINGS and action == HC.CONTENT_UPDATE_PETITION: + +- munge_row = lambda ( tag, hashes, reason ): ( tag, hashes ) ++ munge_row = lambda tup: tup[:-1] + + + else: new_action = action +diff --git a/include/HydrusImageHandling.py b/include/HydrusImageHandling.py +index 97c2fca..a69fe4e 100755 +--- a/include/HydrusImageHandling.py ++++ b/include/HydrusImageHandling.py +@@ -50,7 +50,8 @@ def ConvertToPngIfBmp( path ): + + + +-def EfficientlyResizeNumpyImage( numpy_image, ( target_x, target_y ) ): ++def EfficientlyResizeNumpyImage( numpy_image, target_dims ): ++ ( target_x, target_y ) = target_dims + + ( im_y, im_x, depth ) = numpy_image.shape + +@@ -63,7 +64,8 @@ def EfficientlyResizeNumpyImage( numpy_image, ( target_x, target_y ) ): + + return cv2.resize( result, ( target_x, target_y ), interpolation = cv2.INTER_LINEAR ) + +-def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ): ++def EfficientlyResizePILImage( pil_image, target_dims ): ++ ( target_x, target_y ) = target_dims + + ( im_x, im_y ) = pil_image.size + +@@ -76,7 +78,8 @@ def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ): + + return pil_image.resize( ( target_x, target_y ), PILImage.ANTIALIAS ) + +-def EfficientlyThumbnailNumpyImage( numpy_image, ( target_x, target_y ) ): ++def EfficientlyThumbnailNumpyImage( numpy_image, target_dims ): ++ ( target_x, target_y ) = target_dims + + ( im_y, im_x, depth ) = numpy_image.shape + +@@ -86,7 +89,8 @@ def EfficientlyThumbnailNumpyImage( numpy_image, ( target_x, target_y ) ): + + return cv2.resize( numpy_image, ( target_x, target_y ), interpolation = cv2.INTER_AREA ) + +-def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ): ++def EfficientlyThumbnailPILImage( pil_image, target_dims ): ++ ( target_x, target_y ) = target_dims + + ( im_x, im_y ) = pil_image.size + +@@ -348,7 +352,9 @@ def GetResolutionAndNumFrames( path ): + + return ( ( x, y ), num_frames ) + +-def GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) ): ++def GetThumbnailResolution( im_dims, target_dims ): ++ ( im_x, im_y ) = im_dims ++ ( target_x, target_y ) = target_dims + + im_x = float( im_x ) + im_y = float( im_y ) +diff --git a/include/HydrusMessageHandling.py b/include/HydrusMessageHandling.py +index 1b29013..2ce6c3b 100755 +--- a/include/HydrusMessageHandling.py ++++ b/include/HydrusMessageHandling.py +@@ -162,237 +162,3 @@ class Message( HydrusData.HydrusYAMLBase ): + verifier = Crypto.Signature.PKCS1_v1_5.new( public_key ) + + return verifier.verify( hash_object, self._signature ) +- +- +-# here begins the new stuff, I'm pretty sure +- +-class Identity( object ): # should be a yamlable object +- +- def __init__( self ): +- +- # no name, right? we associate names and addresses with the identity, but the id only has keys +- +- # store key_type -> key +- # hence need a key_type enum +- +- pass +- +- +-class IMManager( object ): +- +- def __init__( self ): +- +- self._accounts = {} +- self._contexts = {} +- self._persistent_connections = {} +- self._temporary_connections = {} +- +- # go fetch all accounts from the db +- +- # set up many pubsubs +- +- # start up some sort of daemon to keep our accounts logged in +- +- pass +- +- +- def _GetContext( self, identifier_local, name_local, identifier_remote, name_remote ): +- +- if ( identifier_remote, name_remote ) not in self._contexts[ identifier_local ]: +- +- account = self._accounts[ ( identifier_local, name_local ) ] +- +- context = HydrusEncryption.HydrusOTRContext( account, identifier_remote, name_remote ) +- +- self._contexts[ ( identifier_local, name_local, identifier_remote, name_remote ) ] = context +- +- +- context = self._contexts[ identifier_local ][ ( identifier_remote, name_remote ) ] +- +- return context +- +- +- def LoginPersistentConnections( self ): +- +- # this is on a daemon thread, so move to twisted +- +- for ( identifier, name ) in self._accounts.keys(): +- +- if ( identifier, name ) not in self._persistent_connections: +- +- # get host, port for that identity +- +- creator = ClientCreator( reactor, HydrusServerAMP.MessagingClientProtocol ) +- +- deferred = creator.connectTCP( host, port ) +- +- # deferred is called with the connection, or an error +- # callRemote to register with session key and whatnot +- +- self._persistent_connections[ ( identifier, name ) ] = connection +- +- +- +- +- def ReceiveMessage( self, identifier_from, name_from, identifier_to, name_to, message ): +- +- # currently on wx loop +- # move it to the twisted loop +- +- if ( identifier_from, name_from, identifier_to, name_to ) not in self._temporary_connections: +- +- self._temporary_connections[ ( identifier_from, name_from, identifier_to, name_to ) ] = self._persistent_connections[ ( identifier_to, name_to ) ] +- # this should have a better error, if the _to doesn't exist +- # we should really just disregard it, and any other weirdness +- +- +- context = self._GetContext( identifier_to, name_to, identifier_from, name_from ) +- +- response = context.receiveMessage( message ) +- +- if response is not None: +- +- ( decrypted_message, gumpf ) = response +- +- message_object = yaml.safe_load( decrypted_message ) +- +- # do the pubsub +- +- +- +- def RemovePersistentConnection( self, identifier, name ): +- +- # if it is still alive, loseConnection or whatever. +- # remove it +- # pubsub the login daemon +- +- pass +- +- +- def RemoveTemporaryConnection( self, identifier_from, name_from, identifier_to, name_to ): +- +- # if it is still alive, loseConnection or whatever. +- # remove it +- +- pass +- +- +- def SendMessage( self, identifier_from, name_from, identifier_to, name_to, message ): +- +- context = self._GetContext( identifier_from, name_from, identifier_to, name_to ) +- +- context.sendMessage( potr.context.FRAGMENT_SEND_ALL, message ) +- +- +- def SendEncryptedMessage( self, identifier_from, name_from, identifier_to, name_to, message ): +- +- # currently on wx loop +- # move it to the twisted loop +- +- connection = self._temporary_connections[ ( identifier_from, name_from, identifier_to, name_to ) ] +- +- connection.callRemote( HydrusServerAMP.IMMessageServer, identifier_to = identifier_to, name_to = name_to, message = message ) +- +- # if it breaks, we should pubsub that it broke +- +- +- def StartTalking( self, identifier_from, name_from, identifier_to, name_to ): +- +- # currently on wx loop +- # move it to the twisted loop +- +- # fetch host and port for that id +- +- creator = ClientCreator( reactor, HydrusServerAMP.MessagingClientProtocol ) +- +- deferred = creator.connectTCP( host, port ) +- +- # deferred is called with the connection, or an error +- # callRemote to register identifier_from and name_from as temp login +- # then add to temp_connections +- +- self._temporary_connections[ ( identifier_from, name_from, identifier_to, name_to ) ] = connection +- +- message = '' # this is just to get the OTR handshake going; it'll never be sent +- +- connection.callRemote( HydrusServerAMP.IMMessageServer, identifier_to = identifier_to, name_to = name_to, message = message ) +- +- # how do I detect when we are ready to do encrypted comms? +- # I can check periodically context.status, but that is a _little_ bleh +- # I can write a pubsub in the setStatus thing in context +- # check that article again, or the code, on the exact name +- +- # do a pubsub to say we are ready to do encrypted comms +- +- # if it fails, we should pubsub that it broke +- +- +- def StopTalking( self, identifier, name ): +- +- # close temp connection +- # +- +- pass +- +- +-class IMMessage( HydrusData.HydrusYAMLBase ): +- +- yaml_tag = u'!IMMessage' +- +-class IMMessageQuestion( IMMessage ): +- +- yaml_tag = u'!IMMessageQuestion' +- +- def __init__( self, job_key = None ): +- +- if job_key is None: job_key = os.urandom( 32 ) +- +- self._job_key = job_key +- +- +- def GenerateAnswer( self, answer ): +- +- return IMMessageQuestionAnswer( self._job_key, answer ) +- +- +- def GetJobKey( self ): return self._job_key +- +-class IMMessageQuestionAnswer( IMMessageQuestion ): +- +- yaml_tag = u'!IMMessageQuestionAnswer' +- +- def __init__( self, job_key, answer ): +- +- IMMessageQuestion.__init__( self, job_key ) +- +- self._answer = answer +- +- +- def GetAnswer( self ): return self._answer +- +-class IMMessageQuestionFiles( IMMessageQuestion ): +- +- yaml_tag = u'!IMMessageFiles' +- +- def __init__( self, media_results ): +- +- IMMessageQuestion.__init__( self ) +- +- self._text = text +- +- +-IM_MESSAGE_TYPE_CONVO = 0 +-IM_MESSAGE_TYPE_STATUS = 1 +- +-class IMMessageText( IMMessage ): +- +- yaml_tag = u'!IMMessageText' +- +- def __init__( self, message_type, text ): +- +- self._type = message_type +- self._text = text +- +- +- def ToTuple( self ): return ( self._type, self._text ) +- +\ No newline at end of file +diff --git a/include/HydrusSerialisable.py b/include/HydrusSerialisable.py +index 473cfe9..533f55c 100644 +--- a/include/HydrusSerialisable.py ++++ b/include/HydrusSerialisable.py +@@ -130,4 +130,4 @@ class SerialisableBaseNamed( SerialisableBase ): + def GetName( self ): return self._name + + def SetName( self, name ): self._name = name +- +\ No newline at end of file ++ +diff --git a/include/HydrusTags.py b/include/HydrusTags.py +index dee1b78..413c6f0 100644 +--- a/include/HydrusTags.py ++++ b/include/HydrusTags.py +@@ -352,7 +352,7 @@ class TagsManagerSimple( object ): + combined_current = combined_statuses_to_tags[ HC.CURRENT ] + combined_pending = combined_statuses_to_tags[ HC.PENDING ] + +- slice = { tag for tag in combined_current.union( combined_pending ) if True in ( tag.startswith( namespace + ':' ) for namespace in namespaces ) } ++ slice = { tag for tag in combined_current.union( combined_pending ) if True in (lambda tag:( tag.startswith( namespace + ':' ) for namespace in namespaces ))(tag) } + + if collapse_siblings: + diff --git a/hydrus-client b/hydrus-client new file mode 100644 index 000000000000..003a2adc0b53 --- /dev/null +++ b/hydrus-client @@ -0,0 +1,2 @@ +#!/bin/sh +exec python2 -OO /opt/hydrus/client.pyw "$@" diff --git a/hydrus-server b/hydrus-server new file mode 100644 index 000000000000..66f4bbc158f3 --- /dev/null +++ b/hydrus-server @@ -0,0 +1,2 @@ +#!/bin/sh +exec python2 -OO /opt/hydrus/server.pyw "$@" diff --git a/hydrus.desktop b/hydrus.desktop new file mode 100644 index 000000000000..518a6037019c --- /dev/null +++ b/hydrus.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=148 +Name=Hydrus Client +Comment=A booru-like media organizer for the desktop +Exec=hydrus-client +Icon=/opt/hydrus/static/hydrus_non-transparent.png +Terminal=false +Type=Application +Categories=Application;FileTools;Graphics;Network; diff --git a/hydrus.install b/hydrus.install new file mode 100644 index 000000000000..e111ef946053 --- /dev/null +++ b/hydrus.install @@ -0,0 +1,11 @@ +post_install() { + update-desktop-database -q +} + +post_upgrade() { + post_install +} + +post_remove() { + post_install +} diff --git a/paths-in-opt.patch b/paths-in-opt.patch new file mode 100644 index 000000000000..5e50d3b4f954 --- /dev/null +++ b/paths-in-opt.patch @@ -0,0 +1,121 @@ +diff --git a/include/ClientCaches.py b/include/ClientCaches.py +index 4dd2292..9e0e3ee 100644 +--- a/include/ClientCaches.py ++++ b/include/ClientCaches.py +@@ -486,4 +486,4 @@ class ThumbnailCache( object ): + + + +- +\ No newline at end of file ++ +diff --git a/include/ClientGUI.py b/include/ClientGUI.py +index 96fef72..ea9f71f 100755 +--- a/include/ClientGUI.py ++++ b/include/ClientGUI.py +@@ -131,7 +131,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ): + aboutinfo.SetVersion( HydrusData.ToString( HC.SOFTWARE_VERSION ) ) + aboutinfo.SetDescription( CC.CLIENT_DESCRIPTION ) + +- with open( HC.BASE_DIR + os.path.sep + 'license.txt', 'rb' ) as f: license = f.read() ++ with open( '/usr/share/licenses/hydrus/license.txt', 'rb' ) as f: license = f.read() + + aboutinfo.SetLicense( license ) + +@@ -1884,7 +1884,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p + + elif command == '8chan_board': webbrowser.open( 'http://8ch.net/hydrus/index.html' ) + elif command == 'file_integrity': self._CheckFileIntegrity() +- elif command == 'help': webbrowser.open( 'file://' + HC.BASE_DIR + '/help/index.html' ) ++ elif command == 'help': webbrowser.open( 'file:///opt/hydrus/help/index.html' ) + elif command == 'help_about': self._AboutWindow() + elif command == 'help_shortcuts': wx.MessageBox( CC.SHORTCUT_HELP ) + elif command == 'import_files': self._ImportFiles() +@@ -3100,4 +3100,4 @@ class FrameSplash( ClientGUICommon.Frame ): + + self.Refresh() + +- +\ No newline at end of file ++ +diff --git a/include/ClientGUIDialogs.py b/include/ClientGUIDialogs.py +index 3b964a7..3f5efb5 100755 +--- a/include/ClientGUIDialogs.py ++++ b/include/ClientGUIDialogs.py +@@ -664,7 +664,7 @@ class DialogFirstStart( Dialog ): + self._ok.SetForegroundColour( ( 0, 128, 0 ) ) + + message1 = 'Hi, this looks like the first time you have started the hydrus client. Don\'t forget to check out the' +- link = wx.HyperlinkCtrl( self, id = -1, label = 'help', url = 'file://' + HC.BASE_DIR + '/help/index.html' ) ++ link = wx.HyperlinkCtrl( self, id = -1, label = 'help', url = 'file:///opt/hydrus/help/index.html' ) + message2 = 'if you haven\'t already.' + message3 = 'When you close this dialog, the client will start its local http server. You will probably get a firewall warning.' + message4 = 'You can block it if you like, or you can allow it. It doesn\'t phone home, or expose your files to your network; it just provides another way to locally export your files.' +@@ -5133,4 +5133,4 @@ class DialogYesNo( Dialog ): + if event.KeyCode == wx.WXK_ESCAPE: self.EndModal( wx.ID_NO ) + else: event.Skip() + +- +\ No newline at end of file ++ +diff --git a/include/HydrusConstants.py b/include/HydrusConstants.py +index 79580fd..327989c 100755 +--- a/include/HydrusConstants.py ++++ b/include/HydrusConstants.py +@@ -3,9 +3,9 @@ import sys + + # dirs + +-BASE_DIR = sys.path[0] ++BASE_DIR = os.path.expanduser("~/.local/share/hydrus") + +-BIN_DIR = BASE_DIR + os.path.sep + 'bin' ++BIN_DIR = "/opt/hydrus/bin" + DB_DIR = BASE_DIR + os.path.sep + 'db' + CLIENT_ARCHIVES_DIR = DB_DIR + os.path.sep + 'client_archives' + CLIENT_FILES_DIR = DB_DIR + os.path.sep + 'client_files' +@@ -16,8 +16,14 @@ SERVER_MESSAGES_DIR = DB_DIR + os.path.sep + 'server_messages' + CLIENT_UPDATES_DIR = DB_DIR + os.path.sep + 'client_updates' + SERVER_UPDATES_DIR = DB_DIR + os.path.sep + 'server_updates' + LOGS_DIR = BASE_DIR + os.path.sep + 'logs' +-STATIC_DIR = BASE_DIR + os.path.sep + 'static' +- ++STATIC_DIR = '/opt/hydrus/static' ++TEMP_DIR = BASE_DIR + os.path.sep + 'temp' ++ ++for dirs in [LOGS_DIR, TEMP_DIR, DB_DIR]: ++ try: ++ os.makedirs(dirs) ++ except os.error: ++ pass + # + + PLATFORM_WINDOWS = False +@@ -556,4 +562,4 @@ sqlite3.register_adapter( bool, int ) + + sqlite3.register_converter( 'BLOB_BYTES', str ) + sqlite3.register_converter( 'INTEGER_BOOLEAN', integer_boolean_to_bool ) +-sqlite3.register_converter( 'TEXT_YAML', yaml.safe_load ) +\ No newline at end of file ++sqlite3.register_converter( 'TEXT_YAML', yaml.safe_load ) +diff --git a/include/HydrusServerResources.py b/include/HydrusServerResources.py +index 8c3779c..3037df8 100644 +--- a/include/HydrusServerResources.py ++++ b/include/HydrusServerResources.py +@@ -784,11 +784,11 @@ class HydrusResourceCommandBooruThumbnail( HydrusResourceCommandBooru ): + mime = media_result.GetMime() + + if mime in HC.MIMES_WITH_THUMBNAILS: path = ClientFiles.GetThumbnailPath( hash, full_size = False ) +- elif mime in HC.AUDIO: path = HC.STATIC_DIR + os.path.sep + 'audio_resized.png' +- elif mime in HC.VIDEO: path = HC.STATIC_DIR + os.path.sep + 'video_resized.png' +- elif mime == HC.APPLICATION_FLASH: path = HC.STATIC_DIR + os.path.sep + 'flash_resized.png' +- elif mime == HC.APPLICATION_PDF: path = HC.STATIC_DIR + os.path.sep + 'pdf_resized.png' +- else: path = HC.STATIC_DIR + os.path.sep + 'hydrus_resized.png' ++ elif mime in HC.AUDIO: path = HC.TEMP_DIR + os.path.sep + 'audio_resized.png' ++ elif mime in HC.VIDEO: path = HC.TEMP_DIR + os.path.sep + 'video_resized.png' ++ elif mime == HC.APPLICATION_FLASH: path = HC.TEMP_DIR + os.path.sep + 'flash_resized.png' ++ elif mime == HC.APPLICATION_PDF: path = HC.TEMP_DIR + os.path.sep + 'pdf_resized.png' ++ else: path = HC.TEMP_DIR + os.path.sep + 'hydrus_resized.png' + + response_context = ResponseContext( 200, path = path ) + diff --git a/running-the-server.patch b/running-the-server.patch new file mode 100644 index 000000000000..28dfd8b41f13 --- /dev/null +++ b/running-the-server.patch @@ -0,0 +1,26 @@ +diff --git a/include/ClientGUI.py b/include/ClientGUI.py +index ea9f71f..3edbf21 100755 +--- a/include/ClientGUI.py ++++ b/include/ClientGUI.py +@@ -234,20 +234,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ): + + HydrusData.ShowText( u'Starting server\u2026' ) + +- my_scriptname = sys.argv[0] +- +- if my_scriptname.endswith( 'pyw' ): +- +- if HC.PLATFORM_WINDOWS or HC.PLATFORM_OSX: python_bin = 'pythonw' +- else: python_bin = 'python' +- +- subprocess.Popen( python_bin + ' "' + HC.BASE_DIR + os.path.sep + 'server.pyw"', shell = True ) +- +- else: +- +- if HC.PLATFORM_WINDOWS: subprocess.Popen( '"' + HC.BASE_DIR + os.path.sep + 'server.exe"', shell = True ) +- else: subprocess.Popen( '"./' + HC.BASE_DIR + os.path.sep + 'server"', shell = True ) +- ++ subprocess.Popen( ["hydrus-server"] ) + + time_waited = 0 + |