1010from datetime import timedelta
1111from os .path import normpath
1212from os .path import split
13+ from pathlib import Path
14+ from pathlib import PosixPath
1315from typing import Dict
1416from typing import Iterable
1517from typing import List
2224from atomicwrites import AtomicWriter
2325from dateutil .rrule import rrulestr
2426from dateutil .tz import tzlocal
25-
2627from todoman import exceptions
2728
2829logger = logging .getLogger (name = __name__ )
3435
3536
3637def register_adapters_and_converters ():
38+ sqlite3 .register_adapter (Path , str )
39+ sqlite3 .register_adapter (PosixPath , str )
40+
41+ sqlite3 .register_converter ("path" , lambda p : Path (p .decode ()))
3742 sqlite3 .register_converter (
38- 'timestamp' ,
39- lambda d : datetime .fromtimestamp (float (d ), LOCAL_TIMEZONE )
43+ "timestamp" , lambda d : datetime .fromtimestamp (float (d ), LOCAL_TIMEZONE )
4044 )
4145
4246
@@ -288,7 +292,7 @@ def path(self) -> str:
288292 if not self .list :
289293 raise ValueError ("A todo without a list does not have a path." )
290294
291- return os . path . join ( self .list .path , self .filename )
295+ return self .list .path . joinpath ( self .filename )
292296
293297 def cancel (self ) -> None :
294298 self .status = "CANCELLED"
@@ -387,7 +391,7 @@ def _read(self, path):
387391 return component
388392
389393 def write (self ):
390- if os . path . exists ( self .todo .path ):
394+ if self .todo .path . exists ( ):
391395 self ._write_existing (self .todo .path )
392396 else :
393397 self ._write_new (self .todo .path )
@@ -436,9 +440,10 @@ class Cache:
436440
437441 SCHEMA_VERSION = 7
438442
439- def __init__ (self , path : str ):
440- self .cache_path = str (path )
441- os .makedirs (os .path .dirname (self .cache_path ), exist_ok = True )
443+ def __init__ (self , path : Path ):
444+ self .cache_path = path
445+ # XXX: Use the below once we drop python3.4
446+ self .cache_path .parent .mkdir (parents = True , exist_ok = True )
442447
443448 self ._conn = sqlite3 .connect (
444449 str (self .cache_path ),
@@ -485,7 +490,7 @@ def create_tables(self):
485490 """
486491 CREATE TABLE IF NOT EXISTS lists (
487492 "name" TEXT PRIMARY KEY,
488- "path" TEXT ,
493+ "path" path ,
489494 "colour" TEXT,
490495 "mtime" INTEGER,
491496
@@ -497,7 +502,7 @@ def create_tables(self):
497502 self ._conn .execute (
498503 """
499504 CREATE TABLE IF NOT EXISTS files (
500- "path" TEXT PRIMARY KEY,
505+ "path" path PRIMARY KEY,
501506 "list_name" TEXT,
502507 "mtime" INTEGER,
503508
@@ -510,7 +515,7 @@ def create_tables(self):
510515 self ._conn .execute (
511516 """
512517 CREATE TABLE IF NOT EXISTS todos (
513- "file_path" TEXT ,
518+ "file_path" path ,
514519
515520 "id" INTEGER PRIMARY KEY,
516521 "uid" TEXT,
@@ -539,7 +544,7 @@ def create_tables(self):
539544
540545 def clear (self ):
541546 self ._conn .close ()
542- os . remove ( self .cache_path )
547+ self .cache_path . unlink
543548 self ._conn = None
544549
545550 def add_list (self , name : str , path : str , colour : str , mtime : int ):
@@ -860,24 +865,24 @@ def todos(
860865
861866 def _todo_from_db (self , row : dict ) -> Todo :
862867 todo = Todo ()
863- todo .id = row ['id' ]
864- todo .uid = row [' uid' ]
865- todo .summary = row [' summary' ]
866- todo .due = row [' due' ]
867- todo .start = row [' start' ]
868- todo .priority = row [' priority' ]
869- todo .created_at = row [' created_at' ]
870- todo .completed_at = row [' completed_at' ]
871- todo .dtstamp = row [' dtstamp' ]
872- todo .percent_complete = row [' percent_complete' ]
873- todo .status = row [' status' ]
874- todo .description = row [' description' ]
875- todo .location = row [' location' ]
876- todo .sequence = row [' sequence' ]
877- todo .last_modified = row [' last_modified' ]
878- todo .list = self .lists_map [row [' list_name' ]]
879- todo .filename = os . path . basename ( row [' path' ])
880- todo .rrule = row [' rrule' ]
868+ todo .id = row ["id" ]
869+ todo .uid = row [" uid" ]
870+ todo .summary = row [" summary" ]
871+ todo .due = row [" due" ]
872+ todo .start = row [" start" ]
873+ todo .priority = row [" priority" ]
874+ todo .created_at = row [" created_at" ]
875+ todo .completed_at = row [" completed_at" ]
876+ todo .dtstamp = row [" dtstamp" ]
877+ todo .percent_complete = row [" percent_complete" ]
878+ todo .status = row [" status" ]
879+ todo .description = row [" description" ]
880+ todo .location = row [" location" ]
881+ todo .sequence = row [" sequence" ]
882+ todo .last_modified = row [" last_modified" ]
883+ todo .list = self .lists_map [row [" list_name" ]]
884+ todo .filename = row [" path" ]. name
885+ todo .rrule = row [" rrule" ]
881886 return todo
882887
883888 def lists (self ) -> Iterable [TodoList ]:
@@ -957,18 +962,16 @@ def __init__(self, name: str, path: str, colour: str = None):
957962 @staticmethod
958963 def colour_for_path (path : str ) -> Optional [str ]:
959964 try :
960- with open (os .path .join (path , "color" )) as f :
961- return f .read ().strip ()
962- except OSError :
965+ return path .joinpath ("color" ).read_text ().strip ()
966+ except (OSError , IOError ):
963967 logger .debug ("No colour for list %s" , path )
964968
965969 @staticmethod
966970 def name_for_path (path : str ) -> str :
967971 try :
968- with open (os .path .join (path , "displayname" )) as f :
969- return f .read ().strip ()
970- except OSError :
971- return split (normpath (path ))[1 ]
972+ return path .joinpath ("displayname" ).read_text ().strip ()
973+ except (OSError , IOError ):
974+ return path .name
972975
973976 @staticmethod
974977 def mtime_for_path (path : str ) -> int :
@@ -1005,8 +1008,8 @@ class Database:
10051008 """
10061009
10071010 def __init__ (self , paths , cache_path ):
1008- self .cache = Cache (cache_path )
1009- self .paths = [str (path ) for path in paths ]
1011+ self .cache = Cache (Path ( cache_path ) )
1012+ self .paths = [Path (path ) for path in paths ]
10101013 self .update_cache ()
10111014
10121015 def update_cache (self ) -> None :
@@ -1023,13 +1026,11 @@ def update_cache(self) -> None:
10231026 TodoList .colour_for_path (path ),
10241027 paths [path ],
10251028 )
1026- for entry in os . listdir ( path ):
1027- if not entry .endswith (".ics" ):
1029+ for entry in path . iterdir ( ):
1030+ if not entry .name . endswith (".ics" ):
10281031 continue
1029- entry_path = os .path .join (path , entry )
1030- mtime = _getmtime (entry_path )
1031- paths_to_mtime [entry_path ] = mtime
1032- paths_to_list_name [entry_path ] = list_name
1032+ paths_to_mtime [entry ] = _getmtime (entry )
1033+ paths_to_list_name [entry ] = list_name
10331034
10341035 self .cache .expire_files (paths_to_mtime )
10351036
@@ -1043,11 +1044,11 @@ def update_cache(self) -> None:
10431044 continue
10441045
10451046 try :
1046- with open ( entry_path , "rb" ) as f :
1047- cal = icalendar .Calendar .from_ical (f . read () )
1048- for component in cal .walk ("VTODO" ):
1049- self .cache .add_vtodo (component , entry_path )
1050- except Exception :
1047+ data = entry_path . read_bytes ()
1048+ cal = icalendar .Calendar .from_ical (data )
1049+ for component in cal .walk ("VTODO" ):
1050+ self .cache .add_vtodo (component , entry_path )
1051+ except Exception as e :
10511052 logger .exception ("Failed to read entry %s." , entry_path )
10521053
10531054 self .cache .save_to_disk ()
@@ -1062,17 +1063,16 @@ def lists(self) -> Iterable[TodoList]:
10621063 return self .cache .lists ()
10631064
10641065 def move (self , todo : Todo , new_list : TodoList , from_list : TodoList ) -> None :
1065- orig_path = os .path .join ( from_list . path , todo .filename )
1066- dest_path = os .path .join ( new_list . path , todo .filename )
1066+ orig_path = from_list .path .joinpath ( todo .filename )
1067+ dest_path = new_list .path .joinpath ( todo .filename )
10671068
1068- os .rename (orig_path , dest_path )
1069+ orig_path .rename (dest_path )
10691070
10701071 def delete (self , todo : Todo ) -> None :
10711072 if not todo .list :
10721073 raise ValueError ("Cannot delete Todo without a list." )
10731074
1074- path = os .path .join (todo .list .path , todo .filename )
1075- os .remove (path )
1075+ todo .list .path .joinpath (todo .filename ).unlink ()
10761076
10771077 def flush (self ) -> Iterable [Todo ]:
10781078 for todo in self .todos (status = ["ANY" ]):
@@ -1104,5 +1104,5 @@ def save(self, todo: Todo) -> None:
11041104
11051105
11061106def _getmtime (path : str ) -> int :
1107- stat = os .stat (path )
1107+ stat = path .stat ()
11081108 return getattr (stat , "st_mtime_ns" , stat .st_mtime )
0 commit comments