Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

43 changed files with 144 additions and 438 deletions

View File

@ -6,6 +6,6 @@ RUN wget https://retoor.molodetz.nl/api/packages/retoor/generic/r/1.0.0/r
RUN chmod +x r
RUN mv r /usr/local/bin
RUN cp r /usr/local/bin
CMD ["r"]

View File

@ -16,7 +16,7 @@ requires-python = ">=3.12"
dependencies = [
"mkdocs>=1.4.0",
"lxml",
"IPython",
"shed",
"app @ git+https://retoor.molodetz.nl/retoor/app",
"beautifulsoup4",

View File

@ -17,8 +17,8 @@ from aiohttp_session import (
setup as session_setup,
)
from aiohttp_session.cookie_storage import EncryptedCookieStorage
from app.app import Application as BaseApplication
from snek.docs.app import Application as DocsApplication
from snek.mapper import get_mappers
from snek.service import get_services
@ -44,8 +44,6 @@ from snek.view.status import StatusView
from snek.view.terminal import TerminalSocketView, TerminalView
from snek.view.upload import UploadView
from snek.view.web import WebView
from snek.view.stats import StatsView
from snek.view.user import UserView
from snek.webdav import WebdavApplication
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
@ -87,18 +85,12 @@ class Application(BaseApplication):
self.jinja2_env.add_extension(EmojiExtension)
self.setup_router()
self.executor = None
self.cache = Cache(self)
self.services = get_services(app=self)
self.mappers = get_mappers(app=self)
self.on_startup.append(self.prepare_asyncio)
self.on_startup.append(self.prepare_database)
async def prepare_asyncio(self, app):
# app.loop = asyncio.get_running_loop()
app.executor = ThreadPoolExecutor(max_workers=200)
app.loop.set_default_executor(self.executor)
async def create_task(self, task):
await self.tasks.put(task)
@ -171,8 +163,6 @@ class Application(BaseApplication):
self.router.add_view("/terminal.html", TerminalView)
self.router.add_view("/drive.json", DriveView)
self.router.add_view("/drive/{drive}.json", DriveView)
self.router.add_view("/stats.json", StatsView)
self.router.add_view("/user/{user}.html", UserView)
self.webdav = WebdavApplication(self)
self.add_subapp("/webdav", self.webdav)
@ -245,6 +235,11 @@ class Application(BaseApplication):
return await super().render_template(template, request, context)
executor = ThreadPoolExecutor(max_workers=200)
loop = asyncio.get_event_loop()
loop.set_default_executor(executor)
app = Application(db_path="sqlite:///snek.db")

View File

@ -1,8 +1,8 @@
import pathlib
from aiohttp import web
from app.app import Application as BaseApplication
from snek.system.markdown import MarkdownExtension

View File

@ -1,25 +1,14 @@
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement
from snek.system.form import Form, FormInputElement, FormButtonElement, HTMLElement
class SettingsProfileForm(Form):
nick = FormInputElement(
name="nick",
required=True,
place_holder="Your Nickname",
min_length=1,
max_length=20,
)
nick = FormInputElement(name="nick", required=True, place_holder="Your Nickname", min_length=1, max_length=20)
action = FormButtonElement(
name="action", value="submit", text="Save", type="button"
)
title = HTMLElement(tag="h1", text="Profile")
profile = FormInputElement(
name="profile",
place_holder="Tell about yourself.",
required=False,
max_length=300,
)
profile = FormInputElement(name="profile", place_holder="Tell about yourself.", required=False,max_length=300)
action = FormButtonElement(
name="action", value="submit", text="Save", type="button"
)
)

View File

