You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
5.1KB

  1. from datetime import datetime
  2. import errno
  3. import logging
  4. import os
  5. from pathlib import PurePath
  6. from stat import S_IFDIR, S_IFREG
  7. from threading import Event, Thread
  8. from time import sleep
  9. from fuse import FuseOSError, LoggingMixIn, Operations
  10. import iso8601
  11. from requests.exceptions import ConnectionError
  12. from itemmanager import ItemManager
  13. class StandardNotesFUSE(LoggingMixIn, Operations):
  14. def __init__(self, sn_api, sync_sec, path='.'):
  15. self.item_manager = ItemManager(sn_api)
  16. self.notes = self.item_manager.get_notes()
  17. self.uid = os.getuid()
  18. self.gid = os.getgid()
  19. now = datetime.now().timestamp()
  20. self.dir_stat = dict(st_mode=(S_IFDIR | 0o755), st_ctime=now,
  21. st_mtime=now, st_atime=now, st_nlink=2,
  22. st_uid=self.uid, st_gid=self.gid)
  23. self.note_stat = dict(st_mode=(S_IFREG | 0o644), st_ctime=now,
  24. st_mtime=now, st_atime=now, st_nlink=1,
  25. st_uid=self.uid, st_gid=self.gid)
  26. self.sync_sec = sync_sec
  27. self.run_sync = Event()
  28. self.stop_sync = Event()
  29. self.sync_thread = Thread(target=self._sync_thread)
  30. def init(self, path):
  31. self.sync_thread.start()
  32. def destroy(self, path):
  33. self._sync_now()
  34. logging.info('Stopping sync thread.')
  35. self.stop_sync.set()
  36. self.sync_thread.join()
  37. return 0
  38. def _sync_thread(self):
  39. while not self.stop_sync.is_set():
  40. self.run_sync.clear()
  41. manually_synced = self.run_sync.wait(timeout=self.sync_sec)
  42. if not manually_synced: logging.info('Auto-syncing items...')
  43. sleep(0.1) # fixes race condition of quick create() then write()
  44. try:
  45. self.item_manager.sync_items()
  46. except ConnectionError:
  47. logging.error('Unable to connect to sync server. Retrying...')
  48. def _sync_now(self):
  49. self.run_sync.set()
  50. def _path_to_note(self, path):
  51. pp = PurePath(path)
  52. note_name = pp.parts[1]
  53. self.notes = self.item_manager.get_notes()
  54. note = self.notes[note_name]
  55. return note, note['uuid']
  56. def getattr(self, path, fh=None):
  57. if path == '/':
  58. return self.dir_stat
  59. try:
  60. note, uuid = self._path_to_note(path)
  61. st = self.note_stat
  62. st['st_size'] = len(note['text'])
  63. st['st_ctime'] = iso8601.parse_date(note['created']).timestamp()
  64. st['st_mtime'] = iso8601.parse_date(note['modified']).timestamp()
  65. return st
  66. except KeyError:
  67. raise FuseOSError(errno.ENOENT)
  68. def readdir(self, path, fh):
  69. dirents = ['.', '..']
  70. if path == '/':
  71. dirents.extend(list(self.notes.keys()))
  72. return dirents
  73. def read(self, path, size, offset, fh):
  74. note, uuid = self._path_to_note(path)
  75. return note['text'][offset : offset + size]
  76. def truncate(self, path, length, fh=None):
  77. note, uuid = self._path_to_note(path)
  78. text = note['text'][:length]
  79. self.item_manager.write_note(uuid, text)
  80. self._sync_now()
  81. return 0
  82. def write(self, path, data, offset, fh):
  83. note, uuid = self._path_to_note(path)
  84. text = note['text'][:offset] + data
  85. try:
  86. self.item_manager.write_note(uuid, text)
  87. except UnicodeError:
  88. logging.error('Unable to parse non-unicode data.')
  89. raise FuseOSError(errno.EIO)
  90. self._sync_now()
  91. return len(data)
  92. def create(self, path, mode):
  93. path_parts = path.split('/')
  94. note_name = path_parts[1]
  95. # disallow hidden files (usually editor / OS files)
  96. if note_name[0] == '.':
  97. logging.error('Creation of hidden files is disabled.')
  98. raise FuseOSError(errno.EPERM)
  99. now = datetime.utcnow().isoformat()[:-3] + 'Z' # hack
  100. self.item_manager.create_note(note_name, now)
  101. self._sync_now()
  102. return 0
  103. def unlink(self, path):
  104. note, uuid = self._path_to_note(path)
  105. self.item_manager.delete_note(uuid)
  106. self._sync_now()
  107. return 0
  108. def mkdir(self, path, mode):
  109. logging.error('Creation of directories is disabled.')
  110. raise FuseOSError(errno.EPERM)
  111. def utimens(self, path, times=None):
  112. note, uuid = self._path_to_note(path)
  113. self.item_manager.touch_note(uuid)
  114. self._sync_now()
  115. return 0
  116. def rename(self, old, new):
  117. note, uuid = self._path_to_note(old)
  118. new_path_parts = new.split('/')
  119. new_note_name = new_path_parts[1]
  120. self.item_manager.rename_note(uuid, new_note_name)
  121. self._sync_now()
  122. return 0
  123. def chmod(self, path, mode):
  124. logging.error('chmod is disabled.')
  125. raise FuseOSError(errno.EPERM)
  126. def chown(self, path, uid, gid):
  127. logging.error('chown is disabled.')
  128. raise FuseOSError(errno.EPERM)
  129. def readlink(self, path):
  130. return 0
  131. def rmdir(self, path):
  132. return 0
  133. def symlink(self, target, source):
  134. return 0