330 lines
9.8 KiB
Python
330 lines
9.8 KiB
Python
import datetime
|
|
import typing
|
|
|
|
from hydrus.core import HydrusConstants as HC
|
|
from hydrus.core import HydrusGlobals as HG
|
|
from hydrus.core import HydrusSerialisable
|
|
|
|
from hydrus.client import ClientConstants as CC
|
|
from hydrus.client import ClientGlobals as CG
|
|
|
|
try:
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
DATEUTIL_OK = True
|
|
|
|
except:
|
|
|
|
DATEUTIL_OK = False
|
|
|
|
|
|
from hydrus.core import HydrusTime
|
|
|
|
try:
|
|
|
|
import dateparser
|
|
|
|
DATEPARSER_OK = True
|
|
|
|
except:
|
|
|
|
DATEPARSER_OK = False
|
|
|
|
|
|
def CalendarDelta( dt: datetime.datetime, month_delta = 0, day_delta = 0 ) -> datetime.datetime:
|
|
|
|
if DATEUTIL_OK:
|
|
|
|
delta = relativedelta( months = month_delta, days = day_delta )
|
|
|
|
return dt + delta
|
|
|
|
else:
|
|
|
|
total_days = ( 30 * month_delta ) + day_delta
|
|
|
|
return dt + datetime.timedelta( days = total_days )
|
|
|
|
|
|
|
|
def ParseDate( date_string: str ):
|
|
|
|
if not DATEPARSER_OK:
|
|
|
|
raise Exception( 'Sorry, you need the dateparse library for this, please try reinstalling your venv!' )
|
|
|
|
|
|
dt = dateparser.parse( date_string )
|
|
|
|
if dt is None:
|
|
|
|
raise Exception( 'Sorry, could not parse that date!' )
|
|
|
|
|
|
return HydrusTime.DateTimeToTimestamp( dt )
|
|
|
|
|
|
def MergeModifiedTimes( existing_timestamp: typing.Optional[ int ], new_timestamp: typing.Optional[ int ] ) -> typing.Optional[ int ]:
|
|
|
|
if ShouldUpdateModifiedTime( existing_timestamp, new_timestamp ):
|
|
|
|
return new_timestamp
|
|
|
|
else:
|
|
|
|
return existing_timestamp
|
|
|
|
|
|
|
|
def ShouldUpdateModifiedTime( existing_timestamp: int, new_timestamp: typing.Optional[ int ] ) -> bool:
|
|
|
|
if new_timestamp is None:
|
|
|
|
return False
|
|
|
|
|
|
if existing_timestamp is None:
|
|
|
|
return True
|
|
|
|
|
|
# only go backwards, in general
|
|
if new_timestamp >= existing_timestamp:
|
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
def TimestampIsSensible( timestamp: typing.Optional[ int ] ) -> bool:
|
|
|
|
if timestamp is None:
|
|
|
|
return False
|
|
|
|
|
|
# assume anything too early is a meme and a timestamp parsing conversion error
|
|
if timestamp <= 86400 * 7:
|
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
def TimestampToPrettyTimeDelta( timestamp, just_now_string = 'just now', just_now_threshold = 3, history_suffix = ' ago', show_seconds = True, no_prefix = False ):
|
|
|
|
if CG.client_controller.new_options.GetBoolean( 'always_show_iso_time' ):
|
|
|
|
return HydrusTime.TimestampToPrettyTime( timestamp )
|
|
|
|
else:
|
|
|
|
return HydrusTime.BaseTimestampToPrettyTimeDelta( timestamp, just_now_string = just_now_string, just_now_threshold = just_now_threshold, history_suffix = history_suffix, show_seconds = show_seconds, no_prefix = no_prefix )
|
|
|
|
|
|
|
|
HydrusTime.TimestampToPrettyTimeDelta = TimestampToPrettyTimeDelta
|
|
|
|
REAL_SIMPLE_TIMESTAMP_TYPES = {
|
|
HC.TIMESTAMP_TYPE_ARCHIVED,
|
|
HC.TIMESTAMP_TYPE_MODIFIED_FILE
|
|
}
|
|
|
|
SIMPLE_TIMESTAMP_TYPES = {
|
|
HC.TIMESTAMP_TYPE_ARCHIVED,
|
|
HC.TIMESTAMP_TYPE_MODIFIED_FILE,
|
|
HC.TIMESTAMP_TYPE_MODIFIED_AGGREGATE
|
|
}
|
|
|
|
FILE_SERVICE_TIMESTAMP_TYPES = {
|
|
HC.TIMESTAMP_TYPE_IMPORTED,
|
|
HC.TIMESTAMP_TYPE_DELETED,
|
|
HC.TIMESTAMP_TYPE_PREVIOUSLY_IMPORTED
|
|
}
|
|
|
|
class TimestampData( HydrusSerialisable.SerialisableBase ):
|
|
|
|
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TIMESTAMP_DATA
|
|
SERIALISABLE_NAME = 'Timestamp Data'
|
|
SERIALISABLE_VERSION = 2
|
|
|
|
def __init__( self, timestamp_type = None, location = None, timestamp_ms: typing.Optional[ typing.Union[ int, float ] ] = None ):
|
|
|
|
HydrusSerialisable.SerialisableBase.__init__( self )
|
|
|
|
self.timestamp_type = timestamp_type
|
|
self.location = location
|
|
self.timestamp_ms = None if timestamp_ms is None else int( timestamp_ms )
|
|
self.timestamp = HydrusTime.SecondiseMS( self.timestamp_ms ) # TODO: pretty sure I can delete this variable now, but I am currently attempting to fold space using the spice melange and don't want to make a foolish mistake
|
|
|
|
|
|
def __eq__( self, other ):
|
|
|
|
if isinstance( other, TimestampData ):
|
|
|
|
return self.__hash__() == other.__hash__()
|
|
|
|
|
|
return NotImplemented
|
|
|
|
|
|
def __hash__( self ):
|
|
|
|
return ( self.timestamp_type, self.location, self.timestamp_ms ).__hash__()
|
|
|
|
|
|
def __repr__( self ):
|
|
|
|
return self.ToString()
|
|
|
|
|
|
def _GetSerialisableInfo( self ):
|
|
|
|
if self.timestamp_type in FILE_SERVICE_TIMESTAMP_TYPES:
|
|
|
|
serialisable_location = self.location.hex()
|
|
|
|
else:
|
|
|
|
serialisable_location = self.location # str, int, or None
|
|
|
|
|
|
return ( self.timestamp_type, serialisable_location, self.timestamp_ms )
|
|
|
|
|
|
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
|
|
|
( self.timestamp_type, serialisable_location, self.timestamp_ms ) = serialisable_info
|
|
|
|
self.timestamp = HydrusTime.SecondiseMS( self.timestamp_ms )
|
|
|
|
if self.timestamp_type in FILE_SERVICE_TIMESTAMP_TYPES:
|
|
|
|
self.location = bytes.fromhex( serialisable_location )
|
|
|
|
else:
|
|
|
|
self.location = serialisable_location
|
|
|
|
|
|
|
|
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
|
|
|
|
if version == 1:
|
|
|
|
( timestamp_type, serialisable_location, timestamp ) = old_serialisable_info
|
|
|
|
timestamp_ms = HydrusTime.MillisecondiseS( timestamp )
|
|
|
|
new_serialisable_info = ( timestamp_type, serialisable_location, timestamp_ms )
|
|
|
|
return ( 2, new_serialisable_info )
|
|
|
|
|
|
|
|
def ToString( self ) -> str:
|
|
|
|
if self.timestamp_type in SIMPLE_TIMESTAMP_TYPES:
|
|
|
|
type_base = HC.timestamp_type_str_lookup[ self.timestamp_type ]
|
|
|
|
else:
|
|
|
|
if self.timestamp_type in FILE_SERVICE_TIMESTAMP_TYPES:
|
|
|
|
try:
|
|
|
|
service_string = CG.client_controller.services_manager.GetName( self.location )
|
|
|
|
except:
|
|
|
|
service_string = 'unknown service'
|
|
|
|
|
|
type_base = '"{}" {}'.format( service_string, HC.timestamp_type_str_lookup[ self.timestamp_type ] )
|
|
|
|
elif self.timestamp_type == HC.TIMESTAMP_TYPE_LAST_VIEWED:
|
|
|
|
type_base = '{} {}'.format( CC.canvas_type_str_lookup[ self.location ], HC.timestamp_type_str_lookup[ self.timestamp_type ] )
|
|
|
|
elif self.timestamp_type == HC.TIMESTAMP_TYPE_MODIFIED_DOMAIN:
|
|
|
|
type_base = '"{}" {}'.format( self.location, HC.timestamp_type_str_lookup[ self.timestamp_type ] )
|
|
|
|
else:
|
|
|
|
type_base = 'unknown timestamp type'
|
|
|
|
|
|
|
|
if self.timestamp_ms is None:
|
|
|
|
# we are a stub, type summary is appropriate
|
|
return type_base
|
|
|
|
else:
|
|
|
|
return '{}: {}'.format( type_base, HydrusTime.TimestampMSToPrettyTime( self.timestamp_ms ) )
|
|
|
|
|
|
|
|
@staticmethod
|
|
def STATICArchivedTime( timestamp_ms: int ) -> "TimestampData":
|
|
|
|
return TimestampData( timestamp_type = HC.TIMESTAMP_TYPE_ARCHIVED, timestamp_ms = timestamp_ms )
|
|
|
|
|
|
@staticmethod
|
|
def STATICAggregateModifiedTime( timestamp_ms: int ) -> "TimestampData":
|
|
|
|
return TimestampData( timestamp_type = HC.TIMESTAMP_TYPE_MODIFIED_AGGREGATE, timestamp_ms = timestamp_ms )
|
|
|
|
|
|
@staticmethod
|
|
def STATICDeletedTime( service_key: bytes, timestamp_ms: int ) -> "TimestampData":
|
|
|
|
return TimestampData( timestamp_type = HC.TIMESTAMP_TYPE_DELETED, location = service_key, timestamp_ms = timestamp_ms )
|
|
|
|
|
|
@staticmethod
|
|
def STATICDomainModifiedTime( domain: str, timestamp_ms: int ) -> "TimestampData":
|
|
|
|
return TimestampData( timestamp_type = HC.TIMESTAMP_TYPE_MODIFIED_DOMAIN, location = domain, timestamp_ms = timestamp_ms )
|
|
|
|
|
|
@staticmethod
|
|
def STATICFileModifiedTime( timestamp_ms: int ) -> "TimestampData":
|
|
|
|
return TimestampData( timestamp_type = HC.TIMESTAMP_TYPE_MODIFIED_FILE, timestamp_ms = timestamp_ms )
|
|
|
|
|
|
@staticmethod
|
|
def STATICImportedTime( service_key: bytes, timestamp_ms: int ) -> "TimestampData":
|
|
|
|
return TimestampData( timestamp_type = HC.TIMESTAMP_TYPE_IMPORTED, location = service_key, timestamp_ms = timestamp_ms )
|
|
|
|
|
|
@staticmethod
|
|
def STATICLastViewedTime( canvas_type: int, timestamp_ms: int ) -> "TimestampData":
|
|
|
|
return TimestampData( timestamp_type = HC.TIMESTAMP_TYPE_LAST_VIEWED, location = canvas_type, timestamp_ms = timestamp_ms )
|
|
|
|
|
|
@staticmethod
|
|
def STATICPreviouslyImportedTime( service_key: bytes, timestamp_ms: int ) -> "TimestampData":
|
|
|
|
return TimestampData( timestamp_type = HC.TIMESTAMP_TYPE_PREVIOUSLY_IMPORTED, location = service_key, timestamp_ms = timestamp_ms )
|
|
|
|
|
|
@staticmethod
|
|
def STATICSimpleStub( timestamp_type: int ) -> "TimestampData":
|
|
|
|
return TimestampData( timestamp_type = timestamp_type )
|
|
|
|
|
|
|
|
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TIMESTAMP_DATA ] = TimestampData
|