diff --git a/_config.py b/_config.py index 0c57ce3..658d06b 100644 --- a/_config.py +++ b/_config.py @@ -42,6 +42,7 @@ def __init_subclass__(cls, **kwargs): PROJECT_VERSION: str = "0.0.1" PROJECT_DESCRIPTION: str = "Simplenote" + SIMPLENOTE_DIR: str = BASE_DIR SETTINGS_FILE: str = "simplenote.sublime-settings" SIMPLENOTE_APP_ID: str = os.getenv("SIMPLENOTE_APP_ID", "chalk-bump-f49") __SIMPLENOTE_APP_KEY: str = os.getenv("SIMPLENOTE_APP_KEY", "YzhjMmI4NjMzNzE1NGNkYWJjOTg5YjIzZTMwYzZiZjQ=") @@ -51,18 +52,14 @@ def __init_subclass__(cls, **kwargs): # SIMPLENOTE_APP_KEY: bytes = base64.b64decode(__SIMPLENOTE_APP_KEY) SIMPLENOTE_APP_KEY: str = base64.b64decode("YzhjMmI4NjMzNzE1NGNkYWJjOTg5YjIzZTMwYzZiZjQ=").decode("utf-8") SIMPLENOTE_BUCKET: str = os.getenv("SIMPLENOTE_BUCKET", "note") - SIMPLENOTE_AUTH_URL: str = f"https://auth.simperium.com/1/{SIMPLENOTE_APP_ID}/authorize/" - SIMPLENOTE_DATA_URL: str = f"https://api.simperium.com/1/{SIMPLENOTE_APP_ID}/{SIMPLENOTE_BUCKET}" SIMPLENOTE_USERNAME: str = os.getenv("SIMPLENOTE_USERNAME", "") SIMPLENOTE_PASSWORD: str = os.getenv("SIMPLENOTE_PASSWORD", "") - STARTED: bool = False - RELOAD_CALLS: int = -1 - NOTE_FETCH_LENGTH: int = 1 - _NOTE_CACHE_FILE: str = "_note_cache.pkl" - NOTE_CACHE_FILE: str = "note_cache.pkl" - os.remove(_NOTE_CACHE_FILE) if os.path.exists(_NOTE_CACHE_FILE) else None - os.remove(NOTE_CACHE_FILE) if os.path.exists(NOTE_CACHE_FILE) else None - DEFAULT_NOTE_TITLE: str = "untitled" + SIMPLENOTE_TOKEN_FILE: str = os.getenv("SIMPLENOTE_TOKEN_FILE", "simplenote_token.pkl") + SIMPLENOTE_STARTED: bool = False + SIMPLENOTE_RELOAD_CALLS: int = -1 + SIMPLENOTE_NOTE_FETCH_LENGTH: int = 1 + SIMPLENOTE_NOTE_CACHE_FILE: str = "note_cache.pkl" + SIMPLENOTE_DEFAULT_NOTE_TITLE: str = "untitled" class Development(_BaseConfig): @@ -87,6 +84,13 @@ class Production(_BaseConfig): CONFIG: typing.Type[_BaseConfig] = _BaseConfig.mapping.get(env, Development) +# for key, value in vars(CONFIG).items(): +for attr in dir(CONFIG): + if attr.startswith("_"): + continue + print(attr, getattr(CONFIG, attr)) + os.environ[attr] = str(getattr(CONFIG, attr)) +print("os.environ", os.environ) from importlib import import_module diff --git a/api.py b/api.py index 7c9e2a3..0338557 100644 --- a/api.py +++ b/api.py @@ -12,19 +12,29 @@ from urllib.parse import urlencode import uuid -from _config import CONFIG from utils.patterns.singleton.base import Singleton from utils.request import request -from utils.tools import Settings logger = logging.getLogger() +__all__ = ["Simplenote"] + +SIMPLENOTE_DIR = os.environ.get("SIMPLENOTE_DIR", "") +SIMPLENOTE_APP_ID: str = os.environ.get("SIMPLENOTE_APP_ID", "") +SIMPLENOTE_APP_KEY: str = os.environ.get("SIMPLENOTE_APP_KEY", "") +SIMPLENOTE_BUCKET: str = os.environ.get("SIMPLENOTE_BUCKET", "") +_SIMPLENOTE_TOKEN_FILE = os.environ.get("SIMPLENOTE_TOKEN_FILE", "simplenote_token.pkl") +SIMPLENOTE_TOKEN_FILE = os.path.join(SIMPLENOTE_DIR, _SIMPLENOTE_TOKEN_FILE) +simplenote_variables = [SIMPLENOTE_DIR, SIMPLENOTE_APP_ID, SIMPLENOTE_APP_KEY, SIMPLENOTE_BUCKET, SIMPLENOTE_TOKEN_FILE] +if not all(simplenote_variables): + raise Exception("Simplenote variables %s must be set in environment variables" % simplenote_variables) + class URL: BASE: str = "https://api.simperium.com/1" - DATA = f"{BASE}/{CONFIG.SIMPLENOTE_APP_ID}/{CONFIG.SIMPLENOTE_BUCKET}" - __auth = f"{BASE}/{CONFIG.SIMPLENOTE_APP_ID}/authorize/" + DATA = f"{BASE}/{SIMPLENOTE_APP_ID}/{SIMPLENOTE_BUCKET}" + __auth = f"{BASE}/{SIMPLENOTE_APP_ID}/authorize/" __index = DATA + "/index" __retrieve = DATA + "/i/%s" __modify = __retrieve @@ -79,27 +89,11 @@ class SimplenoteLoginFailed(Exception): class Simplenote(Singleton): """Class for interacting with the simplenote web service""" - # TODO: This should be a relative path, not an absolute one - TOKEN_FILE = "/Users/nut/Library/Application Support/Sublime Text/Packages/Simplenote/pkl/simplenote.pkl" - - if not os.path.exists("pkl"): - os.makedirs("pkl") - assert os.path.exists("pkl"), "pkl directory does not exist!" - assert os.path.isdir("pkl"), "pkl is not a directory!" - assert os.access("pkl", os.W_OK), "pkl directory is not writable!" - assert os.access("pkl", os.R_OK), "pkl directory is not readable!" - assert os.path.exists(TOKEN_FILE), "pkl/simplenote.pkl does not exist!" - def __init__(self, username: str = "", password: str = ""): """object constructor""" super().__init__() self.username = username self.password = password - if not all([self.username, self.password]): - SETTINGS = Settings(os.path.join(CONFIG.BASE_DIR, CONFIG.SETTINGS_FILE)) - self.username = SETTINGS.get("username", "") - self.password = SETTINGS.get("password", "") - logger.info(("username:", self.username, "password:", self.password)) assert all(map(bool, [self.username, self.password])), "username and password must be set" self.header = "X-Simperium-Token" self.mark = "mark" @@ -116,7 +110,7 @@ def authenticate(cls, username: str, password: str): Returns: Simplenote API token as string """ - headers = {"X-Simperium-API-Key": CONFIG.SIMPLENOTE_APP_KEY} + headers = {"X-Simperium-API-Key": SIMPLENOTE_APP_KEY} request_data = {"username": username, "password": password} logger.info(("request_data:", request_data, "headers:", headers)) response = request(URL.auth(), method="POST", headers=headers, data=request_data, data_as_json=False) @@ -135,7 +129,7 @@ def authenticate(cls, username: str, password: str): raise err assert isinstance(token, str), "token is not a string: %s" % token assert len(token) == 32, "token length is not 32: %s" % token - with open(cls.TOKEN_FILE, "wb") as fh: + with open(SIMPLENOTE_TOKEN_FILE, "wb") as fh: pickle.dump(token, fh) return token @@ -151,7 +145,7 @@ def token(self): """ if not self._token: try: - with open(self.TOKEN_FILE, "rb") as fh: + with open(SIMPLENOTE_TOKEN_FILE, "rb") as fh: token = pickle.load(fh, encoding="utf-8") self._token = token except FileNotFoundError as err: @@ -163,7 +157,7 @@ def token(self): def index( self, - limit: int = CONFIG.NOTE_FETCH_LENGTH, + limit: int = 1000, data: bool = False, ): """Method to get the note list @@ -333,6 +327,8 @@ def trash(self, note_id: str, version: Optional[int] = None): if __name__ == "__main__": + from _config import CONFIG + simplenote = Simplenote(username=CONFIG.SIMPLENOTE_USERNAME, password=CONFIG.SIMPLENOTE_PASSWORD) token = simplenote.token print("token: %s" % token) diff --git a/models.py b/models.py index 03b0901..5187408 100644 --- a/models.py +++ b/models.py @@ -4,7 +4,6 @@ from typing import Dict, List, Optional, TypedDict import uuid -from _config import CONFIG from api import Simplenote @@ -74,7 +73,7 @@ def __eq__(self, value: "Note") -> bool: return self.id == value.id and self.v == value.v @staticmethod - def index(limit: int = CONFIG.NOTE_FETCH_LENGTH, data: bool = True) -> List["Note"]: + def index(limit: int = 1000, data: bool = True) -> List["Note"]: status, result = API.index(limit, data) assert status == 0 assert isinstance(result, dict) @@ -82,7 +81,6 @@ def index(limit: int = CONFIG.NOTE_FETCH_LENGTH, data: bool = True) -> List["Not _notes = result.get("index", []) assert status == 0, "Error retrieving notes" assert isinstance(_notes, list) - logger.warning(_notes) return [Note(**note) for note in _notes] @staticmethod diff --git a/simplenote.py b/simplenote.py index d337cd7..7f1b0af 100644 --- a/simplenote.py +++ b/simplenote.py @@ -7,7 +7,6 @@ import time from typing import Any, Dict, List, Optional -from _config import CONFIG from api import Simplenote from models import Note @@ -21,11 +20,18 @@ logger = logging.getLogger() -PACKAGE_PATH = os.path.join(sublime.packages_path(), CONFIG.PROJECT_NAME) -TEMP_PATH = os.path.join(PACKAGE_PATH, "temp") -# SETTINGS = sublime.load_settings(CONFIG.SETTINGS_FILE) -SETTINGS: Settings = Settings(os.path.join(PACKAGE_PATH, CONFIG.SETTINGS_FILE)) -API = Simplenote() +SIMPLENOTE_PROJECT_NAME = os.environ.get("SIMPLENOTE_PROJECT_NAME", "Simplenote") +SIMPLENOTE_PACKAGE_PATH = os.path.join(sublime.packages_path(), SIMPLENOTE_PROJECT_NAME) +SIMPLENOTE_DEFAULT_NOTE_TITLE = os.environ.get("SIMPLENOTE_DEFAULT_NOTE_TITLE", "Untitled") +SIMPLENOTE_TEMP_PATH = os.path.join(SIMPLENOTE_PACKAGE_PATH, "temp") +_SIMPLENOTE_NOTE_CACHE_FILE = os.environ.get("SIMPLENOTE_NOTE_CACHE_FILE", "note_cache.pkl") +SIMPLENOTE_NOTE_CACHE_FILE = os.path.join(SIMPLENOTE_PACKAGE_PATH, _SIMPLENOTE_NOTE_CACHE_FILE) +SIMPLENOTE_NOTE_FETCH_LENGTH = int(os.environ.get("SIMPLENOTE_NOTE_FETCH_LENGTH", 1000)) +_SIMPLENOTE_SETTINGS_FILE = os.environ.get("SIMPLENOTE_SETTINGS_FILE", "simplenote.sublime-settings") +SIMPLENOTE_SETTINGS_FILE = os.path.join(SIMPLENOTE_PACKAGE_PATH, _SIMPLENOTE_SETTINGS_FILE) +# SETTINGS = sublime.load_settings(SIMPLENOTE_SETTINGS_FILE) +SETTINGS: Settings = Settings(SIMPLENOTE_SETTINGS_FILE) +API = Simplenote(SETTINGS.get("username", ""), SETTINGS.get("password", "")) class _BaseManager(Singleton): @@ -33,8 +39,6 @@ class _BaseManager(Singleton): class Local(_BaseManager): - _NOTE_CACHE_FILE_PATH = os.path.join(PACKAGE_PATH, CONFIG._NOTE_CACHE_FILE) - NOTE_CACHE_FILE_PATH = os.path.join(PACKAGE_PATH, CONFIG.NOTE_CACHE_FILE) def __init__(self): super().__init__() @@ -61,60 +65,24 @@ def objects(self, value: List[Note]): # @classmethod def load_notes(self): try: - with open(self._NOTE_CACHE_FILE_PATH, "rb") as cache_file: - self.notes = pickle.load(cache_file, encoding="utf-8") - except (EOFError, IOError, FileNotFoundError) as err: - logger.exception(err) - with open(self._NOTE_CACHE_FILE_PATH, "w+b") as cache_file: - pickle.dump(self._notes, cache_file) - logger.info((f"Created new note cache file: {self._NOTE_CACHE_FILE_PATH}")) - # logger.info(("Loaded notes length: ", len(cls.notes))) - try: - with open(self.NOTE_CACHE_FILE_PATH, "rb") as cache_file: + with open(SIMPLENOTE_NOTE_CACHE_FILE, "rb") as cache_file: self.objects = pickle.load(cache_file, encoding="utf-8") except (EOFError, IOError, FileNotFoundError) as err: logger.exception(err) - with open(self.NOTE_CACHE_FILE_PATH, "w+b") as cache_file: + with open(SIMPLENOTE_NOTE_CACHE_FILE, "w+b") as cache_file: pickle.dump(self._objects, cache_file) - logger.info((f"Created new objects cache file: {self.NOTE_CACHE_FILE_PATH}")) + logger.info((f"Created new objects cache file: {SIMPLENOTE_NOTE_CACHE_FILE}")) # logger.info(("Loaded objects length: ", len(cls.objects))) @staticmethod - def _save_notes(_NOTE_CACHE_FILE_PATH: str, notes: List[Dict[str, Any]]): - with open(_NOTE_CACHE_FILE_PATH, "w+b") as cache_file: - pickle.dump(notes, cache_file) - - # @classmethod - def save_notes(cls): - cls._save_notes(cls._NOTE_CACHE_FILE_PATH, cls._notes) - # objects: List[Note] = [] - # for note in cls.notes: - # d = {} - # for key in Note.__annotations__["d"].__annotations__.keys(): - # if key in note: - # d[key] = note[key] - # kwargs = { - # "id": note["key"], - # "v": note["version"], - # "d": note, - # } - # objects.append(Note(**kwargs)) - # cls.objects = objects - # cls._save_objects(cls.NOTE_CACHE_FILE_PATH, cls.objects) - - @staticmethod - def _save_objects(NOTE_CACHE_FILE_PATH: str, objects: List[Note]): - with open(NOTE_CACHE_FILE_PATH, "w+b") as cache_file: + def _save_objects(SIMPLENOTE_NOTE_CACHE_FILE: str, objects: List[Note]): + with open(SIMPLENOTE_NOTE_CACHE_FILE, "w+b") as cache_file: pickle.dump(objects, cache_file) # @classmethod def save_objects(cls): return - cls._save_objects(cls.NOTE_CACHE_FILE_PATH, cls._objects) - - # @staticmethod - # def dicts_to_models(notes: List[Dict[str, Any]]) -> List[Note]: - # return [Local.dict_to_model(note) for note in notes] + cls._save_objects(SIMPLENOTE_NOTE_CACHE_FILE, cls._objects) @staticmethod def dict_to_model(note: Dict[str, Any]) -> Note: @@ -133,10 +101,6 @@ def dict_to_model(note: Dict[str, Any]) -> Note: # def model_to_dict(note: Note) -> Dict[str, Any]: # return note.d.__dict__ - @classmethod - def save(cls): - cls.save_notes() - @staticmethod def get_filename_for_note(title_extension_map: List[Dict], note: Note) -> str: # Take out invalid characters from title and use that as base for the name @@ -165,13 +129,13 @@ def _get_filename_for_note(cls, note: Note) -> str: # @classmethod def get_path_for_note(cls, note: Note): - return os.path.join(TEMP_PATH, cls._get_filename_for_note(note)) + return os.path.join(SIMPLENOTE_TEMP_PATH, cls._get_filename_for_note(note)) # @classmethod def get_note_from_path(cls, view_filepath: str): note = None if view_filepath: - if os.path.dirname(view_filepath) == TEMP_PATH: + if os.path.dirname(view_filepath) == SIMPLENOTE_TEMP_PATH: view_note_filename = os.path.split(view_filepath)[1] list__note_filename = [ note for note in cls._objects if cls._get_filename_for_note(note) == view_note_filename @@ -192,20 +156,16 @@ def get_note_from_path(cls, view_filepath: str): class Remote(_BaseManager): - def __init__(self): - # self._api: Simplenote = Simplenote(SETTINGS.get("username", ""), SETTINGS.get("password", "")) - # self._api: Simplenote = Simplenote(CONFIG.SIMPLENOTE_USERNAME, CONFIG.SIMPLENOTE_PASSWORD) - self._api: Optional[Simplenote] = None + _api: Simplenote @functools.cached_property def api(self) -> Simplenote: - # logger.info(("instance", self._api)) - if not isinstance(self._api, Simplenote): - SETTINGS = sublime.load_settings(CONFIG.SETTINGS_FILE) + if not isinstance(getattr(self, "_api"), Simplenote): + # SETTINGS = sublime.load_settings(SIMPLENOTE_SETTINGS_FILE) + SETTINGS = Settings(SIMPLENOTE_SETTINGS_FILE) self._api: Simplenote = Simplenote( username=SETTINGS.get("username", ""), password=SETTINGS.get("password", "") ) - # self._instance = Simplenote(CONFIG.SIMPLENOTE_USERNAME, CONFIG.SIMPLENOTE_PASSWORD) assert isinstance(self._api, Simplenote), f"Invalid Simplenote instance: {self._api}" return self._api @@ -311,7 +271,7 @@ def get_note_name(note: Note): try: content = note.d.content except Exception as err: - return CONFIG.DEFAULT_NOTE_TITLE + return SIMPLENOTE_DEFAULT_NOTE_TITLE index = content.find("\n") if index > -1: title = content[:index] @@ -319,7 +279,7 @@ def get_note_name(note: Note): if content: title = content else: - title = CONFIG.DEFAULT_NOTE_TITLE + title = SIMPLENOTE_DEFAULT_NOTE_TITLE return title diff --git a/simplenotecommands.py b/simplenotecommands.py index e1b31a8..e65fb9b 100644 --- a/simplenotecommands.py +++ b/simplenotecommands.py @@ -8,6 +8,7 @@ import time from typing import Any, Dict, List +from _config import CONFIG from models import Note from operations import ( GetNotesDelta, @@ -17,10 +18,10 @@ NoteUpdater, OperationManager, ) -from simplenote import ( # cmp_to_key, - CONFIG, +from simplenote import ( SETTINGS, - TEMP_PATH, + SIMPLENOTE_SETTINGS_FILE, + SIMPLENOTE_TEMP_PATH, SimplenoteManager, get_note_name, handle_open_filename_change, @@ -107,7 +108,7 @@ def on_load(self, view: sublime.View): note = sm.local.get_note_from_path(view_filepath) assert isinstance(note, Note), "note is not a Note: %s" % type(note) logger.info(("note", note)) - SETTINGS = sublime.load_settings(CONFIG.SETTINGS_FILE) + SETTINGS = sublime.load_settings(SIMPLENOTE_SETTINGS_FILE) note_syntax = SETTINGS.get("note_syntax") assert isinstance(note_syntax, str) logger.info(("note_syntax", note_syntax)) @@ -180,7 +181,7 @@ def handle_selected(self, selected_index): open_note(selected_note) def run(self): - if not CONFIG.STARTED: + if not CONFIG.SIMPLENOTE_STARTED: if not start(): return @@ -468,14 +469,14 @@ def sync(): def start(): sync() - CONFIG.STARTED = True - return CONFIG.STARTED + CONFIG.SIMPLENOTE_STARTED = True + return CONFIG.SIMPLENOTE_STARTED def reload_if_needed(): logger.info(("Reloading", SETTINGS.get("autostart"))) # RELOAD_CALLS = locals().get("RELOAD_CALLS", -1) - RELOAD_CALLS = CONFIG.RELOAD_CALLS + RELOAD_CALLS = CONFIG.SIMPLENOTE_RELOAD_CALLS # Sublime calls this twice for some reason :( RELOAD_CALLS += 1 if RELOAD_CALLS % 2 != 0: @@ -492,11 +493,11 @@ def plugin_loaded(): if len(sm.local.objects): logger.info(("Loaded notes: ", sm.local.objects[0])) note_files = [note.filename for note in sm.local.objects] - if not os.path.exists(TEMP_PATH): - os.makedirs(TEMP_PATH) - for f in os.listdir(TEMP_PATH): + if not os.path.exists(SIMPLENOTE_TEMP_PATH): + os.makedirs(SIMPLENOTE_TEMP_PATH) + for f in os.listdir(SIMPLENOTE_TEMP_PATH): if f not in note_files: - os.remove(os.path.join(TEMP_PATH, f)) + os.remove(os.path.join(SIMPLENOTE_TEMP_PATH, f)) logger.info(("SETTINGS.__dict__: ", SETTINGS.__dict__)) logger.info(("SETTINGS.username: ", SETTINGS.get("username"))) @@ -508,4 +509,4 @@ def plugin_loaded(): reload_if_needed() -CONFIG.STARTED = False +CONFIG.SIMPLENOTE_STARTED = False