From b684c293b0ed284764b30ec65f686b0d290d4a65 Mon Sep 17 00:00:00 2001
From: Remy Moll <me@moll.re>
Date: Tue, 14 Nov 2023 17:06:31 +0100
Subject: [PATCH] Refactor get process to add multiple sources later on

---
 deploy/deploy.playbook.yml               |  7 +-
 deploy/templates/eink-show.timer.j2      |  2 +-
 image_get.py                             | 94 ------------------------
 src/get/combined.py                      |  0
 src/get/immich.py                        | 57 ++++++++++++++
 src/get/public.py                        | 24 ++++++
 src/get/template.py                      | 67 +++++++++++++++++
 image_convert.py => src/image_convert.py |  0
 image_show.py => src/image_show.py       |  0
 main.py => src/main.py                   | 18 ++---
 10 files changed, 162 insertions(+), 107 deletions(-)
 delete mode 100644 image_get.py
 create mode 100644 src/get/combined.py
 create mode 100644 src/get/immich.py
 create mode 100644 src/get/public.py
 create mode 100644 src/get/template.py
 rename image_convert.py => src/image_convert.py (100%)
 rename image_show.py => src/image_show.py (100%)
 rename main.py => src/main.py (59%)

diff --git a/deploy/deploy.playbook.yml b/deploy/deploy.playbook.yml
index bba8d1c..f6796f3 100644
--- a/deploy/deploy.playbook.yml
+++ b/deploy/deploy.playbook.yml
@@ -4,14 +4,15 @@
   # become: true
 
   vars:
-    code_dest: /home/remy/eink
+    repo_dest: /home/remy/eink
+    code_dest: /home/remy/eink/src
     service_target_dir: /etc/systemd/system/
 
   tasks:
     - name: Pull the latest version of the code
       git:
         repo: https://git.kluster.moll.re/remoll/rpi-eink-picture-frame
-        dest: "{{ code_dest }}"
+        dest: "{{ repo_dest }}"
         version: main
 
     - name: Install pillow dependencies
@@ -25,7 +26,7 @@
     - name: Install from the pipenv-file
       command: "pipenv install --system --deploy --categories=\"packages prod-packages\""
       args:
-        chdir: "{{ code_dest }}"
+        chdir: "{{ repo_dest }}"
 
     - name: Copy keys python file
       copy:
diff --git a/deploy/templates/eink-show.timer.j2 b/deploy/templates/eink-show.timer.j2
index 26be874..fa6722f 100644
--- a/deploy/templates/eink-show.timer.j2
+++ b/deploy/templates/eink-show.timer.j2
@@ -2,7 +2,7 @@
 Description=Run photo update regularly
 
 [Timer]
-OnCalendar=08:00
+OnCalendar=7:30
 OnCalendar=16:00
 Persistent=true
 
