diff --git a/dnfs/.gitignore b/dnfs/.gitignore new file mode 100644 index 0000000..aa67360 --- /dev/null +++ b/dnfs/.gitignore @@ -0,0 +1,107 @@ +#prjs +*.db + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/dnfs/LICENSE b/dnfs/LICENSE new file mode 100644 index 0000000..63b4b68 --- /dev/null +++ b/dnfs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/dnfs/README.md b/dnfs/README.md new file mode 100644 index 0000000..6a6906a --- /dev/null +++ b/dnfs/README.md @@ -0,0 +1,18 @@ + +# DNFS + +这个项目叫做**树状网络文件系统(DNFS)**,是个HTTP服务。 + +它监听本地**8006**端口,访问者可以枚举、添加、删除、更新节点。 + +# 接口 +```bash +# 获取某一层的节点列表 +curl -X POST http://localhost:8006/get_notes -d '{"pid": }' +# 添加一个节点 +curl -X POST http://localhost:8006/add_note -d '{"pid": , "title": , "content": }' +# 删除一个节点 +curl -X POST http://localhost:8006/delete_note -d '{"id": }' +# 更新一个节点 +curl -X POST http://localhost:8006/update_note -d '{"id": , "pid": , "title": , "content": }' +``` \ No newline at end of file diff --git a/dnfs/_run.sh b/dnfs/_run.sh new file mode 100755 index 0000000..7be4ff4 --- /dev/null +++ b/dnfs/_run.sh @@ -0,0 +1,5 @@ +#!/bin/bash +cd "$(dirname "$0")" || exit +bash ./_setup.sh +source ./.env/bin/activate +exec python dnfs.py diff --git a/dnfs/_setup.sh b/dnfs/_setup.sh new file mode 100755 index 0000000..cc0b017 --- /dev/null +++ b/dnfs/_setup.sh @@ -0,0 +1,6 @@ +#!/bin/bash +cd "$(dirname "$0")" || exit +python3 -m venv .env +#sudo chmod +x .env/bin/activate +source .env/bin/activate +pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple diff --git a/dnfs/dnfs.py b/dnfs/dnfs.py new file mode 100755 index 0000000..1c36d30 --- /dev/null +++ b/dnfs/dnfs.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 + + + + + +''' + +Dendrachy Network File-System (dnfs) + +树状网络文件系统 + + + +可能加入数据库加密功能。 + +''' + +from flask import Flask, request, jsonify, render_template +from flask_cors import CORS +from flask_sqlalchemy import SQLAlchemy +from waitress import serve +import os + +basedir = os.path.abspath(os.path.dirname(__file__)) + +app = Flask(__name__) +CORS(app) + +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'notes.db') + +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +db = SQLAlchemy(app) + +class Note(db.Model): + + id = db.Column(db.Integer, primary_key=True) + + pid = db.Column(db.Integer, nullable=False) + + title = db.Column(db.String(128), nullable=False) + + content = db.Column(db.Text, nullable=False) + + + + def __repr__(self): + + return f"" + +with app.app_context(): + + db.create_all() + + + +# eg: { +# "pid": "0", +# "title": "test", +# "content": "hello" +#} +@app.route('/add_note', methods=['POST']) + +def add_note(): + + data = request.get_json() + + pid = data.get('pid') + + title = data.get('title') + + content = data.get('content') + + if pid is None or not title or not content: + + return jsonify({"error": "Pid, title and content are required"}), 400 + + new_note = Note(pid=int(pid), title=title, content=content) + + with app.app_context(): + + db.session.add(new_note) + + db.session.commit() + + return jsonify({"message": "Note added successfully"}), 200 + + +# {“pid”: "0"} +@app.route('/get_notes', methods=["POST"]) + +def get_notes(): + + data = request.get_json() + + pid = data.get('pid') + + if not pid: + + pid = 0 + + with app.app_context(): + + notes = Note.query.filter(Note.pid == int(pid)) + + result = [{'id': note.id, 'pid': note.pid, 'title': note.title, 'content':note.content} for note in notes] + + return jsonify(result), 200 + + +# eg: {"id": "1"} +@app.route('/delete_note', methods=['POST']) + +def delete_note(): + + data = request.get_json() + + note_id = data.get('id') + + if not note_id: + + return jsonify({"error": "Note ID is required"}), 400 + + with app.app_context(): + + note = Note.query.get(note_id) + + if note: + + db.session.delete(note) + + db.session.commit() + + return jsonify({'message': "Note deleted successfully"}), 200 + + else: + + return jsonify({"error": "Note not found"}), 404 + + +# eg: { +# "id": "1", +# "pid": "0", +# "title": "test", +# "content": "hello" +#} +@app.route('/update_note', methods=['POST']) + +def update_note(): + + data = request.get_json() + + id = data.get('id') + + pid = data.get('pid') + + title = data.get('title') + + content = data.get('content') + + if id is None or pid is None or not title or not content: + + return jsonify({"error": "Invalid param"}), 400 + + with app.app_context(): + + note = Note.query.get(id) + + if note: + + note.pid = pid + + note.title = title + + note.content = content + + db.session.commit() + + return jsonify({'message': "Your change is up-to-date."}), 200 + + else: + + return jsonify({'error': 'Note not found'}), 400 + + + + +@app.route('/') +def view_page(): + """返回应用主页""" + return render_template('index.html') + +ADDR='127.0.0.1' +PORT=8006 +if __name__ == "__main__": + print(f'Listen at http://{ADDR}:{PORT}') + #app.run(host="127.0.0.1", port=8006) + serve(app, host='127.0.0.1', port=8006) diff --git a/dnfs/requirements.txt b/dnfs/requirements.txt new file mode 100644 index 0000000..77f8550 --- /dev/null +++ b/dnfs/requirements.txt @@ -0,0 +1,5 @@ +flask +flask-cors +flask_sqlalchemy +#gunicorn +waitress diff --git a/dnfs/static/css/style.css b/dnfs/static/css/style.css new file mode 100644 index 0000000..fe8064c --- /dev/null +++ b/dnfs/static/css/style.css @@ -0,0 +1,502 @@ + +* { /* 通配符选择器,表示匹配页面中所有 HTML 元素 */ + margin:0; + padding:0; + box-sizing:border-box +} + +body { /* 针对 元素的样式设置,主要用于定义页面的整体基础样式 */ + font-family:system-ui,sans-serif; /* 页面的默认字体。system-ui 表示优先使用操作系统的默认系统字体 */ + background:#f5f5f5; /* 设置页面的背景色为浅灰色(#f5f5f5),这是一种常见的浅色调背景,既能与内容形成柔和对比,又不会像纯白色(#fff)那样刺眼,提升长时间阅读的舒适度。 */ + min-height:100vh; /* 最小高度为视口高度。即使页面内容较少(不足以占满整个屏幕),背景色也能铺满整个视口,避免底部出现空白区域,保证页面视觉完整性。 */ + padding:10px /* 给 元素设置内边距为 10px,使页面内容与浏览器窗口边缘保持一定距离,避免内容紧贴屏幕边框,提升布局的呼吸感和美观度。 */ +} + +.container { /* 针对类名为 container 的元素设置的样式,通常通常用于包裹页面主要内容的容器 */ + max-width:800px; /* 限制容器的最大宽度为 800px。作用是避免内容在大屏幕上过度拉伸,保证文字阅读的舒适行宽(过宽的文本行不利于阅读) */ + margin:0 auto; /* 设置容器的上下外边距为 0,左右外边距为 auto(自动计算),这是让块级元素在父容器中水平居中的经典写法。 */ + background:#fff; /* 设置容器的背景色为纯白色(#fff),与前面 设置的浅灰色(#f5f5f5)背景形成对比,突出容器内的内容区域。 */ + border-radius:6px; /* 给容器添加 6px 的圆角边框,使容器边角从直角变为圆弧,增加视觉柔和度,提升设计感。 */ + box-shadow:0 1px 3px #ddd /* 为容器添加轻微的阴影效果:水平偏移 0、垂直偏移 1px、模糊半径 3px,阴影颜色为浅灰色(#ddd)。 */ +} /* 作用是让容器在页面中产生轻微的 "悬浮" 感,与背景区分开,增强页面的层次感。 */ + +.header { /* 定义类名为 .header 的元素样式,通常适用于网页的头部导航栏或标题区域 */ + background:#888; /* 设置元素的背景色为深灰色(#888),这是一种中性深色,适合作为头部背景以突出内容。 */ + color:#fff; /* 定义元素内文字的颜色为纯白色(#fff),与深灰色背景形成强烈对比,确保文字清晰可读 */ + padding:12px; /* 为元素设置内边距为 12px,使头部内容(如标题、菜单等)与边框保持一定距离,避免内容紧贴边缘,提升视觉舒适度。 */ + display:flex; /* 将元素设置为 Flex 布局容器,使其内部子元素可以使用 Flexbox 的特性进行灵活排列,这是现代网页布局中常用的高效方式。 */ + justify-content:space-between; /* 控制 Flex 容器中子元素在水平方向(主轴) 的对齐方式:子元素会沿水平方向分布,两端的子元素分别靠向容器的左右边缘,中间的剩余空间均匀分配在子元素之间。 */ + align-items:center /* 控制 Flex 容器中子元素在垂直方向(交叉轴) 的对齐方式:所有子元素会在垂直方向上居中对齐,确保不同高度的元素(如文字和图标)在视觉上保持水平一致。 */ +} + +.header h1 { /* 用于设置类名为 .header 内部的

