505 lines
18 KiB
Python
Executable File
505 lines
18 KiB
Python
Executable File
import collections
|
|
import dircache
|
|
import hashlib
|
|
import httplib
|
|
import ClientConstants as CC
|
|
import HydrusConstants as HC
|
|
import HydrusImageHandling
|
|
import HydrusMessageHandling
|
|
import multipart
|
|
import os
|
|
import random
|
|
import sqlite3
|
|
import threading
|
|
import time
|
|
import threading
|
|
import traceback
|
|
import urlparse
|
|
import yaml
|
|
import wx
|
|
import zlib
|
|
|
|
class Conversation():
|
|
|
|
def __init__( self, identity, conversation_key, subject, messages, drafts, search_context ):
|
|
|
|
self._identity = identity
|
|
self._conversation_key = conversation_key
|
|
self._subject = subject
|
|
self._messages = messages
|
|
self._drafts = drafts
|
|
self._search_context = search_context
|
|
|
|
HC.pubsub.sub( self, 'DeleteDraft', 'delete_draft_data' )
|
|
HC.pubsub.sub( self, 'DeleteMessage', 'delete_message' )
|
|
HC.pubsub.sub( self, 'DraftSaved', 'draft_saved' )
|
|
HC.pubsub.sub( self, 'ArchiveConversation', 'archive_conversation_data' )
|
|
HC.pubsub.sub( self, 'InboxConversation', 'inbox_conversation_data' )
|
|
HC.pubsub.sub( self, 'UpdateMessageStatuses', 'message_statuses_data' )
|
|
|
|
|
|
def AddDraft( self, draft ): self._drafts.append( draft )
|
|
|
|
def AddMessage( self, message ): self._messages.append( message )
|
|
|
|
def ArchiveConversation( self, conversation_key ):
|
|
|
|
if conversation_key == self._conversation_key:
|
|
|
|
self._inbox = False
|
|
|
|
for message in self._messages: message.Archive()
|
|
|
|
|
|
|
|
def DeleteDraft( self, draft_key ):
|
|
|
|
self._drafts = [ draft for draft in self._drafts if draft.GetDraftKey() != draft_key ]
|
|
|
|
if len( self._messages ) + len( self._drafts ) == 0:
|
|
|
|
HC.pubsub.pub( 'delete_conversation_data', self._conversation_key )
|
|
HC.pubsub.pub( 'delete_conversation_gui', self._conversation_key )
|
|
|
|
|
|
|
|
def DraftSaved( self, draft_key, draft_message ):
|
|
|
|
for ( index, draft ) in enumerate( self._drafts ):
|
|
|
|
if draft.GetDraftKey() == draft_key:
|
|
|
|
self._drafts[ index ] = draft_message
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
def DeleteMessage( self, message_key ):
|
|
|
|
self._messages = [ message for message in self._messages if message.GetMessageKey() != message_key ]
|
|
|
|
if len( self._messages ) + len( self._drafts ) == 0:
|
|
|
|
HC.pubsub.pub( 'delete_conversation_data', self._conversation_key )
|
|
HC.pubsub.pub( 'delete_conversation_gui', self._conversation_key )
|
|
|
|
|
|
|
|
def GetConversationKey( self ): return self._conversation_key
|
|
|
|
def GetListCtrlTuple( self ):
|
|
|
|
if len( self._messages ) > 0:
|
|
|
|
first_message = self._messages[0]
|
|
last_message = self._messages[-1]
|
|
|
|
first_timestamp = first_message.GetTimestamp()
|
|
last_timestamp = last_message.GetTimestamp()
|
|
|
|
from_name = first_message.GetContactFrom().GetName()
|
|
|
|
else:
|
|
|
|
first_timestamp = None
|
|
last_timestamp = None
|
|
|
|
from_name = self._drafts[0].GetContactFrom().GetName()
|
|
|
|
|
|
participants = self.GetParticipants()
|
|
|
|
num_messages_unread = len( [ message for message in self._messages if ( self._identity, 'sent' ) in message.GetDestinations() ] )
|
|
|
|
inbox = True in ( message.IsInbox() for message in self._messages )
|
|
|
|
return ( self._conversation_key, inbox, self._subject, from_name, participants, len( self._messages ), num_messages_unread, first_timestamp, last_timestamp )
|
|
|
|
|
|
def GetMessages( self ): return ( self._messages, self._drafts )
|
|
|
|
def GetMessageKeysWithDestination( self, destination ): return [ message.GetMessageKey() for message in self._messages if message.HasDestination( destination ) ]
|
|
|
|
def GetParticipants( self ):
|
|
|
|
if len( self._messages ) == 0: return []
|
|
else:
|
|
|
|
first_message = self._messages[ 0 ]
|
|
|
|
return first_message.GetParticipants()
|
|
|
|
|
|
|
|
def GetStartedBy( self ):
|
|
|
|
if len( self._messages ) > 0: return self._messages[ 0 ].GetContactFrom()
|
|
elif len( self._drafts ) > 0: return self._drafts[ 0 ].GetContactFrom()
|
|
else: return None
|
|
|
|
|
|
def GetSubject( self ): return self._subject
|
|
|
|
def GetUpdated( self ):
|
|
|
|
if len( self._messages ) > 0:
|
|
|
|
last_message = self._messages[-1]
|
|
last_timestamp = last_message.GetTimestamp()
|
|
|
|
else: last_timestamp = None
|
|
|
|
return last_timestamp
|
|
|
|
|
|
def HasMessageKey( self, message_key ): return True in ( message_key == message.GetMessageKey() for message in self._messages )
|
|
|
|
def HasRead( self ): return True in ( message.IsRead( self._identity ) for message in self._messages )
|
|
|
|
def HasUnread( self ): return True in ( message.IsUnread( self._identity ) for message in self._messages )
|
|
|
|
def InboxConversation( self, conversation_key ):
|
|
|
|
if conversation_key == self._conversation_key:
|
|
|
|
self._inbox = True
|
|
|
|
for message in self._messages: message.Inbox()
|
|
|
|
|
|
|
|
def IsInbox( self ): return True in ( message.IsInbox() for message in self._messages )
|
|
|
|
def UpdateMessageStatuses( self, message_key, status_updates ):
|
|
|
|
for message in self._messages:
|
|
|
|
if message_key == message.GetMessageKey():
|
|
|
|
message.UpdateMessageStatuses( status_updates )
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
class Contact( HC.HydrusYAMLBase ):
|
|
|
|
yaml_tag = u'!Contact'
|
|
|
|
def __init__( self, public_key, name, host, port ):
|
|
|
|
HC.HydrusYAMLBase.__init__( self )
|
|
|
|
self._public_key = public_key
|
|
self._name = name
|
|
self._host = host
|
|
self._port = port
|
|
|
|
|
|
def __hash__( self ): return self._name.__hash__()
|
|
|
|
def __eq__( self, other ): return self.__hash__() == other.__hash__()
|
|
|
|
def __ne__( self, other ): return not self.__eq__( other )
|
|
|
|
def Encrypt( self, message ): return HydrusMessageHandling.PackageMessageForDelivery( message, self._public_key )
|
|
|
|
def GetAddress( self ): return ( self._host, self._port )
|
|
|
|
def GetConnection( self ): return CC.ConnectionToService( None, CC.Credentials( self._host, self._port ) )
|
|
|
|
def GetContactKey( self ):
|
|
|
|
if self._public_key is None: return None
|
|
else: return hashlib.sha256( self._public_key ).digest()
|
|
|
|
|
|
def GetInfo( self ): return ( self._public_key, self._name, self._host, self._port )
|
|
|
|
def GetName( self ): return self._name
|
|
|
|
def GetPublicKey( self ): return self._public_key
|
|
|
|
def HasPublicKey( self ): return self._public_key is not None
|
|
|
|
class DraftMessage():
|
|
|
|
def __init__( self, draft_key, conversation_key, subject, contact_from, contacts_names_to, recipients_visible, body, attachment_hashes, is_new = False ):
|
|
|
|
self._draft_key = draft_key
|
|
self._conversation_key = conversation_key
|
|
self._subject = subject
|
|
self._contact_from = contact_from
|
|
self._contacts_names_to = contacts_names_to
|
|
self._recipients_visible = recipients_visible
|
|
self._body = body
|
|
self._attachment_hashes = attachment_hashes
|
|
self._is_new = is_new
|
|
|
|
|
|
def __hash__( self ): return self._draft_key.__hash__()
|
|
|
|
def __eq__( self, other ): return self.__hash__() == other.__hash__()
|
|
|
|
def __ne__( self, other ): return not self.__eq__( other )
|
|
|
|
def GetContactFrom( self ): return self._contact_from
|
|
|
|
def GetDraftKey( self ): return self._draft_key
|
|
|
|
def GetInfo( self ): return ( self._draft_key, self._conversation_key, self._subject, self._contact_from, self._contacts_names_to, self._recipients_visible, self._body, self._attachment_hashes )
|
|
|
|
def IsNew( self ): return self._is_new
|
|
|
|
def IsReply( self ): return self._conversation_key != self._draft_key
|
|
|
|
def Saved( self ): self._is_new = False
|
|
|
|
class Message():
|
|
|
|
def __init__( self, message_key, contact_from, destinations, timestamp, body, attachment_hashes, inbox ):
|
|
|
|
self._message_key = message_key
|
|
self._contact_from = contact_from
|
|
self._destinations = destinations
|
|
self._timestamp = timestamp
|
|
self._body = body
|
|
self._attachment_hashes = attachment_hashes
|
|
self._inbox = inbox
|
|
|
|
|
|
def __hash__( self ): return self._message_key.__hash__()
|
|
|
|
def __eq__( self, other ): return self.__hash__() == other.__hash__()
|
|
|
|
def __ne__( self, other ): return not self.__eq__( other )
|
|
|
|
def Archive( self ): self._inbox = False
|
|
|
|
def GetBody( self ): return self._body
|
|
def GetContactFrom( self ): return self._contact_from
|
|
def GetContactsTo( self ): return [ contact_to for ( contact_to, status ) in self._destinations ]
|
|
def GetDestinations( self ): return self._destinations
|
|
def GetMessageKey( self ): return self._message_key
|
|
def GetParticipants( self ): return [ self._contact_from ] + self.GetContactsTo()
|
|
def GetTimestamp( self ): return self._timestamp
|
|
|
|
def HasDestination( self, destination ): return destination in self._destinations
|
|
|
|
def Inbox( self ): self._inbox = True
|
|
|
|
def IsInbox( self ): return self._inbox
|
|
def IsRead( self, identity ): return ( identity, 'read' ) in self._destinations
|
|
def IsUnread( self, identity ): return ( identity, 'sent' ) in self._destinations
|
|
|
|
def Read( self, identity ): self.UpdateMessageStatuses( [ ( identity.GetContactKey(), 'read' ) ] )
|
|
|
|
def UpdateMessageStatuses( self, updates ):
|
|
|
|
contact_keys_to_contacts = { contact.GetContactKey() : contact for ( contact, status ) in self._destinations }
|
|
|
|
dest_dict = dict( self._destinations )
|
|
|
|
for ( contact_key, status ) in updates:
|
|
|
|
if contact_key in contact_keys_to_contacts:
|
|
|
|
dest_dict[ contact_keys_to_contacts[ contact_key ] ] = status
|
|
|
|
|
|
|
|
self._destinations = dest_dict.items()
|
|
|
|
|
|
def Unread( self, identity ): self.UpdateMessageStatuses( [ ( identity.GetContactKey(), 'sent' ) ] )
|
|
|
|
class MessageSearchContext():
|
|
|
|
def __init__( self, identity, raw_predicates = [] ):
|
|
|
|
self._identity = identity
|
|
|
|
raw_system_predicates = [ predicate for predicate in raw_predicates if predicate.startswith( 'system:' ) ]
|
|
|
|
self._system_predicates = MessageSystemPredicates( raw_system_predicates )
|
|
|
|
raw_search_terms = [ predicate for predicate in raw_predicates if not predicate.startswith( 'system:' ) ]
|
|
|
|
self._search_terms_to_include = [ search_term for search_term in raw_search_terms if not search_term.startswith( '-' ) ]
|
|
self._search_terms_to_exclude = [ search_term[1:] for search_term in raw_search_terms if search_term.startswith( '-' ) ]
|
|
|
|
|
|
def GetIdentity( self ): return self._identity
|
|
def GetSystemPredicates( self ): return self._system_predicates
|
|
def GetTermsToExclude( self ): return self._search_terms_to_exclude
|
|
def GetTermsToInclude( self ): return self._search_terms_to_include
|
|
|
|
class MessageSystemPredicates():
|
|
|
|
STATUS = 0
|
|
CONTACT_STARTED = 1
|
|
CONTACT_FROM = 2
|
|
CONTACT_TO = 3
|
|
TIMESTAMP = 4
|
|
NUM_ATTACHMENTS = 5
|
|
|
|
def __init__( self, system_predicates ):
|
|
|
|
self._predicates = {}
|
|
|
|
self._predicates[ self.NUM_ATTACHMENTS ] = []
|
|
|
|
self._status = None
|
|
|
|
self._contact_from = None
|
|
self._contact_to = None
|
|
self._contact_started = None
|
|
self._min_timestamp = None
|
|
self._max_timestamp = None
|
|
|
|
self._inbox = 'system:inbox' in system_predicates
|
|
|
|
self._archive = 'system:archive' in system_predicates
|
|
|
|
self._draft = 'system:draft' in system_predicates
|
|
|
|
isin = lambda a, b: a in b
|
|
startswith = lambda a, b: a.startswith( b )
|
|
lessthan = lambda a, b: a < b
|
|
greaterthan = lambda a, b: a > b
|
|
equals = lambda a, b: a == b
|
|
about_equals = lambda a, b: a < b * 1.15 and a > b * 0.85
|
|
|
|
for predicate in system_predicates:
|
|
|
|
if predicate.startswith( 'system:status' ):
|
|
|
|
try:
|
|
|
|
status = predicate[14:]
|
|
|
|
self._status = status
|
|
|
|
except: raise Exception( 'I could not parse the status predicate.' )
|
|
|
|
|
|
if predicate.startswith( 'system:started_by' ):
|
|
|
|
try:
|
|
|
|
started_by = predicate[18:]
|
|
|
|
self._contact_started = started_by
|
|
|
|
except: raise Exception( 'I could not parse the started by predicate.' )
|
|
|
|
|
|
if predicate.startswith( 'system:from' ):
|
|
|
|
try:
|
|
|
|
contact_from = predicate[12:]
|
|
|
|
self._contact_from = contact_from
|
|
|
|
except: raise Exception( 'I could not parse the contact from predicate.' )
|
|
|
|
|
|
if predicate.startswith( 'system:to' ):
|
|
|
|
try:
|
|
|
|
contact_to = predicate[10:]
|
|
|
|
self._contact_to = contact_to
|
|
|
|
except: raise Exception( 'I could not parse the contact to predicate.' )
|
|
|
|
|
|
if predicate.startswith( 'system:age' ):
|
|
|
|
try:
|
|
|
|
condition = predicate[10]
|
|
|
|
if condition not in ( '<', '>', u'\u2248' ): raise Exception()
|
|
|
|
age = predicate[11:]
|
|
|
|
years = 0
|
|
months = 0
|
|
days = 0
|
|
|
|
if 'y' in age:
|
|
|
|
( years, age ) = age.split( 'y' )
|
|
|
|
years = int( years )
|
|
|
|
|
|
if 'm' in age:
|
|
|
|
( months, age ) = age.split( 'm' )
|
|
|
|
months = int( months )
|
|
|
|
|
|
if 'd' in age:
|
|
|
|
( days, age ) = age.split( 'd' )
|
|
|
|
days = int( days )
|
|
|
|
|
|
timestamp = HC.GetNow() - ( ( ( ( ( years * 12 ) + months ) * 30 ) + days ) * 86400 )
|
|
|
|
# this is backwards because we are talking about age, not timestamp
|
|
|
|
if condition == '<': self._max_timestamp = timestamp
|
|
elif condition == '>': self._min_timestamp = timestamp
|
|
elif condition == u'\u2248':
|
|
self._min_timestamp = int( timestamp * 0.85 )
|
|
self._max_timestamp = int( timestamp * 1.15 )
|
|
|
|
except: raise Exception( 'I could not parse the age predicate.' )
|
|
|
|
|
|
if predicate.startswith( 'system:numattachments' ):
|
|
|
|
try:
|
|
|
|
condition = predicate[21]
|
|
|
|
if condition not in ( '>', '<', '=', u'\u2248' ): raise Exception()
|
|
|
|
num_attachments = int( predicate[22:] )
|
|
|
|
if num_attachments >= 0:
|
|
|
|
if condition == '<': self._predicates[ self.NUM_ATTACHMENTS ].append( ( lessthan, num_attachments ) )
|
|
elif condition == '>': self._predicates[ self.NUM_ATTACHMENTS ].append( ( greaterthan, num_attachments ) )
|
|
elif condition == '=': self._predicates[ self.NUM_ATTACHMENTS ].append( ( equals, num_attachments ) )
|
|
elif condition == u'\u2248': self._predicates[ self.NUM_ATTACHMENTS ].append( ( about_equals, num_attachments ) )
|
|
|
|
|
|
except: raise Exception( 'I could not parse the num attachments predicate.' )
|
|
|
|
|
|
|
|
|
|
def GetInfo( self ): return ( self._inbox, self._archive, self._draft, self._status, self._contact_from, self._contact_to, self._contact_started, self._min_timestamp, self._max_timestamp )
|
|
|
|
# maybe reconfigure this!
|
|
# instead of Ok, I could do some real good searching and ANDing with the above predicates
|
|
# especially since this is for getting the message_ids, not the whole convo, which will have the rich data
|
|
|
|
def Ok( self, num_attachments ):
|
|
|
|
if False in ( function( num_attachments, arg ) for ( function, arg ) in self._predicates[ self.NUM_ATTACHMENTS ] ): return False
|
|
|
|
return True
|
|
|
|
|