diff --git a/image_get.py b/image_get.py
deleted file mode 100644
index f8b2400..0000000
--- a/image_get.py
+++ /dev/null
@@ -1,94 +0,0 @@
-import httpx as h
-import json
-import random
-from pathlib import Path
-import uuid
-import keys
-
-
-class ImageGetException(Exception):
-    pass
-
-
-class ImageGetter:
-    headers = {
-        "x-api-key": keys.immich_api_key
-    }
-    cached_num = 10
-    cached_path = Path("./.image-cache/")
-
-
-    def get_random_image(self) -> bytearray:
-        try:
-            id = self.get_random_image_ids()[0]
-            bytes = self.get_image_file(id)
-            self.save_cached_files()
-        except (h.ConnectError, h.HTTPStatusError, h.NetworkError, h.RequestError, h.DecodingError, h.TransportError, ImageGetException):
-            print("Loading image from cache")
-            bytes = self.load_cached_file()
-
-        return bytes
-
-
-    def get_random_image_ids(self, num=1) -> str:
-        url = keys.immich_api_root_url + "album/" + keys.immich_album_id
-        headers = self.headers | {"Accept": "application/json"}
-        
-        response = h.request("GET", url, headers=headers, data={})
-        # raises an htppx exception if anything goes wrong
-        if response.status_code == 200:
-            response = json.loads(response.text)
-        else:
-            raise ImageGetException("Error in step get_random_image_id: " + str(response.status_code))
-
-        images = response['assets']
-
-        print(f"Picking {num} random id(s) out of {len(images)} album images")
-        ids = []
-        for i in range(num):
-            image = random.choice(images)
-            print(f"Image considered: {image['exifInfo']}")
-            ids.append(image["id"])
-
-        return ids
-
-
-    def get_image_file(self, image_id: str) -> bytearray:
-        url = keys.immich_api_root_url + "asset/download/" + image_id
-
-        headers = self.headers | {"Accept": "application/octet-stream"}
-        response = h.request("POST", url, headers=headers, data={})
-        if not response.status_code == 200:
-            raise ImageGetException("Error in step get_image_file: " + str(response.status_code))
-            
-        return response.content
-
-
-    def save_cached_files(self) -> None:
-        """Ensures self.cached_num files are at self.cached_path at any time"""
-        if not self.cached_path.exists():
-            self.cached_path.mkdir()
-
-        present_count = len(list(self.cached_path.glob("*")))
-        missing = self.cached_num - present_count
-        if missing == 0:
-            return
-    
-        ids = self.get_random_image_ids(missing)
-        for i, id in enumerate(ids):
-            print(f"Caching image {i + 1}")
-            new_cache = self.cached_path / f"{uuid.uuid4()}"
-            new_cache.write_bytes(self.get_image_file(id))
-
-
-    def load_cached_file(self) -> bytearray:
-        """Returns a random file from self.cached_path"""
-        files = list(self.cached_path.glob("*"))
-
-        if len(files) == 0:
-            raise ImageGetException("Could not load cached file: directory empty")
-
-        file = random.choice(files)
-        bytes  = file.read_bytes()
-        file.unlink()
-        return bytes
diff --git a/src/get/combined.py b/src/get/combined.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/get/immich.py b/src/get/immich.py
new file mode 100644
index 0000000..2423d23
--- /dev/null
+++ b/src/get/immich.py
@@ -0,0 +1,57 @@
+import keys
+import httpx as h
+import random
+import json
+
+from .template import ImageGet, ImageGetException
+
+class ImageGetImmich(ImageGet):
+
+    def __init__(self) -> None:
+        headers = {
+            "x-api-key": keys.immich_api_key
+        }
+
+        super().__init__(
+            base_url=keys.immich_api_root_url,
+            headers=headers
+            )
+
+    def get_random_image_ids(self, num=1) -> str:
+        url = keys.immich_api_root_url + "album/" + keys.immich_album_id
+        headers = self.headers | {"Accept": "application/json"}
+        
+        response = h.request("GET", url, headers=headers, data={})
+        # raises an htppx exception if anything goes wrong
+        if response.status_code == 200:
+            response = json.loads(response.text)
+        else:
+            raise ImageGetException(f"Error in step get_random_image_id: {response.status_code}")
+
+        images = response['assets']
+
+        print(f"Picking {num} random id(s) out of {len(images)} album images")
+        ids = []
+        for i in range(num):
+            image = random.choice(images)
+            print(f"Image considered: {image['exifInfo']}")
+            ids.append(image["id"])
+
+        return ids
+
+
+    def get_image_file(self, image_id: str) -> bytearray:
+        url = keys.immich_api_root_url + "asset/download/" + image_id
+
+        headers = self.headers | {"Accept": "application/octet-stream"}
+        response = h.request("POST", url, headers=headers, data={})
+        if not response.status_code == 200:
+            raise ImageGetException("Error in step get_image_file: " + str(response.status_code))
+            
+        return response.content
+
+
+    # def save_cached_files(self) -> None:
+        # in super
+        # return
+
diff --git a/src/get/public.py b/src/get/public.py
new file mode 100644
index 0000000..e679414
--- /dev/null
+++ b/src/get/public.py
@@ -0,0 +1,24 @@
+import keys
+import httpx as h
+import random
+import json
+
+from image_get import ImageGet, ImageGetException
+
+class ImageGetImmich(ImageGet):
+
+    def __init__(self) -> None:
+        headers = {
+            "AIC-User-Agent": "Eink Art Display (remoll@ethz.ch)"
+        }
+
+        super().__init__(
+            base_url=keys.immich_api_root_url,
+            headers=self.headers
+            )
+    
+    def get_random_image_ids(self) -> list[str]:
+        raise NotImplementedError
+    
+    def get_image_file(self, image_id: str) -> bytearray:
+        raise NotImplementedError
diff --git a/src/get/template.py b/src/get/template.py
new file mode 100644
index 0000000..63f3214
--- /dev/null
+++ b/src/get/template.py
@@ -0,0 +1,67 @@
+import httpx as h
+import random
+from pathlib import Path
+import uuid
+
+
+class ImageGetException(Exception):
+    pass
+
+
+class ImageGet:
+    def __init__(self, base_url, headers, cache_num = 10) -> None:
+        self.base_url = base_url
+        self.headers = headers
+        self.cache_num = cache_num
+        self.cache_dir = Path("../.cache/{self.__class__.__name__}/")
+        self.cache_dir.mkdir(parents=True, exist_ok=True)
+
+
+    # Main entrypoint, called externally
+    def get_random_image(self) -> bytearray:
+        try:
+            id = self.get_random_image_ids()[0]
+            bytes = self.get_image_file(id)
+            # self.save_cached_files()
+        except (h.ConnectError, h.HTTPStatusError, h.NetworkError, h.RequestError, h.DecodingError, h.TransportError, ImageGetException):
+            print("Loading image from cache")
+            bytes = self.load_cached_file()
+
+        return bytes
+
+
+    def save_cached_files(self) -> None:
+        """Ensures self.cache_num files are at self.cache_dir at any time"""
+
+        present_count = len(list(self.cached_path.glob("*")))
+        missing = self.cached_num - present_count
+        if missing == 0:
+            return
+    
+        ids = self.get_random_image_ids(missing)
+        for i, id in enumerate(ids):
+            print(f"Caching image {i + 1}")
+            new_cache = self.cached_path / f"{uuid.uuid4()}"
+            new_cache.write_bytes(self.get_image_file(id))
+
+
+    def load_cached_file(self) -> bytearray:
+        """Returns a random file from self.cached_path"""
+        files = list(self.cached_path.glob("*"))
+
+        if len(files) == 0:
+            raise ImageGetException("Could not load cached file: directory empty")
+
+        file = random.choice(files)
+        bytes  = file.read_bytes()
+        file.unlink()
+        return bytes
+
+
+
+    def get_random_image_ids(self) -> list[str]:
+        raise NotImplementedError
+    
+
+    def get_image_file(self, image_id: str) -> bytearray:
+        raise NotImplementedError
diff --git a/image_convert.py b/src/image_convert.py
similarity index 100%
rename from image_convert.py
rename to src/image_convert.py
diff --git a/image_show.py b/src/image_show.py
similarity index 100%
rename from image_show.py
rename to src/image_show.py
diff --git a/main.py b/src/main.py
similarity index 59%
rename from main.py
rename to src/main.py
index b73347c..429ef89 100644
--- a/main.py
+++ b/src/main.py
@@ -1,12 +1,12 @@
 import sys
 from image_convert import ImageShrink
-from image_get import ImageGetter
+from get import immich
 from image_show import ImageShow
 
 if len(sys.argv) == 2 and sys.argv[1] == "test":
     print("Running test")
-    show = ImageShow()
-    show.draw_sample_image()
+    shower = ImageShow()
+    shower.draw_sample_image()
     sys.exit()
 
 
@@ -18,10 +18,10 @@ if "noreduce" in sys.argv:
     print("Disabling color reduction")
     shrink_kwargs["colors"] = -1
 
-get = ImageGetter()
-convert = ImageShrink(**shrink_kwargs)
-show = ImageShow()
+getter = immich.ImageGetImmich()
+converter = ImageShrink(**shrink_kwargs)
+shower = ImageShow()
 
-image = get.get_random_image()
-image = convert.convert(image)
-show.show_image(image)
+image = getter.get_random_image()
+image = converter.convert(image)
+shower.show_image(image)