This commit is contained in:
retoor 2025-11-09 17:44:17 +01:00
parent cd259b0b81
commit 71e1dec041
5 changed files with 124 additions and 6 deletions

View File

@ -63,6 +63,12 @@ class FileService:
# Normalize path
if path and not path.endswith('/'):
path += '/'
# Update last_accessed for the folder if path is not root
if path:
folder_path = path.rstrip('/')
if folder_path in metadata and metadata[folder_path].get("type") == "dir":
metadata[folder_path]["last_accessed"] = datetime.datetime.now().isoformat()
await self._save_metadata(user_email, metadata)
items = []
seen = set()
for item_path, item_meta in metadata.items():
@ -91,6 +97,7 @@ class FileService:
"type": "dir",
"created_at": datetime.datetime.now().isoformat(),
"modified_at": datetime.datetime.now().isoformat(),
"last_accessed": datetime.datetime.now().isoformat(),
}
await self._save_metadata(user_email, metadata)
logger.info(f"create_folder: Folder created: {folder_path}")
@ -115,6 +122,7 @@ class FileService:
"blob_location": {"drive": drive, "path": f"{dir1}/{dir2}/{dir3}/{hash}"},
"created_at": datetime.datetime.now().isoformat(),
"modified_at": datetime.datetime.now().isoformat(),
"last_accessed": datetime.datetime.now().isoformat(),
}
await self._save_metadata(user_email, metadata)
logger.info(f"upload_file: File uploaded to drive {drive}: {file_path}")
@ -127,6 +135,9 @@ class FileService:
logger.warning(f"download_file: File not found in metadata: {file_path}")
return None
item_meta = metadata[file_path]
# Update last_accessed
item_meta["last_accessed"] = datetime.datetime.now().isoformat()
await self._save_metadata(user_email, metadata)
blob_loc = item_meta["blob_location"]
blob_path = self.drives_dir / blob_loc["drive"] / blob_loc["path"]
if not blob_path.exists():
@ -144,6 +155,9 @@ class FileService:
logger.warning(f"read_file_content: File not found in metadata: {file_path}")
return None
item_meta = metadata[file_path]
# Update last_accessed
item_meta["last_accessed"] = datetime.datetime.now().isoformat()
await self._save_metadata(user_email, metadata)
blob_loc = item_meta["blob_location"]
blob_path = self.drives_dir / blob_loc["drive"] / blob_loc["path"]
if not blob_path.exists():
@ -168,6 +182,9 @@ class FileService:
logger.warning(f"read_file_content: File not found in metadata: {file_path}")
return None
item_meta = metadata[file_path]
# Update last_accessed
item_meta["last_accessed"] = datetime.datetime.now().isoformat()
await self._save_metadata(user_email, metadata)
blob_loc = item_meta["blob_location"]
blob_path = self.drives_dir / blob_loc["drive"] / blob_loc["path"]
if not blob_path.exists():
@ -303,6 +320,7 @@ class FileService:
"type": "dir",
"created_at": datetime.datetime.now().isoformat(),
"modified_at": datetime.datetime.now().isoformat(),
"last_accessed": datetime.datetime.now().isoformat(),
}
migrated_count += 1
for file_name in files:
@ -330,9 +348,28 @@ class FileService:
"blob_location": {"drive": drive, "path": f"{dir1}/{dir2}/{dir3}/{hash}"},
"created_at": datetime.datetime.fromtimestamp(full_file_path.stat().st_ctime).isoformat(),
"modified_at": datetime.datetime.fromtimestamp(full_file_path.stat().st_mtime).isoformat(),
"last_accessed": datetime.datetime.fromtimestamp(full_file_path.stat().st_atime).isoformat(),
}
migrated_count += 1
await self._save_metadata(user_email, metadata)
logger.info(f"Migrated {migrated_count} items for {user_email}")
# Optionally remove old dir
# shutil.rmtree(old_user_dir)
async def get_recent_files(self, user_email: str, limit: int = 50) -> list:
"""Gets the most recently accessed files and folders for the user."""
metadata = await self._load_metadata(user_email)
items = []
for path, meta in metadata.items():
if meta.get("type") in ("file", "dir"):
last_accessed = meta.get("last_accessed", meta.get("modified_at", ""))
items.append({
"path": path,
"name": Path(path).name,
"is_dir": meta["type"] == "dir",
"size": meta.get("size", 0) if meta["type"] == "file" else 0,
"last_accessed": last_accessed,
})
# Sort by last_accessed descending
items.sort(key=lambda x: x["last_accessed"], reverse=True)
return items[:limit]

View File

