Skip to content

Instantly share code, notes, and snippets.

@KokoseiJ
Created July 3, 2022 03:00
Show Gist options
  • Save KokoseiJ/d8ee2813745acf20e29b0a7dd00da7b4 to your computer and use it in GitHub Desktop.
Save KokoseiJ/d8ee2813745acf20e29b0a7dd00da7b4 to your computer and use it in GitHub Desktop.
Fileserve rewrite

Data Structure

Root Data

Root data is the data where all the configs get stored.

It contains 2 keys:

key type description
files List[File] List of available files
users List[User] List of users

File

File data contains the data about file entry.

key type description
path string Path of a file, can be a dir or a file. Object is treated as a virtual folder if this isn't present.
name string Name of a file. Required if the folder is virtual. Will change the shown name if otherwise.
access list[string] List of groups that can access this path. If it's not present, it will be available to everyone.
Since a user is into a group with its name by default, You can use a username directly to control the access.
hidden bool Whether if the entry is visible. Default value is False.
files List[File] List of files. Only used if a folder is virtual.

File gets determined as virtual depending on whether a path value is present or not.

User

User data contains the data about the user.

key type description
id string An ID that will be used to log in.
pw string MD5(username:realm:password) where realm is "Auth".
groups List[string] List of groups this user is in. By default, a user is always in a group with its name.
from flask import Flask
import os
import json
from functools import cached_property
def log(level, *args, sep=" ", end="\n"):
print(f"[{level.upper()}] \t| {sep.join(args) + end}")
def info(*args, sep=" ", end="\n"):
return log("info", *args, sep, end)
def warn(*args, sep=" ", end="\n"):
return log("warn", *args, sep, end)
def error(*args, sep=" ", end="\n"):
return log("error", *args, sep, end)
def namefrompath(path):
return os.path.split(path.rstrip("/"))[-1]
def getindir(path, dir):
return os.path.join(path, dir)
class File:
def __init__(
self, path=None, name=None, access=[], hidden=False, files=[]):
self.path = path
self.name = name
self.access = access
self.is_hidden = hidden
self.files = files
if self.is_virtual:
if not self.name:
error("`Name` parameter is required "
"when the object is virtual.")
exit(1)
else:
if self.files:
warn("`files` parameter is ignored "
"when the object is not virtual.")
self.files = []
if self.path:
if not isinstance(self.path, str):
error("`Path` parameter should be a string.")
exit(1)
if not os.path.exists(self.path):
warn(f"{self.path} doesn't exist!")
if self.name:
if not isinstance(self.name, str):
error("`Name` parameter should be a string.")
exit(1)
else:
self.name = namefrompath(self.path)
@classmethod
def create_from_json(cls, data):
return cls(
data.get('path'),
data.get('name'),
data.get('access', []),
data.get('hidden', False),
[cls.create_from_json(file) for file in data.get('files', [])]
)
@cached_property
def is_virtual(self):
return not bool(self.path)
@property
def is_anyone(self):
return not bool(self.access)
@property
def is_file(self):
return not self.is_virtual and os.path.isfile(self.path)
@property
def is_dir(self):
return not self.is_file
@property
def exists(self):
if self.is_virtual:
return True
else:
return os.path.exists(self.path)
@property
def filemap(self):
if self.is_file:
raise TypeError("This object is a file.")
if not self.is_virtual:
self.update_files()
return {x.name: x for x in self.files}
def update_files(self):
print(self.name, "update_files")
if self.is_virtual or self.is_file:
raise TypeError("This method only works with real folder.")
path = self.path
filemap = {namefrompath(x.path): x for x in self.files}
newlist = []
for filename in os.listdir(path):
if filename in filemap:
newlist.append(filemap[filename])
else:
filepath = os.path.join(path, filename)
newlist.append(File(filepath, hidden=filename.startswith(".")))
self.files = newlist
return self.files
def load_data(file="data.json"):
with open("data.json") as f:
data = json.load(f)
files = [File.create_from_json(x) for x in data['files']]
return File(name="/", files=files), None
rootdir, users = load_data()
app = Flask("__name__")
@app.get("/favicon.ico")
def meow():
return "", 404
@app.get("/")
@app.get("/<path:itempath>")
def get_file(itempath=""):
obj = rootdir
for name in itempath.rstrip("/").split("/"):
if not name:
continue
print("searching", name)
obj = obj.filemap.get(name)
if obj is None:
return "Not found", 404
elif not obj.exists:
return "Doesn't exist", 404
if obj.is_dir:
if not obj.is_virtual:
obj.update_files()
files_sort = sorted(sorted(obj.files, key=lambda x: x.name, reverse=False), key=lambda x: x.is_file)
parentdirentry = "<tr><td></td><td><a href=\"..\">..</a></td></tr>"
tableentry = "".join([f"<tr><td>{'File' if x.is_file else 'Dir'}</td><td><a href=\"/{getindir(itempath, x.name)}\">{x.name}</a></td></tr>" for x in files_sort if not x.is_hidden])
tablehtml = f"<table><thead><tr><th>Type</th><th>Name</th></tr></thead><tbody>{parentdirentry if obj is not rootdir else ''}{tableentry}</tbody></table>"
html = f"<head></head><body><h1>Current Directory: /{itempath}</h1><br><br>{tablehtml}</body>"
return html
else:
return "This is a file but I'm too lazy to actually send it towards you"
app.run("0.0.0.0", 4199)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment