progress in image viewing
authorPhiTux <redacted>
Tue, 28 Jan 2025 17:37:27 +0000 (18:37 +0100)
committerPhiTux <redacted>
Tue, 28 Jan 2025 17:37:27 +0000 (18:37 +0100)
backend/server/routers/logs.py
backend/server/utils/fileHandling.py
backend/server/utils/security.py
frontend/src/routes/write/+page.svelte

index 2c8558f87a546ada251746f72947c85a5f6c4a58..7027a2b2d31b600eeb7a8fdec505a784bad6f9fd 100644 (file)
@@ -1,3 +1,4 @@
+import base64
 import datetime
 import logging
 import re
@@ -314,4 +315,12 @@ async def deleteFile(uuid: str, day: int, month: int, year: int, cookie = Depend
                     raise HTTPException(status_code=500, detail="Failed to write changes of deleted file!")
                 return {"success": True}
 
-    raise HTTPException(status_code=500, detail="Failed to delete file - not found in log")
\ No newline at end of file
+    raise HTTPException(status_code=500, detail="Failed to delete file - not found in log")
+
+@router.get("/downloadFile")
+async def downloadFile(uuid: str, cookie = Depends(users.isLoggedIn)):
+    enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"])
+    file = fileHandling.readFile(cookie["user_id"], uuid)
+    if file is None:
+        raise HTTPException(status_code=500, detail="Failed to read file")
+    return {"file": base64.b64encode(security.decrypt_file(file, enc_key))}
\ No newline at end of file
index 0ca7a4c05a69ea9870465c35e7c334c440c08f35..82d506cc6087dda6b097e15a6ff3cee066fa7e0d 100644 (file)
@@ -85,6 +85,19 @@ def writeFile(file, user_id, uuid):
             f.write(file)
             return True
         
+def readFile(user_id, uuid):
+    try:
+        f = open(os.path.join(settings.data_path, str(user_id), 'files', uuid), "r")
+    except FileNotFoundError:
+        logger.info(f"{user_id}/files/{uuid} - File not found")
+        raise HTTPException(status_code=404, detail="File not found")
+    except Exception as e:
+        logger.exception(e)
+        raise HTTPException(status_code=500, detail="Internal Server Error when trying to open file {uuid}")
+    else:
+        with f:
+            return f.read()
+        
 def removeFile(user_id, uuid):
     try:
         os.remove(os.path.join(settings.data_path, str(user_id), 'files', uuid))
index 349f5d30700252dd684f261c9fab8acd6735f03b..aec5102611d7eaa4f6cc9c1631171f36d07b9363 100644 (file)
@@ -43,4 +43,8 @@ def decrypt_text(text: str, key: str) -> str:
 
 def encrypt_file(file: bytes, key: str) -> str:
     f = Fernet(key)
-    return f.encrypt(file).decode()
\ No newline at end of file
+    return f.encrypt(file).decode()
+
+def decrypt_file(file: str, key: str) -> bytes:
+    f = Fernet(key)
+    return f.decrypt(file.encode())
\ No newline at end of file
index f903fa1c2be1ab343e49a87b227cb47eb939b8a4..b662567f3c7f1eae6acc4e9be50dc8d153f2b845 100644 (file)
@@ -14,7 +14,7 @@
        import { faCloudArrowUp, faTrash } from '@fortawesome/free-solid-svg-icons';
        import Fa from 'svelte-fa';
        import { v4 as uuidv4 } from 'uuid';
-       import { slide } from 'svelte/transition';
+       import { slide, fade } from 'svelte/transition';
 
        axios.interceptors.request.use((config) => {
                config.withCredentials = true;
@@ -71,6 +71,7 @@
        });
 
        let lastSelectedDate = $state($selectedDate);
+       let images = $state([]);
 
        let loading = false;
        $effect(() => {
@@ -78,6 +79,9 @@
                loading = true;
 
                if ($selectedDate !== lastSelectedDate) {
+                       images = [];
+                       filesOfDay = [];
+
                        clearTimeout(timeout);
                        const result = getLog();
                        if (result) {
                }
        }
 
+       const imageExtensions = ['jpeg', 'jpg', 'gif', 'png'];
+
+       function base64ToArrayBuffer(base64) {
+               var binaryString = atob(base64);
+               var bytes = new Uint8Array(binaryString.length);
+               for (var i = 0; i < binaryString.length; i++) {
+                       bytes[i] = binaryString.charCodeAt(i);
+               }
+               return bytes.buffer;
+       }
+
+       $effect(() => {
+               if (filesOfDay) {
+                       // add all files to images if correct extension
+                       filesOfDay.forEach((file) => {
+                               // if image -> load it!
+                               if (
+                                       imageExtensions.includes(file.filename.split('.').pop().toLowerCase()) &&
+                                       !images.find((image) => image.uuid_filename === file.uuid_filename)
+                               ) {
+                                       images = [...images, file];
+                                       axios
+                                               .get(API_URL + '/logs/downloadFile', { params: { uuid: file.uuid_filename } })
+                                               .then((response) => {
+                                                       images = images.map((image) => {
+                                                               if (image.uuid_filename === file.uuid_filename) {
+                                                                       image.src = response.data.file;
+                                                               }
+                                                               return image;
+                                                       });
+                                               })
+                                               .catch((error) => {
+                                                       console.error(error);
+                                               });
+                               }
+                       });
+               }
+       });
+
        async function saveLog() {
                if (currentLog === savedLog) {
                        return true;
                if (!+bytes) return '0 Bytes';
 
                const k = 1024;
-               const dm = 2;
-               const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+               //const dm = 2; // decimal places
+               const sizes = ['B', 'KB', 'MB', 'GB'];
 
                const i = Math.floor(Math.log(bytes) / Math.log(k));
 
-               return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
+               return `${parseFloat((bytes / Math.pow(k, i)).toFixed(0))} ${sizes[i]}`;
        }
 
        function downloadFile(uuid) {
                        })
                        .then((response) => {
                                filesOfDay = filesOfDay.filter((file) => file.uuid_filename !== uuid);
+                               images = images.filter((image) => image.uuid_filename !== uuid);
                        })
                        .catch((error) => {
                                console.error(error);
                                <div id="toolbar"></div>
                                <div id="editor"></div>
                        </div>
+                       {#if images.length > 0}
+                               <div class="d-flex flex-row images mt-3">
+                                       {#each images as image (image.uuid_filename)}
+                                               <div class="imageContainer d-flex align-items-center" transition:slide={{ axis: 'x' }}>
+                                                       {#if image.src}
+                                                               <img
+                                                                       transition:fade
+                                                                       class="image"
+                                                                       alt={image.filename}
+                                                                       src={'data:image/jpg;base64,' + image.src}
+                                                               />
+                                                       {:else}
+                                                               <div class="spinner-border" role="status">
+                                                                       <span class="visually-hidden">Loading...</span>
+                                                               </div>
+                                                       {/if}
+                                               </div>
+                                       {/each}
+                               </div>
+                       {/if}
                        {$selectedDate}<br />
                        {lastSelectedDate}
                </div>
 </div>
 
 <style>
+       .image,
+       .imageContainer {
+               border-radius: 8px;
+       }
+
+       .imageContainer {
+               min-height: 80px;
+       }
+
+       .image:hover {
+               transform: scale(1.1);
+               box-shadow: 0 0 12px 3px rgba(0, 0, 0, 0.2);
+       }
+
+       .image {
+               max-width: 250px;
+               max-height: 150px;
+               transition: all ease 0.3s;
+       }
+
+       .images {
+               gap: 1rem;
+       }
+
        :global(.modal.show) {
                background-color: rgba(80, 80, 80, 0.1) !important;
                backdrop-filter: blur(2px) saturate(150%);
        .filesize {
                opacity: 0.7;
                font-size: 0.8rem;
+               white-space: nowrap;
        }
 
        .fileBtn {
git clone https://git.99rst.org/PROJECT