Update.
This commit is contained in:
parent
cd259b0b81
commit
71e1dec041
@ -63,6 +63,12 @@ class FileService:
|
|||||||
# Normalize path
|
# Normalize path
|
||||||
if path and not path.endswith('/'):
|
if path and not path.endswith('/'):
|
||||||
path += '/'
|
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 = []
|
items = []
|
||||||
seen = set()
|
seen = set()
|
||||||
for item_path, item_meta in metadata.items():
|
for item_path, item_meta in metadata.items():
|
||||||
@ -91,6 +97,7 @@ class FileService:
|
|||||||
"type": "dir",
|
"type": "dir",
|
||||||
"created_at": datetime.datetime.now().isoformat(),
|
"created_at": datetime.datetime.now().isoformat(),
|
||||||
"modified_at": datetime.datetime.now().isoformat(),
|
"modified_at": datetime.datetime.now().isoformat(),
|
||||||
|
"last_accessed": datetime.datetime.now().isoformat(),
|
||||||
}
|
}
|
||||||
await self._save_metadata(user_email, metadata)
|
await self._save_metadata(user_email, metadata)
|
||||||
logger.info(f"create_folder: Folder created: {folder_path}")
|
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}"},
|
"blob_location": {"drive": drive, "path": f"{dir1}/{dir2}/{dir3}/{hash}"},
|
||||||
"created_at": datetime.datetime.now().isoformat(),
|
"created_at": datetime.datetime.now().isoformat(),
|
||||||
"modified_at": datetime.datetime.now().isoformat(),
|
"modified_at": datetime.datetime.now().isoformat(),
|
||||||
|
"last_accessed": datetime.datetime.now().isoformat(),
|
||||||
}
|
}
|
||||||
await self._save_metadata(user_email, metadata)
|
await self._save_metadata(user_email, metadata)
|
||||||
logger.info(f"upload_file: File uploaded to drive {drive}: {file_path}")
|
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}")
|
logger.warning(f"download_file: File not found in metadata: {file_path}")
|
||||||
return None
|
return None
|
||||||
item_meta = metadata[file_path]
|
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_loc = item_meta["blob_location"]
|
||||||
blob_path = self.drives_dir / blob_loc["drive"] / blob_loc["path"]
|
blob_path = self.drives_dir / blob_loc["drive"] / blob_loc["path"]
|
||||||
if not blob_path.exists():
|
if not blob_path.exists():
|
||||||
@ -144,6 +155,9 @@ class FileService:
|
|||||||
logger.warning(f"read_file_content: File not found in metadata: {file_path}")
|
logger.warning(f"read_file_content: File not found in metadata: {file_path}")
|
||||||
return None
|
return None
|
||||||
item_meta = metadata[file_path]
|
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_loc = item_meta["blob_location"]
|
||||||
blob_path = self.drives_dir / blob_loc["drive"] / blob_loc["path"]
|
blob_path = self.drives_dir / blob_loc["drive"] / blob_loc["path"]
|
||||||
if not blob_path.exists():
|
if not blob_path.exists():
|
||||||
@ -168,6 +182,9 @@ class FileService:
|
|||||||
logger.warning(f"read_file_content: File not found in metadata: {file_path}")
|
logger.warning(f"read_file_content: File not found in metadata: {file_path}")
|
||||||
return None
|
return None
|
||||||
item_meta = metadata[file_path]
|
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_loc = item_meta["blob_location"]
|
||||||
blob_path = self.drives_dir / blob_loc["drive"] / blob_loc["path"]
|
blob_path = self.drives_dir / blob_loc["drive"] / blob_loc["path"]
|
||||||
if not blob_path.exists():
|
if not blob_path.exists():
|
||||||
@ -303,6 +320,7 @@ class FileService:
|
|||||||
"type": "dir",
|
"type": "dir",
|
||||||
"created_at": datetime.datetime.now().isoformat(),
|
"created_at": datetime.datetime.now().isoformat(),
|
||||||
"modified_at": datetime.datetime.now().isoformat(),
|
"modified_at": datetime.datetime.now().isoformat(),
|
||||||
|
"last_accessed": datetime.datetime.now().isoformat(),
|
||||||
}
|
}
|
||||||
migrated_count += 1
|
migrated_count += 1
|
||||||
for file_name in files:
|
for file_name in files:
|
||||||
@ -330,9 +348,28 @@ class FileService:
|
|||||||
"blob_location": {"drive": drive, "path": f"{dir1}/{dir2}/{dir3}/{hash}"},
|
"blob_location": {"drive": drive, "path": f"{dir1}/{dir2}/{dir3}/{hash}"},
|
||||||
"created_at": datetime.datetime.fromtimestamp(full_file_path.stat().st_ctime).isoformat(),
|
"created_at": datetime.datetime.fromtimestamp(full_file_path.stat().st_ctime).isoformat(),
|
||||||
"modified_at": datetime.datetime.fromtimestamp(full_file_path.stat().st_mtime).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
|
migrated_count += 1
|
||||||
await self._save_metadata(user_email, metadata)
|
await self._save_metadata(user_email, metadata)
|
||||||
logger.info(f"Migrated {migrated_count} items for {user_email}")
|
logger.info(f"Migrated {migrated_count} items for {user_email}")
|
||||||
# Optionally remove old dir
|
# Optionally remove old dir
|
||||||
# shutil.rmtree(old_user_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]
|
||||||
|
|||||||
@ -211,3 +211,48 @@ class UserService:
|
|||||||
await self._storage_manager.save_user(email, user)
|
await self._storage_manager.save_user(email, user)
|
||||||
else:
|
else:
|
||||||
await self._save_users()
|
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", [])
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main>
|
||||||
|
{#
|
||||||
<section class="hero-section">
|
<section class="hero-section">
|
||||||
<div class="hero-content">
|
<div class="hero-content">
|
||||||
<h1>Your files, safe and accessible everywhere</h1>
|
<h1>Your files, safe and accessible everywhere</h1>
|
||||||
@ -89,9 +90,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
#}
|
||||||
<section class="use-cases-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-cases-grid">
|
||||||
<div class="use-case-card">
|
<div class="use-case-card">
|
||||||
<img src="/static/images/icon-families.svg" alt="Families Icon" class="use-case-icon">
|
<img src="/static/images/icon-families.svg" alt="Families Icon" class="use-case-icon">
|
||||||
|
|||||||
@ -52,8 +52,14 @@
|
|||||||
<img src="/static/images/icon-families.svg" alt="Folder Icon" class="file-icon">
|
<img src="/static/images/icon-families.svg" alt="Folder Icon" class="file-icon">
|
||||||
<a href="/files?path={{ item.path }}">{{ item.name }}</a>
|
<a href="/files?path={{ item.path }}">{{ item.name }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<img src="/static/images/icon-professionals.svg" alt="File Icon" class="file-icon">
|
<img src="/static/images/icon-professionals.svg" alt="File Icon" class="file-icon">
|
||||||
{{ item.name }}
|
{% 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 %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ user.email }}</td>
|
<td>{{ user.email }}</td>
|
||||||
@ -119,4 +125,4 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="/static/js/main.js"></script>
|
<script type="module" src="/static/js/main.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -142,8 +142,22 @@ class SiteView(web.View):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
async def recent(self):
|
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(
|
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
|
@login_required
|
||||||
@ -186,11 +200,13 @@ class FileBrowserView(web.View):
|
|||||||
# Determine editable and viewable files based on extension
|
# 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'}
|
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'}
|
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:
|
for item in files:
|
||||||
if not item['is_dir']:
|
if not item['is_dir']:
|
||||||
ext = Path(item['name']).suffix.lower()
|
ext = Path(item['name']).suffix.lower()
|
||||||
item['is_editable'] = ext in editable_extensions
|
item['is_editable'] = ext in editable_extensions
|
||||||
item['is_viewable'] = ext in viewable_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")
|
success_message = self.request.query.get("success")
|
||||||
error_message = self.request.query.get("error")
|
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}")
|
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)
|
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}")
|
logger.warning(f"FileBrowserView: Unknown file action for POST request: {route_name}")
|
||||||
raise web.HTTPBadRequest(text="Unknown file action")
|
raise web.HTTPBadRequest(text="Unknown file action")
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user