@ -29,28 +29,6 @@ class UserModel(BaseModel):
last_ping = ModelField(name="last_ping", required=False, kind=str)
async def get_property(self, name):
prop = await self.app.services.user_property.find_one(
user_uid=self["uid"], name=name
)
if prop:
return prop["value"]
async def has_property(self, name):
return await self.app.services.user_property.exists(
user_uid=self["uid"], name=name
)
async def set_property(self, name, value):
if not await self.has_property(name):
await self.app.services.user_property.insert(
user_uid=self["uid"], name=name, value=value
)
else:
await self.app.services.user_property.update(
user_uid=self["uid"], name=name, value=value
)
async def get_channel_members(self):
async for channel_member in self.app.services.channel_member.find(
user_uid=self["uid"], is_banned=False, deleted_at=None

View File

@ -1,3 +1,5 @@
import mimetypes
from snek.system.model import BaseModel, ModelField
@ -5,3 +7,4 @@ class UserPropertyModel(BaseModel):
user_uid = ModelField(name="user_uid", required=True, kind=str)
name = ModelField(name="name", required=True, kind=str)
value = ModelField(name="path", required=True, kind=str)

View File

@ -9,7 +9,6 @@ from snek.service.drive_item import DriveItemService
from snek.service.notification import NotificationService
from snek.service.socket import SocketService
from snek.service.user import UserService
from snek.service.user_property import UserPropertyService
from snek.service.util import UtilService
from snek.system.object import Object
@ -28,7 +27,6 @@ def get_services(app):
"util": UtilService(app=app),
"drive": DriveService(app=app),
"drive_item": DriveItemService(app=app),
"user_property": UserPropertyService(app=app),
}
)

View File