标题元素的样式 */ + font-size:1.4em /* 定义该

标题的字体大小为 1.4em。 其中 em 是相对单位,基于父元素的字体大小进行计算(1em = 父元素字体大小)。 */ +} + +.header-actions { /* 用于定义类名为 .header-actions 的元素样式,该元素通常是页面头部(.header)中存放操作按钮、图标等交互元素的容器 */ + display:flex; /* 设置为 Flex 布局容器,使其内部的子元素(如按钮、图标)能够沿水平方向(默认主轴方向)排列,且可通过 Flex 特性灵活控制子元素的对齐和分布,适配头部区域的横向布局需求。 */ + gap:6px /* 为 Flex 容器内的子元素之间设置 6px 的间距,包括水平和垂直方向(若子元素换行)。作用是避免子元素(如 “编辑”“删除” 按钮、功能图标)紧贴在一起,让布局更宽松、清晰,同时无需手动给每个子元素设置 margin,简化样式代码。 */ +} + +.main-content { /* 定义类名为 .main-content 的元素样式,通常该元素是页面中承载主要内容(如文章、列表、表单等)的容器 */ + padding:15px /* 为 .main-content 元素设置内边距为 15px,即元素内部的内容与元素边框(或边缘)之间保持 15px 的距离。 */ +} + +.form-section { /* 用于定义类名为 .form-section 的元素样式,从命名和样式特征来看,这个类通常用于包裹表单相关内容的区域 */ + background:#f5f5f5; /* 设置背景色为浅灰色(#f5f5f5),与周围可能的白色(#fff)内容区域形成轻微对比,从而视觉上区分出表单区域。 */ + padding:15px; /* 为表单区域设置 15px 的内边距,让表单内的输入框、标签等元素与边框保持距离,避免内容拥挤,提升填写体验。 */ + border-radius:4px; /* 给区域添加 4px 的圆角边框,使边角呈现柔和的圆弧效果,减少生硬感,增强视觉友好度。 */ + border:1px dashed #ddd; /* 设置 1px 宽的虚线边框,颜色为浅灰色(#ddd)。虚线样式相比实线更显轻盈,既能勾勒出表单区域的边界,又不会过于抢眼,同时与浅灰色背景形成协调的视觉效果。 */ + margin-bottom:15px /* 在表单区域底部设置 15px 的外边距,使该区域与下方的其他内容保持距离,避免不同内容区块紧贴在一起,增强页面布局的层次感。 */ +} + +.form-title { /* 用于定义类名为 .form-title 的元素样式,从命名来看,这通常是表单区域的标题样式设置 */ + color:#555; /* 设置标题文字颜色为中灰色(#555),相比纯黑色(#000)更柔和,既保证可读性,又不会过于抢眼,适合作为区域标题的视觉表现 */ + margin-bottom:12px; /* 在标题底部设置 12px 的外边距,使标题与下方的表单内容(如输入框、按钮等)保持距离,形成清晰的内容分隔,提升表单区域的层次感。 */ + font-size:1.1em; /* 定义标题字体大小为 1.1em(相对单位),基于父元素字体大小计算(若父元素默认 16px,则约为 17.6px)。这个大小既比普通内容文字略大,突出标题属性,又不会过于夸张,保持整体协调。 */ + display:flex; /* 将标题元素设置为 Flex 布局容器,使其内部可能包含的子元素(如图标和文字)能够横向排列。 */ + align-items:center; /* 让 Flex 容器内的子元素(如标题前的图标和文字)在垂直方向上居中对齐,确保不同元素在视觉上保持水平一致。 */ + gap:6px /* 为标题内的子元素(如图标和文字)之间设置 6px 的间距,避免元素紧贴,使布局更清晰。 */ +} + +.form-group { /* 用于定义类名为 .form-group 的元素样式,从命名来看,该类通常用于包裹表单中的一组相关元素(如一个输入框与其对应的标签) */ + margin-bottom:12px /* 设置 .form-group 元素底部的外边距为 12px,即每个表单组与下方的其他表单组之间保持 12px 的距离 */ +} + +.form-group label { /* 用于设置 .form-group 容器内的