@ -211,3 +211,48 @@ class UserService:
await self._storage_manager.save_user(email, user)
else:
await self._save_users()
async def add_favorite(self, email: str, file_path: str) -> bool:
user = await self.get_user_by_email(email)
if not user:
return False
if "favorites" not in user:
user["favorites"] = []
if file_path not in user["favorites"]:
user["favorites"].append(file_path)
if self.use_isolated_storage:
await self._storage_manager.save_user(email, user)
else:
await self._save_users()
return True
async def remove_favorite(self, email: str, file_path: str) -> bool:
user = await self.get_user_by_email(email)
if not user:
return False
if "favorites" in user and file_path in user["favorites"]:
user["favorites"].remove(file_path)
if self.use_isolated_storage:
await self._storage_manager.save_user(email, user)
else:
await self._save_users()
return True
async def get_favorites(self, email: str) -> List[str]:
user = await self.get_user_by_email(email)
if not user:
return []
return user.get("favorites", [])
async def is_favorite(self, email: str, file_path: str) -> bool:
user = await self.get_user_by_email(email)
if not user:
return False
return file_path in user.get("favorites", [])

View File

@ -8,6 +8,7 @@
{% block content %}
<main>
{#
<section class="hero-section">
<div class="hero-content">
<h1>Your files, safe and accessible everywhere</h1>
@ -89,9 +90,9 @@
</div>
</div>
</section>
#}
<section class="use-cases-section">
<h2>Perfect for every need</h2>
<h2>Storage for every need</h2>
<div class="use-cases-grid">
<div class="use-case-card">
<img src="/static/images/icon-families.svg" alt="Families Icon" class="use-case-icon">

View File

@ -53,8 +53,14 @@
<a href="/files?path={{ item.path }}">{{ item.name }}</a>
{% else %}
<img src="/static/images/icon-professionals.svg" alt="File Icon" class="file-icon">
{% if item.is_editable %}
<a href="/editor?path={{ item.path }}">{{ item.name }}</a>
{% elif item.is_viewable %}
<a href="/viewer?path={{item.path}}">{{ item.name }}</a>
{% else %}
{{ item.name }}
{% endif %}
{% endif %}
</td>
<td>{{ user.email }}</td>
<td>{{ item.last_accessed[:10] }}</td>

View File

@ -142,8 +142,22 @@ class SiteView(web.View):
@login_required
async def recent(self):
user_email = self.request["user"]["email"]
file_service = self.request.app["file_service"]
recent_files = await file_service.get_recent_files(user_email)
# Determine editable and viewable files based on extension
editable_extensions = {'.txt', '.md', '.py', '.js', '.html', '.css', '.json', '.xml', '.yaml', '.yml', '.ini', '.cfg', '.log', '.sh', '.bat', '.ps1', '.php', '.rb', '.java', '.c', '.cpp', '.h', '.hpp'}
viewable_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv', '.mp3', '.wav', '.flac', '.aac', '.ogg', '.m4a'}
for item in recent_files:
if not item['is_dir']:
from pathlib import Path
ext = Path(item['name']).suffix.lower()
item['is_editable'] = ext in editable_extensions
item['is_viewable'] = ext in viewable_extensions
return aiohttp_jinja2.render_template(
"pages/recent.html", self.request, {"request": self.request, "errors": {}, "user": self.request["user"], "active_page": "recent"}
"pages/recent.html", self.request, {"request": self.request, "errors": {}, "user": self.request["user"], "active_page": "recent", "recent_files": recent_files}
)
@login_required
@ -186,11 +200,13 @@ class FileBrowserView(web.View):
# Determine editable and viewable files based on extension
editable_extensions = {'.txt', '.md', '.py', '.js', '.html', '.css', '.json', '.xml', '.yaml', '.yml', '.ini', '.cfg', '.log', '.sh', '.bat', '.ps1', '.php', '.rb', '.java', '.c', '.cpp', '.h', '.hpp'}
viewable_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv', '.mp3', '.wav', '.flac', '.aac', '.ogg', '.m4a'}
user_service = self.request.app["user_service"]
for item in files:
if not item['is_dir']:
ext = Path(item['name']).suffix.lower()
item['is_editable'] = ext in editable_extensions
item['is_viewable'] = ext in viewable_extensions
item['is_favorite'] = await user_service.is_favorite(user_email, item['path'])
success_message = self.request.query.get("success")
error_message = self.request.query.get("error")
@ -348,6 +364,19 @@ class FileBrowserView(web.View):
logger.error(f"FileBrowserView: Failed to generate any share links for user {user_email}")
return json_response({"error": "Failed to generate share links for any selected items"}, status=500)
elif route_name == "toggle_favorite":
data = await self.request.json()
file_path = data.get("file_path")
if not file_path:
return json_response({"error": "File path is required"}, status=400)
user_service = self.request.app["user_service"]
is_fav = await user_service.is_favorite(user_email, file_path)
if is_fav:
await user_service.remove_favorite(user_email, file_path)
else:
await user_service.add_favorite(user_email, file_path)
return json_response({"is_favorite": not is_fav})
logger.warning(f"FileBrowserView: Unknown file action for POST request: {route_name}")
raise web.HTTPBadRequest(text="Unknown file action")