hydrus/hydrus/client/ClientTime.py

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