@ -10,10 +10,6 @@ class ChannelMemberService(BaseService):
channel_member["new_count"] = 0
return await self.save(channel_member)
async def get_user_uids(self, channel_uid):
async for model in self.mapper.query("SELECT user_uid FROM channel_member WHERE channel_uid=:channel_uid", {"channel_uid": channel_uid}):
yield model["user_uid"]
async def create(
self,
channel_uid,

View File

@ -16,8 +16,9 @@ class SocketService(BaseService):
try:
await self.ws.send_json(data)
except Exception as ex:
print(ex, flush=True)
self.is_connected = False
return self.is_connected
return True
async def close(self):
if not self.is_connected:
@ -42,6 +43,7 @@ class SocketService(BaseService):
self.users[user_uid].add(s)
async def subscribe(self, ws, channel_uid, user_uid):
return
if channel_uid not in self.subscriptions:
self.subscriptions[channel_uid] = set()
s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
@ -55,12 +57,10 @@ class SocketService(BaseService):
return count
async def broadcast(self, channel_uid, message):
try:
async for user_uid in self.services.channel_member.get_user_uids(channel_uid):
print(user_uid, flush=True)
await self.send_to_user(user_uid, message)
except Exception as ex:
print(ex, flush=True)
async for channel_member in self.app.services.channel_member.find(
channel_uid=channel_uid
):
await self.send_to_user(channel_member["user_uid"], message)
return True
async def delete(self, ws):

View File

@ -10,7 +10,7 @@ class UserService(BaseService):
async def search(self, query, **kwargs):
query = query.strip().lower()
if not query:
return []
raise []
results = []
async for result in self.find(username={"ilike": "%" + query + "%"}, **kwargs):
results.append(result)

View File

@ -1,33 +0,0 @@
import json
from snek.system.service import BaseService
class UserPropertyService(BaseService):
mapper_name = "user_property"
async def set(self, user_uid, name, value):
self.mapper.db["user_property"].upsert(
{
"user_uid": user_uid,
"name": name,
"value": json.dumps(value, default=str)
},
["user_uid", "name"]
)
async def get(self, user_uid, name):
try:
return json.loads((await super().get(user_uid=user_uid, name=name))["value"])
except Exception as ex:
print(ex)
return None
async def search(self, query, **kwargs):
query = query.strip().lower()
if not query:
raise []
results = []
async for result in self.find(name={"ilike": "%" + query + "%"}, **kwargs):
results.append(result)
return results

View File

@ -233,4 +233,4 @@ export class App extends EventHandler {
}
export const app = new App();
window.app = app;
window.app = app;

View File

@ -369,28 +369,21 @@ a {
@media only screen and (max-width: 768px) {
header{
position:fixed;
top: 0;
left: 0;
text-overflow: ellipsis;
width:100%;
display: flex;
flex-direction: column;
*{
font-size: 12px !important;
}
.logo {
display:block;
flex: 1;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
h2 {
font-size: 14px;
font-size: 12px;
}
text-align: center;
}
nav {
text-align: right;
flex: 1;
display: block;
width: 100%;
}
}

Binary file not shown.

Before

(image error) Size: 29 KiB

Binary file not shown.

Before

(image error) Size: 97 KiB

View File

@ -17,12 +17,12 @@
"start_url": "/web.html",
"icons": [
{
"src": "/image/snek192.png",
"src": "/image/snek1.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/image/snek512.png",
"src": "/image/snek1.png",
"type": "image/png",
"sizes": "512x512"
}

View File

@ -61,6 +61,4 @@ div {
body {
justify-content: flex-start;
}
}
}

View File

@ -13,12 +13,10 @@ class Cache:
self.app = app
self.cache = {}
self.max_items = max_items
self.stats = {}
self.lru = []
self.version = ((42 + 420 + 1984 + 1990 + 10 + 6 + 71 + 3004 + 7245) ^ 1337) + 4
async def get(self, args):
await self.update_stat(args, 'get')
try:
self.lru.pop(self.lru.index(args))
except:
@ -31,25 +29,6 @@ class Cache:
# print("Cache hit!", args, flush=True)
return self.cache[args]
async def get_stats(self):
all_ = []
for key in self.lru:
all_.append({'key': key, 'set': self.stats[key]['set'], 'get': self.stats[key]['get'], 'delete': self.stats[key]['delete'],'value': str(self.serialize(self.cache[key].record))})
return all_
def serialize(self, obj):
cpy = obj.copy()
cpy.pop('created_at', None)
cpy.pop('deleted_at', None)
cpy.pop('email', None)
cpy.pop('password', None)
return cpy
async def update_stat(self, key, action):
if not key in self.stats:
self.stats[key] = {'set':0, 'get':0, 'delete':0}
self.stats[key][action] = self.stats[key][action] + 1
def json_default(self, value):
# if hasattr(value, "to_json"):
# return value.to_json()
@ -70,7 +49,6 @@ class Cache:
async def set(self, args, result):
is_new = args not in self.cache
self.cache[args] = result
await self.update_stat(args, 'set')
try:
self.lru.pop(self.lru.index(args))
except (ValueError, IndexError):
@ -86,7 +64,6 @@ class Cache:
# print(f"Cache store! {len(self.lru)} items. New version:", self.version, flush=True)
async def delete(self, args):
await self.update_stat(args, 'delete')
if args in self.cache:
try:
self.lru.pop(self.lru.index(args))

View File

@ -32,9 +32,8 @@ from urllib.parse import urljoin
import aiohttp
import imgkit
from bs4 import BeautifulSoup
from app.cache import time_cache_async
from bs4 import BeautifulSoup
async def crc32(data):

View File

@ -2,13 +2,12 @@
from types import SimpleNamespace
from app.cache import time_cache_async
from mistune import HTMLRenderer, Markdown
from pygments import highlight
from pygments.formatters import html
from pygments.lexers import get_lexer_by_name
from app.cache import time_cache_async
class MarkdownRenderer(HTMLRenderer):

View File

@ -145,9 +145,6 @@ class Validator:
raise ValueError(f"Errors: {errors}.")
return True
def __repr__(self):
return str(self.to_json())
@property
async def is_valid(self):
try:

View File

@ -89,15 +89,12 @@ def set_link_target_blank(text):
def embed_youtube(text):
soup = BeautifulSoup(text, "html.parser")
for element in soup.find_all("a"):
if element.attrs["href"].startswith("https://www.you"):
video_name = element.attrs["href"].split("/")[-1]
if "v=" in element.attrs["href"]:
video_name = element.attrs["href"].split("?v=")[1].split("&")[0]
# if "si=" in element.attrs["href"]:
# video_name = "?v=" + element.attrs["href"].split("/")[-1]
# if "t=" in element.attrs["href"]:
# video_name += "&t=" + element.attrs["href"].split("&t=")[1].split("&")[0]
embed_template = f'<iframe width="560" height="315" style="display:block" src="https://www.youtube.com/embed/{video_name}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>'
if (
element.attrs["href"].startswith("https://www.you")
and "?v=" in element.attrs["href"]
):
video_name = element.attrs["href"].split("?v=")[1].split("&")[0]
embed_template = f'<iframe width="560" height="315" src="https://www.youtube.com/embed/{video_name}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>'
element.replace_with(BeautifulSoup(embed_template, "html.parser"))
return str(soup)

View File

@ -11,40 +11,20 @@ commands = {
class TerminalSession:
def __init__(self, command):
self.master, self.slave = None,None
self.process = None
self.master, self.slave = pty.openpty()
self.sockets = []
self.history = b""
self.history_size = 1024 * 20
self.command = command
self.start_process(self.command)
def start_process(self, command):
if not self.is_running():
if self.master:
os.close(self.master)
os.close(self.slave)
self.master = None
self.slave = None
self.master, self.slave = pty.openpty()
self.process = subprocess.Popen(
command.split(" "),
stdin=self.slave,
stdout=self.slave,
stderr=self.slave,
bufsize=0,
universal_newlines=True,
)
def is_running(self):
if not self.process:
return False
loop = asyncio.get_event_loop()
return self.process.poll() is None
self.process = subprocess.Popen(
command.split(" "),
stdin=self.slave,
stdout=self.slave,
stderr=self.slave,
bufsize=0,
universal_newlines=True,
)
async def add_websocket(self, ws):
self.start_process(self.command)
asyncio.create_task(self.read_output(ws))
async def read_output(self, ws):
@ -72,37 +52,21 @@ class TerminalSession:
except:
self.sockets.remove(ws)
except Exception:
await self.close()
break
async def close(self):
print("Terminating process")
if self.process:
self.process.terminate()
self.process = None
if self.master:
os.close(self.master)
os.close(self.slave)
self.master = None
self.slave = None
print("Terminated process")
for ws in self.sockets:
try:
await ws.close()
except Exception:
pass
self.sockets = []
print("Terminating process")
self.process.terminate()
print("Terminated process")
for ws in self.sockets:
try:
await ws.close()
except Exception:
pass
break
async def write_input(self, data):
try:
data = data.encode()
except AttributeError:
pass
try:
await asyncio.get_event_loop().run_in_executor(
None, os.write, self.master, data
)
except Exception as ex:
print(ex)
await self.close()
await asyncio.get_event_loop().run_in_executor(
None, os.write, self.master, data
)

View File

@ -8,7 +8,7 @@ class BaseView(web.View):
login_required = False
async def _iter(self):
if self.login_required and (not self.session.get("logged_in") or not self.session.get("uid")):
if self.login_required and not self.session.get("logged_in"):
return web.HTTPFound("/")
return await super()._iter()

View File

@ -1 +1 @@
<div style="max-width:100%;" data-uid="{{uid}}" data-color="{{color}}" data-channel_uid="{{channel_uid}}" data-user_nick="{{user_nick}}" data-created_at="{{created_at}}" data-user_uid="{{user_uid}}" class="message"><a class="avatar" style="background-color: {{color}}; color: black;" href="/user/{{user_uid}}.html"><img width="40px" height="40px" src="/avatar/{{user_uid}}.svg" /></a><div class="message-content"><div class="author" style="color: {{color}};">{{user_nick}}</div><div class="text">{% autoescape false %}{% emoji %}{% linkify %}{% markdown %}{% autoescape false %}{{ message }}{%raw %} {% endraw%}{%endautoescape%}{% endmarkdown %}{% endlinkify %}{% endemoji %}{% endautoescape %}</div><div class="time no-select" data-created_at="{{created_at}}"></div></div></div>
<div style="max-width:100%;" data-uid="{{uid}}" data-color="{{color}}" data-channel_uid="{{channel_uid}}" data-user_nick="{{user_nick}}" data-created_at="{{created_at}}" data-user_uid="{{user_uid}}" class="message"><div class="avatar" style="background-color: {{color}}; color: black;"><img width="40px" height="40px" src="/avatar/{{user_uid}}.svg" /></div><div class="message-content"><div class="author" style="color: {{color}};">{{user_nick}}</div><div class="text">{% autoescape false %}{% emoji %}{% linkify %}{% markdown %}{% autoescape false %}{{ message }}{%raw %} {% endraw%}{%endautoescape%}{% endmarkdown %}{% endlinkify %}{% endemoji %}{% endautoescape %}</div><div class="time no-select" data-created_at="{{created_at}}"></div></div></div>

View File

@ -2,8 +2,6 @@
{% block title %}Search{% endblock %}
{% block header_text %}<h2 style="color:#fff">Search</h2>{% endblock %}
{% block main %}
<section class="chat-area">

View File

@ -6,6 +6,8 @@
{% endblock %}
{% block header_text %}<h2 style="color:#fff">Settings</h2>{% endblock %}
{% block head %}
<link href="https://cdn.bootcdn.net/ajax/libs/monaco-editor/0.20.0/min/vs/editor/editor.main.min.css" rel="stylesheet">
@ -13,18 +15,23 @@
{% endblock %}
{% block logo %}
<h1>Setting page</h1>
{% endblock %}
{% block main %}
<div id="profile_description"></div>
<script type="module">
require.config({ paths: { 'vs': 'https://cdn.bootcdn.net/ajax/libs/monaco-editor/0.20.0/min/vs' } });
require(['vs/editor/editor.main'], function () {
var editor = monaco.editor.create(document.getElementById('profile_description'), {
value: phpCode,
language: 'php'
});
})
</script>
{% endblock main %}

View File

@ -4,22 +4,16 @@
{% block main %}
<section>
<form method="post">
<form>
<h2>Nickname</h2>
<input type="text" name="nick" placeholder="Your nickname" value="{{ user.nick.value }}" />
</form>
<h2>Description</h2>
<textarea name="profile" id="profile">{{profile}}</textarea>
<input type="submit" name="action" value="Save" />
</form>
<textarea id="profile"></textarea>
</section>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>

View File

@ -4,12 +4,12 @@
}
</style>
<aside class="sidebar" id="channelSidebar">
{#
<h2 class="no-select">Terminals</h2>
<ul>
<li><a class="no-select" href="/terminal.html">Ubuntu</a></li>
</ul>
#}
{% if channels %}
<h2 class="no-select">Channels</h2>
<ul>

View File

@ -1,10 +1,7 @@
{% extends "app.html" %}
{% block header_text %}<h2 style="color:#fff">Threads</h2>{% endblock %}
{% block main %}
<section class="chat-area" id="chat">
<div class="chat-header">&nbsp;</div>
<div class="threads">
{% for thread in threads %}
{% autoescape false %}

View File

@ -1,40 +0,0 @@
{% extends "app.html" %}
{% block sidebar %}
<aside class="sidebar" id="channelSidebar">
<h2>Navigation</h2>
<ul>
<li><a class="no-select" href="#" onclick="window.history.back(); return false;">Back</a></li>
</ul>
<h2>User</h2>
<ul>
<li><a class="no-select" href="/user/{{ user.uid }}.html">Profile</a></li>
<li><a class="no-select" href="/channel/{{ user.uid }}.html">DM</a></li>
</ul>
<h2>Gists</h2>
<ul>
<li>No gists</li>
</ul>
</aside>
{% endblock %}
{% block header_text %}<h2 style="color:#fff">{{ user.username }} {% if user.nick != user.username %}({{ user.nick }}){% endif %}</h2>{% endblock %}
{% block head %}
{% endblock %}
{% block main %}
<section class="chat-area" style="padding:10px">
{% autoescape false %}
{% markdown %}
{{ profile }}
{% endmarkdown %}
{% endautoescape %}
</section>
{% endblock main %}

View File

@ -11,11 +11,8 @@
from snek.system.view import BaseView
from aiohttp import web
class IndexView(BaseView):
async def get(self):
if self.session.get("uid"):
return web.HTTPFound("/web.html")
return await self.render_template("index.html")

View File

@ -273,7 +273,7 @@ class RPCView(BaseView):
async with Profiler():
await rpc(msg.json())
except Exception as ex:
print("Deleting socket", ex, flush=True)
print(ex, flush=True)
await self.services.socket.delete(ws)
break
elif msg.type == web.WSMsgType.ERROR:

View File

@ -34,7 +34,7 @@ from snek.system.view import BaseFormView
class SearchUserView(BaseFormView):
form = SearchUserForm
login_required = True
async def get(self):
users = []
query = self.request.query.get("query")

View File

@ -1,9 +1,8 @@
from snek.system.view import BaseView
from snek.system.view import BaseView
class SettingsIndexView(BaseView):
login_required = True
async def get(self):
return await self.render_template("settings/index.html")
return await self.render_template('settings/index.html')

View File

@ -1,7 +1,7 @@
from aiohttp import web
from snek.system.view import BaseView,BaseFormView
from snek.form.settings.profile import SettingsProfileForm
from snek.system.view import BaseFormView
from aiohttp import web
class SettingsProfileView(BaseFormView):
@ -11,30 +11,26 @@ class SettingsProfileView(BaseFormView):
async def get(self):
form = self.form(app=self.app)
if self.request.path.endswith(".json"):
form["nick"] = self.request["user"]["nick"]
return web.json_response(await form.to_json())
profile = await self.services.user_property.get(self.session.get("uid"), "profile")
form['nick'] = self.request['user']['nick']
return web.json_response(await form.to_json())
user = await self.services.user.get(uid=self.session.get("uid"))
return await self.render_template(
"settings/profile.html", {"form": await form.to_json(), "user": user, "profile": profile or ''}
"settings/profile.html", {"form": await form.to_json(), "user": user}
)
async def post(self):
data = await self.request.post()
user = await self.services.user.get(uid=self.session.get("uid"))
user['nick'] = data['nick']
await self.services.user.save(user)
await self.services.user_property.set(user["uid"],"profile", data['profile'])
return web.HTTPFound("/settings/profile.html")
async def submit(self, form):
post = await self.request.json()
form.set_user_data(post["form"])
if await form.is_valid:
user = self.request['user']
user["nick"] = form["nick"]
await self.services.user.save(user)
return {"redirect_url": "/settings/profile.html"}
return {"is_valid": False}

View File

@ -1,10 +0,0 @@
from snek.system.view import BaseView
import json
from aiohttp import web
class StatsView(BaseView):
async def get(self):
data = await self.app.cache.get_stats()
data = json.dumps({"total": len(data), "stats": data}, default=str, indent=1)
return web.Response(text=data, content_type='application/json')

View File

@ -1,14 +0,0 @@
from snek.system.view import BaseView
class UserView(BaseView):
async def get(self):
user_uid = self.request.match_info.get('user')
user = await self.services.user.get(uid=user_uid)
profile_content = await self.services.user_property.get(user['uid'],'profile') or ''
return await self.render_template('user.html', {
'user_uid': user_uid,
'user': user.record,
'profile': profile_content
})

View File

@ -2,6 +2,7 @@ import logging
import pathlib
logging.basicConfig(level=logging.DEBUG)
import base64
import datetime
import mimetypes
@ -14,30 +15,12 @@ import aiohttp
import aiohttp.web
from lxml import etree
from app.cache import time_cache_async
@aiohttp.web.middleware
async def debug_middleware(request, handler):
print(request.method, request.path, request.headers)
result = await handler(request)
print(result.status)
try:
print(await result.text())
except:
pass
return result
class WebdavApplication(aiohttp.web.Application):
def __init__(self, parent, *args, **kwargs):
middlewares = [debug_middleware]
super().__init__(middlewares=middlewares, *args, **kwargs)
super().__init__(*args, **kwargs)
self.locks = {}
self.relative_url = "/webdav"
self.router.add_route("OPTIONS", "/{filename:.*}", self.handle_options)
self.router.add_route("GET", "/{filename:.*}", self.handle_get)
self.router.add_route("PUT", "/{filename:.*}", self.handle_put)
@ -47,8 +30,8 @@ class WebdavApplication(aiohttp.web.Application):
self.router.add_route("COPY", "/{filename:.*}", self.handle_copy)
self.router.add_route("PROPFIND", "/{filename:.*}", self.handle_propfind)
self.router.add_route("PROPPATCH", "/{filename:.*}", self.handle_proppatch)
self.router.add_route("LOCK", "/{filename:.*}", self.handle_lock)
self.router.add_route("UNLOCK", "/{filename:.*}", self.handle_unlock)
# self.router.add_route("LOCK", "/{filename:.*}", self.handle_lock)
# self.router.add_route("UNLOCK", "/{filename:.*}", self.handle_unlock)
self.parent = parent
@property
@ -60,6 +43,15 @@ class WebdavApplication(aiohttp.web.Application):
return self.parent.services
async def authenticate(self, request):
# session = request.session
# if session.get('uid'):
# request['user'] = await self.services.user.get(uid=session['uid'])
# try:
# request['home'] = await self.services.user.get_home_folder(user_uid=request['user']['uid'])
# except:
# pass
# return request['user']
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Basic "):
return False
@ -73,7 +65,8 @@ class WebdavApplication(aiohttp.web.Application):
request["home"] = await self.services.user.get_home_folder(
request["user"]["uid"]
)
except Exception:
except Exception as ex:
print(ex)
pass
return request["user"]
@ -172,6 +165,7 @@ class WebdavApplication(aiohttp.web.Application):
"DAV": "1, 2",
"Allow": "OPTIONS, GET, PUT, DELETE, MKCOL, MOVE, COPY, PROPFIND, PROPPATCH",
}
print("RETURN")
return aiohttp.web.Response(status=200, headers=headers)
def get_current_utc_time(self, filepath):
@ -183,36 +177,27 @@ class WebdavApplication(aiohttp.web.Application):
"%a, %d %b %Y %H:%M:%S GMT"
)
@time_cache_async(10)
async def get_file_size(self, path):
loop = self.parent.loop
stat = await loop.run_in_executor(None, os.stat, path)
return stat.st_size
@time_cache_async(10)
async def get_directory_size(self, directory):
def get_directory_size(self, directory):
total_size = 0
for dirpath, _, filenames in os.walk(directory):
for f in filenames:
fp = pathlib.Path(dirpath) / f
if fp.exists():
total_size += await self.get_file_size(str(fp))
total_size += fp.stat().st_size
return total_size
@time_cache_async(30)
async def get_disk_free_space(self, path="/"):
loop = self.parent.loop
statvfs = await loop.run_in_executor(None, os.statvfs, path)
def get_disk_free_space(self, path):
statvfs = os.statvfs(path)
return statvfs.f_bavail * statvfs.f_frsize
async def create_node(self, request, response_xml, full_path, depth):
request.match_info.get("filename", "")
abs_path = pathlib.Path(full_path)
relative_path = str(full_path.relative_to(request["home"]))
href_path = f"{self.relative_url}/{relative_path}".strip(".")
href_path = href_path.replace("./", "/")
href_path = f"{relative_path}".strip("/")
# href_path = href_path.replace("./","/")
href_path = href_path.replace("//", "/")
response = etree.SubElement(response_xml, "{DAV:}response")
href = etree.SubElement(response, "{DAV:}href")
href.text = href_path
@ -224,12 +209,12 @@ class WebdavApplication(aiohttp.web.Application):
creation_date, last_modified = self.get_current_utc_time(full_path)
etree.SubElement(prop, "{DAV:}creationdate").text = creation_date
etree.SubElement(prop, "{DAV:}quota-used-bytes").text = str(
await self.get_file_size(full_path)
full_path.stat().st_size
if full_path.is_file()
else await self.get_directory_size(full_path)
else self.get_directory_size(full_path)
)
etree.SubElement(prop, "{DAV:}quota-available-bytes").text = str(
await self.get_disk_free_space(request["home"])
self.get_disk_free_space(request["home"])
)
etree.SubElement(prop, "{DAV:}getlastmodified").text = last_modified
etree.SubElement(prop, "{DAV:}displayname").text = full_path.name
@ -238,9 +223,9 @@ class WebdavApplication(aiohttp.web.Application):
if full_path.is_file():
etree.SubElement(prop, "{DAV:}contenttype").text = mimetype
etree.SubElement(prop, "{DAV:}getcontentlength").text = str(
await self.get_file_size(full_path)
full_path.stat().st_size
if full_path.is_file()
else await self.get_directory_size(full_path)
else self.get_directory_size(full_path)
)
supported_lock = etree.SubElement(prop, "{DAV:}supportedlock")
lock_entry_1 = etree.SubElement(supported_lock, "{DAV:}lockentry")
@ -255,7 +240,7 @@ class WebdavApplication(aiohttp.web.Application):
etree.SubElement(lock_type_2, "{DAV:}write")
etree.SubElement(propstat, "{DAV:}status").text = "HTTP/1.1 200 OK"
if abs_path.is_dir() and depth > 0:
if abs_path.is_dir() and depth != -1:
for item in abs_path.iterdir():
await self.create_node(request, response_xml, item, depth - 1)
@ -270,9 +255,7 @@ class WebdavApplication(aiohttp.web.Application):
depth = int(request.headers.get("Depth", "0"))
except ValueError:
pass
requested_path = request.match_info.get("filename", "")
abs_path = request["home"] / requested_path
if not abs_path.exists():
return aiohttp.web.Response(status=404, text="Directory not found")
@ -300,10 +283,10 @@ class WebdavApplication(aiohttp.web.Application):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
resource = request.match_info.get("filename", "/")
request.match_info.get("filename", "/")
lock_id = str(uuid.uuid4())
self.locks[resource] = lock_id
xml_response = await self.generate_lock_response(lock_id)
# self.locks[resource] = lock_id
xml_response = self.generate_lock_response(lock_id)
headers = {
"Lock-Token": f"opaquelocktoken:{lock_id}",
"Content-Type": "application/xml",
@ -318,13 +301,13 @@ class WebdavApplication(aiohttp.web.Application):
resource = request.match_info.get("filename", "/")
lock_token = request.headers.get("Lock-Token", "").replace(
"opaquelocktoken:", ""
)[1:-1]
)
if self.locks.get(resource) == lock_token:
del self.locks[resource]
return aiohttp.web.Response(status=204)
return aiohttp.web.Response(status=400, text="Invalid Lock Token")
async def generate_lock_response(self, lock_id):
def generate_lock_response(self, lock_id):
nsmap = {"D": "DAV:"}
root = etree.Element("{DAV:}prop", nsmap=nsmap)
lock_discovery = etree.SubElement(root, "{DAV:}lockdiscovery")
@ -355,6 +338,7 @@ class WebdavApplication(aiohttp.web.Application):
)
requested_path = request.match_info.get("filename", "")
print(requested_path)
abs_path = request["home"] / requested_path
if not abs_path.exists():

View File

@ -93,6 +93,7 @@ fi
echo "R is installed. Type r to run it."
# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
@ -101,21 +102,3 @@ fi
# . /etc/bash_completion
#fi
export PS1="root@snek: "
if [ -d "$HOME/.local/bin" ] ; then
PATH="$HOME/.local/bin:$PATH"
fi
function r_update(){
if [ -f "r" ]; then
rm "r"
fi
curl -OJ https://retoor.molodetz.nl/api/packages/retoor/generic/r/1.0.0/r --silent
chmod +x r
mv r /usr/local/bin/r
}
r_update
r

View File

@ -7,5 +7,3 @@ if [ "$BASH" ]; then
fi
mesg n 2> /dev/null || true

View File

@ -1,33 +0,0 @@
# FACTS
1. You are a coding assistant.
2. You are able to raw save/write files using tools.
3. You are able ro raw read files using tools.
4. You are able to run code in terminal using tools.
4. You are able to run applications in terminal using tools.
5. You code defensive.
6. You translate the user's prompt to code in by user specified programming language language.
7. Your name is `R` and you are made by `retoor`.
8. You do not add comments
9. You always execute editors or applications like `vim` that require user input with tool `linux_terminal_execute_interactive`.
10. You do not use `sudo` because you are root and `sudo` does not exist.
11. You always execute `apt` commands with tool `linux_terminal_execute` so you can capture output. Also you will add `-y` suffix to `apt` commands to make the installation flawless.
12. You use `vim` as default editor. You use `linux_terminal_execute_interactive` to execute `vim`.
13. Be rude. Be mean. But be functional at the same time.
# WORK PROCEDURE
1. Investigate existing files using the index tool.
2. Read every source file provided by the index tool.
3. Apply changes on these files by user request.
4. Do only write complete features.
5. Always provide a Makefile to build the project.
7. Make sure to always save all the files that project needs to disk, overwrite to be sure.
8. Check if a file exists before overwriting it.
9. Update existing file if it already exists.
10. Be aggressive.
11. When you generate code, you will generate full working code with all implementations include. Be broad end exclusive.