10 Commits

146 changed files with 2980 additions and 6156 deletions
+3
View File
@@ -0,0 +1,3 @@
venv/
*__pycache__/
.st*
-55
View File
@@ -1,55 +0,0 @@
cmake_minimum_required(VERSION 3.16)
project(Tanabata
VERSION 1.0.0
HOMEPAGE_URL https://github.com/H1K0/tanabata
LANGUAGES C
)
set(CMAKE_C_STANDARD 99)
set(CORE_SRC
include/core.h
tanabata/core/core_func.h
tanabata/core/sasahyou.c
tanabata/core/sappyou.c
tanabata/core/shoppyou.c
)
set(TANABATA_SRC
${CORE_SRC}
include/tanabata.h
tanabata/lib/database.c
tanabata/lib/sasa.c
tanabata/lib/tanzaku.c
tanabata/lib/kazari.c
)
set(TDBMS_SERVER_SRC
${TANABATA_SRC}
include/tdbms.h
tdbms/server/tdbms-server.c
)
set(TDBMS_CLIENT_SRC
include/tdbms.h
include/tdbms-client.h
tdbms/client/tdbms-client.c
)
set(CLI_SRC
${TANABATA_SRC}
tfm/cli/tfm-cli.c
)
# Tanabata shared lib
add_library(tanabata SHARED ${TANABATA_SRC})
# Tanabata DBMS server
add_executable(tdbms ${TDBMS_SERVER_SRC})
# Tanabata DMBS client lib
add_library(tdb SHARED ${TDBMS_CLIENT_SRC})
# Tanabata CLI app
add_executable(tfm ${CLI_SRC})
+6 -85
View File
@@ -1,101 +1,22 @@
<h1 align="center">Tanabata File Manager</h1>
<h1 align="center">🎋 Tanabata File Manager 🎋</h1>
---
[![Release version][release-shield]][release-link]
<!-- [![Release version][release-shield]][release-link] -->
## Contents
- [About](#about)
- [Glossary](#glossary)
- [Usage](#usage)
- [Command Line Interface](#command-line-interface)
## About
Tanabata (_jp._ 七夕) is a Japanese festival. People generally celebrate this day by writing wishes, sometimes in the form of poetry, on _tanzaku_ (_jp._ 短冊), small pieces of paper, and hanging them on _sasa_ (_jp._ 笹), bamboo. See [this Wikipedia page](https://en.wikipedia.org/wiki/Tanabata) for more information.
Tanabata (_jp._ 七夕) is Japanese festival. People generally celebrate this day (July 7th) by writing wishes, sometimes in the form of poetry, on _tanzaku_ (_jp._ 短冊), small pieces of paper, and hanging them on _sasa_ (_jp._ 笹), bamboo. See [this Wikipedia page](https://en.wikipedia.org/wiki/Tanabata) for more information.
Tanabata FM is a file manager for Linux that will let you enjoy the Tanabata festival. It organizes files as _sasa_ bamboos, on which you can hang almost any number of _tanzaku_, just like adding tags on it.
## Glossary
**TFM (Tanabata File Manager)** is this file manager.
**Sasa (_jp._ 笹)** is a file record. It contains 64-bit ID number, the creation timestamp, and the path to the file.
**Tanzaku (_jp._ 短冊)** is a tag record. It contains 64-bit ID number, creation and last modification timestamps, name and description.
**Kazari (_jp._ 飾り)** is a sasa-tanzaku association record. It contains the creation timestamp and associated sasa and tanzaku IDs.
**Hyou (_jp._ 表)** is a table or database.
**Sasahyou (_jp._ 笹表)** is a database of sasa.
**Sappyou (_jp._ 冊表)** is a database of tanzaku.
**Shoppyou (_jp._ 飾表)** is a database of kazari.
## Usage
First of all, compile the source code with `./build.sh`. By default, it builds all targets to the `./build/` directory, but you can specify your custom build directory and target with `./build.sh [-b <build_dir>] [-t <target>]`. Both of options are... yeah, optional. For example, if you want to build just the CLI app to the `./tfm-cli/` directory, run `./build.sh -t tfm -b ./tfm-cli/`.
### Command Line Interface
Build the CLI app using `./build.sh -t tfm [-b <build_dir>]`. For better experience, you can move the executable to the `/usr/bin/` directory (totally safe unless you have another app named `tfm`) or add the directory with it to `PATH`.
Then just open the terminal and run `tfm -h`. If you are running it for the first time, run it with `sudo` or manually create the `/etc/tfm/` directory and check its permissions. This is the directory where TFM stores its config file. If everything is set up properly, you should get the following help message.
```
(C) Masahiko AMANO aka H1K0, 2022—present
(https://github.com/H1K0/tanabata)
Usage:
tfm <options>
Options:
-h Print this help and exit
-I <dir> Initialize new Tanabata database in directory <dir>
-O <dir> Open existing Tanabata database from directory <dir>
-i View database info
-s Set or add
-u Unset or remove
-e Edit or update
-f <sasa_id or path> File-sasa menu
-t <tanzaku_id or name> Tanzaku menu
-c <sasa_id>-<tanzaku_id> Kazari menu (can only be used with the '-s' or '-u' option)
-w Weed (defragment) database
-V Print version and exit
No database connected
```
So, let's take a look at each option.
Using the `-I <dir>` option, you can initialize an empty TFM database in the specified directory. The app creates empty sasahyou, sappyou and shoppyou files and saves the directory path to the configuration file. The new database will be used the next time you run the app until you change it.
Using the `-O <dir>` option, you can open an existing TFM database in the specified directory. The app checks if the directory contains sasahyou, sappyou and shoppyou files, and if they exist and are valid, saves the directory path to the configuration file. The new database will be used the next time you run the app until you change it.
Using the `-i` option, you can get info about your database. When your hyous were created and last modified, how many records and holes they have, and so on.
Using the `-s` option, you can add new sasa, tanzaku, or kazari.
Using the `-u` option, you can remove sasa, tanzaku, or kazari.
Using the `-e` option, you can update sasa file path or tanzaku name or description. If you want to keep the current value of a field (for example, if you want to change the description of tanzaku while keeping its name), just leave its line blank.
Using the `-f` option, you can manage your sasa. It takes sasa ID when used alone or with the `-u` or `-e` option or target file path when used with the `-s` option. If you want to view the list of all sasa, pass `.` as an argument. For example, `tfm -f 2d` prints the info about sasa with ID `2d` and `tfm -sf path/to/file` adds a new file to the database.
Using the `-t` option, you can manage your tanzaku. It takes tanzaku ID when used alone or with the `-u` or `-e` option or the name of new tanzaku when used with the `-s` option. If you want to view the list of all tanzaku, pass `.` as an argument. For example, `tfm -t c4` prints the info about sasa with ID `c4` and `tfm -st "New tag name"` adds a new tanzaku to the database.
The `-c` option can be used only with the `-s` or `-u` option. It takes the IDs of sasa and tanzaku to link/unlink separated with a hyphen. For example, `tfm -sc 10-4d` links sasa with ID `10` and tanzaku with ID `4d`.
Using the `-w` option, you can _weed_ the database. It's like defragmentation. For example, if you had 4 files with sasa IDs 0, 1, 2, 3 in your database and removed the 1st one, then your database would only have sasa IDs 0, 2, 3 and ID 1 would be a _hole_. Weeding fixes this hole by changing sasa ID 2 to 1, 3 to 2, and updating all related kazari, so for large databases this can take a while.
Using the `-V` option, you just get the current version of TFM.
Tanabata File Manager is a software project that will let you enjoy the Tanabata festival. It allows you to store and organize your files as _sasa_ bamboos, on which you can hang almost any number of _tanzaku_, just like adding tags on it.
---
<h6 align="center"><i>&copy; Masahiko AMANO aka H1K0, 2022—present</i></h6>
[release-shield]: https://img.shields.io/github/release/H1K0/tanabata/all.svg?style=for-the-badge
[release-link]: https://github.com/H1K0/tanabata/releases
<!-- [release-shield]: https://img.shields.io/github/release/H1K0/tanabata/all.svg?style=for-the-badge
[release-link]: https://github.com/H1K0/tanabata/releases -->
+1 -1
View File
@@ -1,5 +1,5 @@
title: Tanabata FM
description: A file manager that will let you enjoy the Tanabata festival!
description: Web file manager with tags!
remote_theme: pages-themes/merlot@v0.2.0
plugins:
- jekyll-remote-theme
+374
View File
@@ -0,0 +1,374 @@
from configparser import ConfigParser
from psycopg2.pool import ThreadedConnectionPool
from psycopg2.extras import RealDictCursor
from contextlib import contextmanager
from os import access, W_OK, makedirs, chmod, system
from os.path import isfile, join, basename
from shutil import move
from magic import Magic
from preview_generator.manager import PreviewManager
conf = None
mage = None
previewer = None
db_pool = None
DEFAULT_SORTING = {
"files": {
"key": "created",
"asc": False
},
"tags": {
"key": "created",
"asc": False
},
"categories": {
"key": "created",
"asc": False
},
"pools": {
"key": "created",
"asc": False
},
}
def Initialize(conf_path="/etc/tfm/tfm.conf"):
global mage, previewer
load_config(conf_path)
mage = Magic(mime=True)
previewer = PreviewManager(conf["Paths"]["Thumbs"])
db_connect(conf["DB.limits"]["MinimumConnections"], conf["DB.limits"]["MaximumConnections"], **conf["DB.params"])
def load_config(path):
global conf
conf = ConfigParser()
conf.read(path)
def db_connect(minconn, maxconn, **kwargs):
global db_pool
db_pool = ThreadedConnectionPool(minconn, maxconn, **kwargs)
@contextmanager
def _db_cursor():
global db_pool
try:
conn = db_pool.getconn()
except:
raise RuntimeError("Database not connected")
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
yield cur
conn.commit()
except:
conn.rollback()
raise
finally:
db_pool.putconn(conn)
def _validate_column_name(cur, table, column):
cur.execute("SELECT get_column_names(%s) AS name", (table,))
if all([column!=col["name"] for col in cur.fetchall()]):
raise RuntimeError("Invalid column name")
def authorize(username, password, useragent):
with _db_cursor() as cur:
cur.execute("SELECT tfm_session_request(tfm_user_auth(%s, %s), %s) AS sid", (username, password, useragent))
sid = cur.fetchone()["sid"]
return TSession(sid)
class TSession:
sid = None
def __init__(self, sid):
with _db_cursor() as cur:
cur.execute("SELECT tfm_session_validate(%s) IS NOT NULL AS valid", (sid,))
if not cur.fetchone()["valid"]:
raise RuntimeError("Invalid sid")
self.sid = sid
def terminate(self):
with _db_cursor() as cur:
cur.execute("CALL tfm_session_terminate(%s)", (self.sid,))
del self
@property
def username(self):
with _db_cursor() as cur:
cur.execute("SELECT tfm_session_username(%s) AS name", (self.sid,))
return cur.fetchone()["name"]
@property
def is_admin(self):
with _db_cursor() as cur:
cur.execute("SELECT * FROM tfm_user_get_info(%s)", (self.sid,))
return cur.fetchone()["can_edit"]
def get_files(self, order_key=DEFAULT_SORTING["files"]["key"], order_asc=DEFAULT_SORTING["files"]["asc"], offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_files", order_key)
cur.execute("SELECT * FROM tfm_get_files(%%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid,))
return list(map(dict, cur.fetchall()))
def get_files_by_filter(self, philter=None, order_key=DEFAULT_SORTING["files"]["key"], order_asc=DEFAULT_SORTING["files"]["asc"], offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_files", order_key)
cur.execute("SELECT * FROM tfm_get_files_by_filter(%%s, %%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid, philter))
return list(map(dict, cur.fetchall()))
def get_tags(self, order_key=DEFAULT_SORTING["tags"]["key"], order_asc=DEFAULT_SORTING["tags"]["asc"], offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_tags", order_key)
cur.execute("SELECT * FROM tfm_get_tags(%%s) ORDER BY %s %s, name ASC OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid,))
return list(map(dict, cur.fetchall()))
def get_categories(self, order_key=DEFAULT_SORTING["categories"]["key"], order_asc=DEFAULT_SORTING["categories"]["asc"], offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_categories", order_key)
cur.execute("SELECT * FROM tfm_get_categories(%%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid,))
return list(map(dict, cur.fetchall()))
def get_pools(self, order_key=DEFAULT_SORTING["pools"]["key"], order_asc=DEFAULT_SORTING["pools"]["asc"], offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_pools", order_key)
cur.execute("SELECT * FROM tfm_get_pools(%%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid,))
return list(map(dict, cur.fetchall()))
def get_autotags(self, order_key="child_id", order_asc=True, offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_autotags", order_key)
cur.execute("SELECT * FROM tfm_get_autotags(%%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid,))
return list(map(dict, cur.fetchall()))
def get_my_sessions(self, order_key="started", order_asc=False, offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_sessions", order_key)
cur.execute("SELECT * FROM tfm_get_my_sessions(%%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid,))
return list(map(dict, cur.fetchall()))
def get_tags_by_file(self, file_id, order_key=DEFAULT_SORTING["tags"]["key"], order_asc=DEFAULT_SORTING["tags"]["asc"], offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_tags", order_key)
cur.execute("SELECT * FROM tfm_get_tags_by_file(%%s, %%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid, file_id))
return list(map(dict, cur.fetchall()))
def get_files_by_tag(self, tag_id, order_key=DEFAULT_SORTING["files"]["key"], order_asc=DEFAULT_SORTING["files"]["asc"], offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_files", order_key)
cur.execute("SELECT * FROM tfm_get_files_by_tag(%%s, %%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid, tag_id))
return list(map(dict, cur.fetchall()))
def get_files_by_pool(self, pool_id, order_key=DEFAULT_SORTING["files"]["key"], order_asc=DEFAULT_SORTING["files"]["asc"], offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_files", order_key)
cur.execute("SELECT * FROM tfm_get_files_by_pool(%%s, %%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid, pool_id))
return list(map(dict, cur.fetchall()))
def get_parent_tags(self, tag_id, order_key=DEFAULT_SORTING["tags"]["key"], order_asc=DEFAULT_SORTING["tags"]["asc"], offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_tags", order_key)
cur.execute("SELECT * FROM tfm_get_parent_tags(%%s, %%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid, tag_id))
return list(map(dict, cur.fetchall()))
def get_my_file_views(self, file_id=None, order_key="datetime", order_asc=False, offset=0, limit=None):
with _db_cursor() as cur:
_validate_column_name(cur, "v_files", order_key)
cur.execute("SELECT * FROM tfm_get_my_file_views(%%s, %%s) ORDER BY %s %s OFFSET %s LIMIT %s" % (
order_key,
"ASC" if order_asc else "DESC",
int(offset),
int(limit) if limit is not None else "ALL"
), (self.sid, file_id))
return list(map(dict, cur.fetchall()))
def get_file(self, file_id):
with _db_cursor() as cur:
cur.execute("SELECT * FROM tfm_get_files(%s) WHERE id=%s", (self.sid, file_id))
return cur.fetchone()
def get_tag(self, tag_id):
with _db_cursor() as cur:
cur.execute("SELECT * FROM tfm_get_tags(%s) WHERE id=%s", (self.sid, tag_id))
return cur.fetchone()
def get_category(self, category_id):
with _db_cursor() as cur:
cur.execute("SELECT * FROM tfm_get_categories(%s) WHERE id=%s", (self.sid, category_id))
return cur.fetchone()
def view_file(self, file_id):
with _db_cursor() as cur:
cur.execute("CALL tfm_view_file(%s, %s)", (self.sid, file_id))
def add_file(self, path, datetime=None, notes=None, is_private=None, orig_name=True):
if not isfile(path):
raise FileNotFoundError("No such file '%s'" % path)
if not access(conf["Paths"]["Files"], W_OK) or not access(conf["Paths"]["Thumbs"], W_OK):
raise PermissionError("Invalid directories for files and thumbs")
mime = mage.from_file(path)
if orig_name == True:
orig_name = basename(path)
with _db_cursor() as cur:
cur.execute("SELECT * FROM tfm_add_file(%s, %s, %s, %s, %s, %s)", (self.sid, mime, datetime, notes, is_private, orig_name))
res = cur.fetchone()
file_id = res["f_id"]
ext = res["ext"]
file_path = join(conf["Paths"]["Files"], file_id)
move(path, file_path)
thumb_path = previewer.get_jpeg_preview(file_path, height=160, width=160)
preview_path = previewer.get_jpeg_preview(file_path, height=1080, width=1920)
chmod(file_path, 0o664)
chmod(thumb_path, 0o664)
chmod(preview_path, 0o664)
return file_id, ext
def add_tag(self, name, notes=None, color=None, category_id=None, is_private=None):
if color is not None:
color = color.replace('#', '')
if not category_id:
category_id = None
with _db_cursor() as cur:
cur.execute("SELECT tfm_add_tag(%s, %s, %s, %s, %s, %s) AS id", (self.sid, name, notes, color, category_id, is_private))
return cur.fetchone()["id"]
def add_category(self, name, notes=None, color=None, is_private=None):
if color is not None:
color = color.replace('#', '')
with _db_cursor() as cur:
cur.execute("SELECT tfm_add_category(%s, %s, %s, %s, %s) AS id", (self.sid, name, notes, color, is_private))
return cur.fetchone()["id"]
def add_pool(self, name, notes=None, parent_id=None, is_private=None):
with _db_cursor() as cur:
cur.execute("SELECT tfm_add_pool(%s, %s, %s, %s, %s) AS id", (self.sid, name, notes, parent_id, is_private))
return cur.fetchone()["id"]
def add_autotag(self, child_id, parent_id, is_active=None, apply_to_existing=None):
with _db_cursor() as cur:
cur.execute("SELECT tfm_add_autotag(%s, %s, %s, %s, %s) AS added", (self.sid, child_id, parent_id, is_active, apply_to_existing))
return cur.fetchone()["added"]
def add_file_to_tag(self, file_id, tag_id):
with _db_cursor() as cur:
cur.execute("SELECT tfm_add_file_to_tag(%s, %s, %s) AS id", (self.sid, file_id, tag_id))
return list(map(lambda t: t["id"], cur.fetchall()))
def add_file_to_pool(self, file_id, pool_id):
with _db_cursor() as cur:
cur.execute("SELECT tfm_add_file_to_pool(%s, %s, %s) AS added", (self.sid, file_id, pool_id))
return cur.fetchone()["added"]
def edit_file(self, file_id, mime=None, datetime=None, notes=None, is_private=None):
with _db_cursor() as cur:
cur.execute("CALL tfm_edit_file(%s, %s, %s, %s, %s, %s)", (self.sid, file_id, mime, datetime, notes, is_private))
def edit_tag(self, tag_id, name=None, notes=None, color=None, category_id=None, is_private=None):
if color is not None:
color = color.replace('#', '')
if not category_id:
category_id = None
with _db_cursor() as cur:
cur.execute("CALL tfm_edit_tag(%s, %s, %s, %s, %s, %s, %s)", (self.sid, tag_id, name, notes, color, category_id, is_private))
def edit_category(self, category_id, name=None, notes=None, color=None, is_private=None):
if color is not None:
color = color.replace('#', '')
with _db_cursor() as cur:
cur.execute("CALL tfm_edit_category(%s, %s, %s, %s, %s, %s)", (self.sid, category_id, name, notes, color, is_private))
def edit_pool(self, pool_id, name=None, notes=None, parent_id=None, is_private=None):
with _db_cursor() as cur:
cur.execute("CALL tfm_edit_pool(%s, %s, %s, %s, %s, %s)", (self.sid, pool_id, name, notes, parent_id, is_private))
def remove_file(self, file_id):
with _db_cursor() as cur:
cur.execute("CALL tfm_remove_file(%s, %s)", (self.sid, file_id))
if system("rm %s/%s*" % (conf["Paths"]["Files"], file_id)):
raise RuntimeError("Failed to remove file '%s'" % file_id)
def remove_tag(self, tag_id):
with _db_cursor() as cur:
cur.execute("CALL tfm_remove_tag(%s, %s)", (self.sid, tag_id))
def remove_category(self, category_id):
with _db_cursor() as cur:
cur.execute("CALL tfm_remove_category(%s, %s)", (self.sid, category_id))
def remove_pool(self, pool_id):
with _db_cursor() as cur:
cur.execute("CALL tfm_remove_pool(%s, %s)", (self.sid, pool_id))
def remove_autotag(self, child_id, parent_id):
with _db_cursor() as cur:
cur.execute("CALL tfm_remove_autotag(%s, %s, %s)", (self.sid, child_id, parent_id))
def remove_file_to_tag(self, file_id, tag_id):
with _db_cursor() as cur:
cur.execute("CALL tfm_remove_file_to_tag(%s, %s, %s)", (self.sid, file_id, tag_id))
def remove_file_to_pool(self, file_id, pool_id):
with _db_cursor() as cur:
cur.execute("CALL tfm_remove_file_to_pool(%s, %s, %s)", (self.sid, file_id, pool_id))
+178
View File
@@ -0,0 +1,178 @@
#!../venv/bin/python3
import telebot
import sys
import os
import atexit
import signal
from time import sleep
from socket import getaddrinfo
from requests import get
from subprocess import check_output
import logging as log
from json import loads as ljson
from datetime import datetime
import pytz
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import api.tfm_api as tfm_api
# set logger
log.basicConfig(
level=log.INFO,
filename="/var/log/tfm/tfm-tb.log",
filemode="a",
format="%(asctime)s | %(threadName)s | %(levelname)s | %(message)s"
)
# actions to do on exit
exit_actions = []
defer = exit_actions.append
def finalize(*args):
exec('\n'.join(exit_actions))
os._exit(0)
atexit.register(finalize)
signal.signal(signal.SIGTERM, finalize)
signal.signal(signal.SIGINT, finalize)
# initialize TFM API
try:
tfm_api.Initialize()
tfm = tfm_api.TSession("af6dde9b-7be1-46f2-872e-9a06ce3d3358")
except Exception as e:
log.critical(f"failed to initialize TFM API: {str(e)}")
exit(1)
# initialize bot
tfm_tb = telebot.TeleBot(tfm_api.conf["Telebot"]["Token"])
TZ = pytz.timezone("Europe/Moscow")
# check if user is authorized and if chat exists in db
def check_chat(message):
return message.from_user.id == 924090228
# file handler
@tfm_tb.message_handler(content_types=['document', 'photo', 'audio', 'video', 'voice', 'animation'])
def file_handler(message):
if not check_chat(message):
return
notes = None
orig_name = None
if message.forward_from_chat:
notes = f"Telegram origin: \"{message.forward_from_chat.title}\" ({message.forward_from_chat.username})"
if message.photo:
fname = f"{message.photo[-1].file_unique_id}"
log.info(f"got photo '{fname}'")
file_info = tfm_tb.get_file(message.photo[-1].file_id)
file_path = os.path.join(tfm_api.conf["Telebot"]["SaveFolder"], fname)
with open(file_path, 'wb') as f:
f.write(tfm_tb.download_file(file_info.file_path))
elif message.video:
fname = f"{message.video.file_unique_id}"
log.info(f"got video '{fname}'")
file_info = tfm_tb.get_file(message.video.file_id)
file_path = os.path.join(tfm_api.conf["Telebot"]["SaveFolder"], fname)
with open(file_path, 'wb') as f:
f.write(tfm_tb.download_file(file_info.file_path))
else:
file = None
if message.document:
file = message.document
elif message.animation:
file = message.animation
else:
tfm_tb.reply_to(message, "Unsupported file type.")
return
log.info(f"got file '{file.file_name}'")
orig_name = file.file_name
file_info = tfm_tb.get_file(file.file_id)
file_path = os.path.join(tfm_api.conf["Telebot"]["SaveFolder"], f"{file.file_unique_id}")
try:
with open(file_path, 'wb') as f:
f.write(tfm_tb.download_file(file_info.file_path))
log.info(f"downloaded file '{file_path}'")
exif = ljson(os.popen(f"exiftool -json \"{file_path}\"").read())[0]
dt = exif["FileModifyDate"]
if "SubSecCreateDate" in exif.keys():
dt = exif["SubSecCreateDate"]
if '.' in dt:
dt = datetime.strptime(dt, "%Y:%m:%d %H:%M:%S.%f%z")
else:
dt = datetime.strptime(dt, "%Y:%m:%d %H:%M:%S%z")
file_id, ext = tfm.add_file(file_path, dt, notes, orig_name=orig_name)
tfm.add_file_to_tag(file_id, "d6d8129a-984d-4451-8c83-d04523ced8a8")
except Exception as e:
tfm_tb.reply_to(message, "Error: %s" % str(e).split('\n')[0])
log.info(f"Error: %s" % str(e).split('\n')[0])
os.remove(file_path)
log.info(f"removed file '{file_path}'")
else:
tfm_tb.reply_to(message, "File saved.")
log.info(f"imported file '{file_path}'")
# folder scanner
@tfm_tb.message_handler(commands=['scan'])
def scan(message):
tfm_tb.reply_to(message, "Scanning...")
log.info("Scanning...")
scan_dir = "/srv/share/hfs/misc/tfm_temp/scan"
files = []
for file in os.listdir(scan_dir):
new_file = {"name": file}
file = os.path.join(scan_dir, file)
if not os.path.isfile(file):
continue
new_file["path"] = file
try:
exif = ljson(os.popen(f"exiftool -json \"{file}\"").read())[0]
except Exception as e:
log.error("Error while parsing EXIF for file '%s': %s" % (file, e))
continue
dt = exif["FileModifyDate"]
if "SubSecDateTimeOriginal" in exif.keys():
dt = exif["SubSecDateTimeOriginal"]
elif "DateTimeOriginal" in exif.keys():
dt = exif["DateTimeOriginal"]
if '.' in dt:
try:
dt = datetime.strptime(dt, "%Y:%m:%d %H:%M:%S.%f%z")
except:
dt = TZ.localize(datetime.strptime(dt, "%Y:%m:%d %H:%M:%S.%f"))
else:
try:
try:
dt = datetime.strptime(dt, "%Y:%m:%d %H:%M:%S%z")
except:
dt = TZ.localize(datetime.strptime(dt[:19], "%Y:%m:%d %H:%M:%S"))
except:
log.error("Broken date: %s\t%s" % (new_file, dt))
continue
new_file["datetime"] = dt
files.append(new_file)
tfm_tb.reply_to(message, f"{len(files)} files found.")
log.info(f"{len(files)} files found.")
files.sort(key=lambda f: f["datetime"])
for file in files:
try:
file_id, ext = tfm.add_file(file["path"], file["datetime"])
tfm.add_file_to_tag(file_id, "d6d8129a-984d-4451-8c83-d04523ced8a8")
except Exception as e:
tfm_tb.reply_to(message, f"Error adding file '{file['name']}': {str(e)}")
log.error(f"Error adding file '{file['name']}': {str(e)}")
tfm_tb.reply_to(message, "Files added.")
log.info("Files added.")
if __name__ == '__main__':
log.info("tfm-tb started")
defer("log.info(\"tfm-tb stopped\")")
while True:
try:
tfm_tb.polling(none_stop=True)
except Exception as e:
log.exception("exception on polling")
sleep(1)
-25
View File
@@ -1,25 +0,0 @@
#!/bin/bash
BUILD_DIR=./build/
TARGET=all
while getopts "b:t:" option; do
case $option in
b) BUILD_DIR=$OPTARG ;;
t) TARGET=$OPTARG ;;
?)
echo "Error: invalid option"
exit 1
;;
esac
done
if [ ! -d "$BUILD_DIR" ]; then
mkdir "$BUILD_DIR"
if [ ! -d "$BUILD_DIR" ]; then
echo "Error: could not create folder '$BUILD_DIR'"
exit 1
fi
fi
cmake -S . -B "$BUILD_DIR" && cmake --build "$BUILD_DIR" --target "$TARGET"
-103
View File
@@ -1,103 +0,0 @@
// Tanabata file manager core names
// By Masahiko AMANO aka H1K0
#pragma once
#ifndef TANABATA_CORE_H
#define TANABATA_CORE_H
#ifdef __cplusplus
#include <cstdint>
#include <cstdio>
extern "C" {
#else
#include <stdint.h>
#include <stdio.h>
#endif
// ==================== STRUCTS ==================== //
// Sasa (笹) - a file record
typedef struct sasa {
uint64_t id; // Sasa ID
uint64_t created_ts; // Sasa creation timestamp
char *path; // File path
} Sasa;
// Tanzaku (短冊) - a tag record
typedef struct tanzaku {
uint64_t id; // Tanzaku ID
uint64_t created_ts; // Tanzaku creation timestamp
uint64_t modified_ts; // Tanzaku last modification timestamp
char *name; // Tanzaku name
char *description; // Tanzaku description
} Tanzaku;
// Kazari (飾り) - a sasa-tanzaku association record
typedef struct kazari {
uint64_t sasa_id; // Sasa ID
uint64_t tanzaku_id; // Tanzaku ID
uint64_t created_ts; // Kazari creation timestamp
} Kazari;
// Sasahyou (笹表) - database of sasa
typedef struct sasahyou {
uint64_t created_ts; // Sasahyou creation timestamp
uint64_t modified_ts; // Sasahyou last modification timestamp
uint64_t size; // Sasahyou size (including holes)
Sasa *database; // Array of sasa
uint64_t hole_cnt; // Number of holes
Sasa **holes; // Array of pointers to holes
FILE *file; // Storage file for sasahyou
} Sasahyou;
// Sappyou (冊表) - database of tanzaku
typedef struct sappyou {
uint64_t created_ts; // Sappyou creation timestamp
uint64_t modified_ts; // Sappyou last modification timestamp
uint64_t size; // Sappyou size (including holes)
Tanzaku *database; // Array of tanzaku
uint64_t hole_cnt; // Number of holes
Tanzaku **holes; // Array of pointers to holes
FILE *file; // Storage file for sappyou
} Sappyou;
// Shoppyou (飾表) - database of kazari
typedef struct shoppyou {
uint64_t created_ts; // Shoppyou creation timestamp
uint64_t modified_ts; // Shoppyou last modification timestamp
uint64_t size; // Shoppyou size (including holes)
Kazari *database; // Array of kazari
uint64_t hole_cnt; // Number of holes
Kazari **holes; // Array of pointers to holes
FILE *file; // Storage file for shoppyou
} Shoppyou;
// Tanabata (七夕) - the struct with all databases
typedef struct tanabata {
Sasahyou sasahyou; // Sasahyou struct
Sappyou sappyou; // Sappyou struct
Shoppyou shoppyou; // Shoppyou struct
uint64_t sasahyou_mod; // Sasahyou file last modificaton timestamp
uint64_t sappyou_mod; // Sappyou file last modificaton timestamp
uint64_t shoppyou_mod; // Shoppyou file last modificaton timestamp
} Tanabata;
// ==================== CONSTANTS ==================== //
// ID of hole - an invalid record
#define HOLE_ID (-1)
// Hole sasa constant with hole ID
extern const Sasa HOLE_SASA;
// Hole tanzaku constant with hole ID
extern const Tanzaku HOLE_TANZAKU;
// Hole kazari constant with hole ID
extern const Kazari HOLE_KAZARI;
#ifdef __cplusplus
}
#endif
#endif //TANABATA_CORE_H
-86
View File
@@ -1,86 +0,0 @@
// Tanabata lib
// By Masahiko AMANO aka H1K0
#pragma once
#ifndef TANABATA_H
#define TANABATA_H
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
#include "core.h"
// ==================== DATABASE SECTION ==================== //
// Initialize empty tanabata
int tanabata_init(Tanabata *tanabata);
// Free tanabata
int tanabata_free(Tanabata *tanabata);
// Weed tanabata
int tanabata_weed(Tanabata *tanabata);
// Load tanabata
int tanabata_load(Tanabata *tanabata);
// Save tanabata
int tanabata_save(Tanabata *tanabata);
// Open tanabata
int tanabata_open(Tanabata *tanabata, const char *path);
// Dump tanabata
int tanabata_dump(Tanabata *tanabata, const char *path);
// ==================== SASA SECTION ==================== //
// Add sasa
Sasa tanabata_sasa_add(Tanabata *tanabata, const char *path);
// Remove sasa by ID
int tanabata_sasa_rem(Tanabata *tanabata, uint64_t sasa_id);
// Update sasa file path
int tanabata_sasa_upd(Tanabata *tanabata, uint64_t sasa_id, const char *path);
// Get sasa by ID
Sasa tanabata_sasa_get(Tanabata *tanabata, uint64_t sasa_id);
// ==================== TANZAKU SECTION ==================== //
// Add tanzaku
Tanzaku tanabata_tanzaku_add(Tanabata *tanabata, const char *name, const char *description);
// Remove tanzaku by ID
int tanabata_tanzaku_rem(Tanabata *tanabata, uint64_t tanzaku_id);
// Update tanzaku name and description
int tanabata_tanzaku_upd(Tanabata *tanabata, uint64_t tanzaku_id, const char *name, const char *description);
// Get tanzaku by ID
Tanzaku tanabata_tanzaku_get(Tanabata *tanabata, uint64_t tanzaku_id);
// ==================== KAZARI SECTION ==================== //
// Add kazari
int tanabata_kazari_add(Tanabata *tanabata, uint64_t sasa_id, uint64_t tanzaku_id);
// Remove kazari
int tanabata_kazari_rem(Tanabata *tanabata, uint64_t sasa_id, uint64_t tanzaku_id);
// Get tanzaku list of sasa
Tanzaku *tanabata_tanzaku_get_by_sasa(Tanabata *tanabata, uint64_t sasa_id);
// Get sasa list of tanzaku
Sasa *tanabata_sasa_get_by_tanzaku(Tanabata *tanabata, uint64_t tanzaku_id);
#ifdef __cplusplus
}
#endif
#endif //TANABATA_H
-27
View File
@@ -1,27 +0,0 @@
// Tanabata DBMS client lib
// By Masahiko AMANO aka H1K0
#pragma once
#ifndef TANABATA_DBMS_CLIENT_H
#define TANABATA_DBMS_CLIENT_H
#ifdef __cplusplus
extern "C" {
#endif
#include "tdbms.h"
// Connect to TDBMS server
int tdbms_connect(const char *domain, const char *addr);
// Close connection to TDBMS server
int tdbms_close(int socket_fd);
// Execute a TDB request
char *tdb_query(int socket_fd, const char *db_name, char request_code, const char *request_body);
#ifdef __cplusplus
}
#endif
#endif //TANABATA_DBMS_CLIENT_H
-58
View File
@@ -1,58 +0,0 @@
// Tanabata DBMS core names
// By Masahiko AMANO aka H1K0
#pragma once
#ifndef TANABATA_DBMS_H
#define TANABATA_DBMS_H
#ifdef __cplusplus
extern "C" {
#endif
// ASCII End Of Transmission code
#define EOT 4
// TDBMS request code bits
enum TRC_BITS {
trc_bit_remove = 0b1,
trc_bit_add = 0b10,
trc_bit_update = 0b100,
trc_bit_kazari = 0b1000,
trc_bit_sasa = 0b10000,
trc_bit_tanzaku = 0b100000,
};
// TDBMS request codes
enum TRC {
trc_db_stats = 0b0,
trc_db_init = 0b11,
trc_db_load = 0b10,
trc_db_save = 0b100,
trc_db_edit = 0b110,
trc_db_remove_soft = 0b1,
trc_db_remove_hard = 0b101,
trc_db_weed = 0b111,
trc_sasa_get = 0b10000,
trc_sasa_get_by_tanzaku = 0b101000,
trc_sasa_add = 0b10010,
trc_sasa_update = 0b10100,
trc_sasa_remove = 0b10001,
trc_tanzaku_get = 0b100000,
trc_tanzaku_get_by_sasa = 0b11000,
trc_tanzaku_add = 0b100010,
trc_tanzaku_update = 0b100100,
trc_tanzaku_remove = 0b100001,
trc_kazari_get = 0b1000,
trc_kazari_add = 0b1010,
trc_kazari_add_single_sasa_to_multiple_tanzaku = 0b11010,
trc_kazari_add_single_tanzaku_to_multiple_sasa = 0b101010,
trc_kazari_remove = 0b1001,
trc_kazari_remove_single_sasa_to_multiple_tanzaku = 0b11001,
trc_kazari_remove_single_tanzaku_to_multiple_sasa = 0b101001,
};
#ifdef __cplusplus
}
#endif
#endif //TANABATA_DBMS_H
+25
View File
@@ -0,0 +1,25 @@
blinker==1.9.0
certifi==2024.12.14
charset-normalizer==3.4.1
click==8.1.8
ffmpeg-python==0.2.0
filelock==3.16.1
Flask==3.1.0
Flask-Cors==5.0.0
future==1.0.0
idna==3.10
itsdangerous==2.2.0
Jinja2==3.1.5
MarkupSafe==3.0.2
preview_generator==0.29
psycopg2-binary==2.9.10
pyexifinfo==0.4.0
pyTelegramBotAPI==4.25.0
python-magic==0.4.27
pytz==2024.2
requests==2.32.3
ua-parser==1.0.0
ua-parser-builtins==0.18.0.post1
urllib3==2.3.0
Wand==0.6.13
Werkzeug==3.1.3
-111
View File
@@ -1,111 +0,0 @@
// Tanabata file manager core functions
// By Masahiko AMANO aka H1K0
#pragma once
#ifndef TANABATA_CORE_FUNC_H
#define TANABATA_CORE_FUNC_H
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include <stdint.h>
#endif
#include "../../include/core.h"
// ==================== SASAHYOU SECTION ==================== //
// Initialize empty sasahyou
int sasahyou_init(Sasahyou *sasahyou);
// Free sasahyou
int sasahyou_free(Sasahyou *sasahyou);
// Load sasahyou from file
int sasahyou_load(Sasahyou *sasahyou);
// Save sasahyou to file
int sasahyou_save(Sasahyou *sasahyou);
// Open sasahyou file and load data from it
int sasahyou_open(Sasahyou *sasahyou, const char *path);
// Dump sasahyou to file
int sasahyou_dump(Sasahyou *sasahyou, const char *path);
// Add sasa to sasahyou
Sasa sasa_add(Sasahyou *sasahyou, const char *path);
// Remove sasa from sasahyou
int sasa_rem(Sasahyou *sasahyou, uint64_t sasa_id);
// Update sasa file path
int sasa_upd(Sasahyou *sasahyou, uint64_t sasa_id, const char *path);
// ==================== SAPPYOU SECTION ==================== //
// Initialize empty sappyou
int sappyou_init(Sappyou *sappyou);
// Free sappyou
int sappyou_free(Sappyou *sappyou);
// Load sappyou from file
int sappyou_load(Sappyou *sappyou);
// Save sappyou to file
int sappyou_save(Sappyou *sappyou);
// Open sappyou file and load data from it
int sappyou_open(Sappyou *sappyou, const char *path);
// Dump sappyou to file
int sappyou_dump(Sappyou *sappyou, const char *path);
// Add new tanzaku to sappyou
Tanzaku tanzaku_add(Sappyou *sappyou, const char *name, const char *description);
// Remove tanzaku from sappyou
int tanzaku_rem(Sappyou *sappyou, uint64_t tanzaku_id);
// Update tanzaku name and description
int tanzaku_upd(Sappyou *sappyou, uint64_t tanzaku_id, const char *name, const char *description);
// ==================== SHOPPYOU SECTION ==================== //
// Initialize empty shoppyou
int shoppyou_init(Shoppyou *shoppyou);
// Free shoppyou
int shoppyou_free(Shoppyou *shoppyou);
// Load shoppyou from file
int shoppyou_load(Shoppyou *shoppyou);
// Save shoppyou to file
int shoppyou_save(Shoppyou *shoppyou);
// Open shoppyou file and load data from it
int shoppyou_open(Shoppyou *shoppyou, const char *path);
// Dump shoppyou to file
int shoppyou_dump(Shoppyou *shoppyou, const char *path);
// Add kazari to shoppyou
int kazari_add(Shoppyou *shoppyou, uint64_t sasa_id, uint64_t tanzaku_id);
// Remove kazari from shoppyou
int kazari_rem(Shoppyou *shoppyou, uint64_t sasa_id, uint64_t tanzaku_id);
// Remove all kazari with a specific sasa ID from shoppyou
int kazari_rem_by_sasa(Shoppyou *shoppyou, uint64_t sasa_id);
// Remove all kazari with a specific tanzaku ID from shoppyou
int kazari_rem_by_tanzaku(Shoppyou *shoppyou, uint64_t tanzaku_id);
#ifdef __cplusplus
}
#endif
#endif //TANABATA_CORE_FUNC_H
-222
View File
@@ -1,222 +0,0 @@
#include <stdint.h>
#include <malloc.h>
#include <string.h>
#include <time.h>
#include "core_func.h"
const Tanzaku HOLE_TANZAKU = {HOLE_ID, 0, 0, NULL, NULL};
// Sappyou file signature: 七夕冊表
const uint16_t SAPPYOU_SIG[4] = {L'', L'', L'', L''};
int sappyou_init(Sappyou *sappyou) {
sappyou->created_ts = time(NULL);
sappyou->modified_ts = sappyou->created_ts;
sappyou->size = 0;
sappyou->database = NULL;
sappyou->hole_cnt = 0;
sappyou->holes = NULL;
sappyou->file = NULL;
return 0;
}
int sappyou_free(Sappyou *sappyou) {
sappyou->created_ts = 0;
sappyou->modified_ts = 0;
sappyou->size = 0;
sappyou->hole_cnt = 0;
if (sappyou->database != NULL) {
for (Tanzaku *current_tanzaku = sappyou->database + sappyou->size - 1;
current_tanzaku >= sappyou->database; current_tanzaku--) {
free(current_tanzaku->name);
free(current_tanzaku->description);
}
free(sappyou->database);
sappyou->database = NULL;
}
free(sappyou->holes);
sappyou->holes = NULL;
if (sappyou->file != NULL) {
fclose(sappyou->file);
sappyou->file = NULL;
}
return 0;
}
int sappyou_load(Sappyou *sappyou) {
if (sappyou->file == NULL ||
(sappyou->file = freopen(NULL, "rb", sappyou->file)) == NULL) {
return 1;
}
Sappyou temp;
sappyou_init(&temp);
temp.file = sappyou->file;
uint16_t signature[4];
if (fread(signature, 2, 4, temp.file) != 4 ||
memcmp(signature, SAPPYOU_SIG, 8) != 0 ||
fread(&temp.created_ts, 8, 1, temp.file) != 1 ||
fread(&temp.modified_ts, 8, 1, temp.file) != 1 ||
fread(&temp.size, 8, 1, temp.file) != 1 ||
fread(&temp.hole_cnt, 8, 1, temp.file) != 1) {
return 1;
}
temp.database = calloc(temp.size, sizeof(Tanzaku));
temp.holes = calloc(temp.hole_cnt, sizeof(Tanzaku *));
size_t max_string_len = SIZE_MAX;
Tanzaku *current_tanzaku = temp.database;
for (uint64_t i = 0, r = temp.hole_cnt; i < temp.size; i++, current_tanzaku++) {
if (fgetc(temp.file) != 0) {
current_tanzaku->id = i;
if (fread(&current_tanzaku->created_ts, 8, 1, temp.file) != 1 ||
fread(&current_tanzaku->modified_ts, 8, 1, temp.file) != 1 ||
getdelim(&current_tanzaku->name, &max_string_len, 0, temp.file) == -1 ||
getdelim(&current_tanzaku->description, &max_string_len, 0, temp.file) == -1) {
temp.file = NULL;
sappyou_free(&temp);
return 1;
}
} else {
current_tanzaku->id = HOLE_ID;
if (r == 0) {
temp.file = NULL;
sappyou_free(&temp);
return 1;
}
r--;
temp.holes[r] = current_tanzaku;
}
}
if (fflush(temp.file) == 0) {
sappyou->file = NULL;
sappyou_free(sappyou);
*sappyou = temp;
return 0;
}
temp.file = NULL;
sappyou_free(&temp);
return 1;
}
int sappyou_save(Sappyou *sappyou) {
if (sappyou->file == NULL ||
(sappyou->file = freopen(NULL, "wb", sappyou->file)) == NULL ||
fwrite(SAPPYOU_SIG, 2, 4, sappyou->file) != 4 ||
fwrite(&sappyou->created_ts, 8, 1, sappyou->file) != 1 ||
fwrite(&sappyou->modified_ts, 8, 1, sappyou->file) != 1 ||
fwrite(&sappyou->size, 8, 1, sappyou->file) != 1 ||
fwrite(&sappyou->hole_cnt, 8, 1, sappyou->file) != 1 ||
fflush(sappyou->file) != 0) {
return 1;
}
Tanzaku *current_tanzaku = sappyou->database;
for (uint64_t i = 0; i < sappyou->size; i++, current_tanzaku++) {
if (current_tanzaku->id != HOLE_ID) {
if (fputc(0xff, sappyou->file) == EOF ||
fwrite(&current_tanzaku->created_ts, 8, 1, sappyou->file) != 1 ||
fwrite(&current_tanzaku->modified_ts, 8, 1, sappyou->file) != 1 ||
fputs(current_tanzaku->name, sappyou->file) == EOF ||
fputc(0, sappyou->file) == EOF ||
fputs(current_tanzaku->description, sappyou->file) == EOF ||
fputc(0, sappyou->file) == EOF) {
return 1;
}
} else if (fputc(0, sappyou->file) == EOF) {
return 1;
}
}
return fflush(sappyou->file);
}
int sappyou_open(Sappyou *sappyou, const char *path) {
if (path == NULL) {
return 1;
}
if (sappyou->file == NULL && (sappyou->file = fopen(path, "rb")) == NULL ||
sappyou->file != NULL && (sappyou->file = freopen(path, "rb", sappyou->file)) == NULL) {
return 1;
}
return sappyou_load(sappyou);
}
int sappyou_dump(Sappyou *sappyou, const char *path) {
if (path == NULL) {
return 1;
}
if (sappyou->file == NULL && (sappyou->file = fopen(path, "wb")) == NULL ||
sappyou->file != NULL && (sappyou->file = freopen(path, "wb", sappyou->file)) == NULL) {
return 1;
}
return sappyou_save(sappyou);
}
Tanzaku tanzaku_add(Sappyou *sappyou, const char *name, const char *description) {
if (name == NULL || description == NULL || sappyou->size == -1 && sappyou->hole_cnt == 0) {
return HOLE_TANZAKU;
}
Tanzaku newbie;
newbie.created_ts = time(NULL);
newbie.modified_ts = newbie.created_ts;
newbie.name = malloc(strlen(name) + 1);
strcpy(newbie.name, name);
newbie.description = malloc(strlen(description) + 1);
strcpy(newbie.description, description);
if (sappyou->hole_cnt > 0) {
sappyou->hole_cnt--;
Tanzaku **hole_ptr = sappyou->holes + sappyou->hole_cnt;
newbie.id = *hole_ptr - sappyou->database;
**hole_ptr = newbie;
sappyou->holes = reallocarray(sappyou->holes, sappyou->hole_cnt, sizeof(Tanzaku *));
} else {
newbie.id = sappyou->size;
sappyou->size++;
sappyou->database = reallocarray(sappyou->database, sappyou->size, sizeof(Tanzaku));
sappyou->database[newbie.id] = newbie;
}
sappyou->modified_ts = newbie.created_ts;
return newbie;
}
int tanzaku_rem(Sappyou *sappyou, uint64_t tanzaku_id) {
if (tanzaku_id == HOLE_ID || tanzaku_id >= sappyou->size) {
return 1;
}
Tanzaku *current_tanzaku = sappyou->database + tanzaku_id;
if (current_tanzaku->id == HOLE_ID) {
return 1;
}
current_tanzaku->id = HOLE_ID;
free(current_tanzaku->name);
free(current_tanzaku->description);
if (tanzaku_id == sappyou->size - 1) {
sappyou->size--;
sappyou->database = reallocarray(sappyou->database, sappyou->size, sizeof(Tanzaku));
} else {
sappyou->hole_cnt++;
sappyou->holes = reallocarray(sappyou->holes, sappyou->hole_cnt, sizeof(Tanzaku *));
sappyou->holes[sappyou->hole_cnt - 1] = current_tanzaku;
}
sappyou->modified_ts = time(NULL);
return 0;
}
int tanzaku_upd(Sappyou *sappyou, uint64_t tanzaku_id, const char *name, const char *description) {
if (tanzaku_id == HOLE_ID || tanzaku_id >= sappyou->size || name == NULL && description == NULL) {
return 1;
}
Tanzaku *current_tanzaku = sappyou->database + tanzaku_id;
if (current_tanzaku->id == HOLE_ID) {
return 1;
}
if (name != NULL) {
current_tanzaku->name = realloc(current_tanzaku->name, strlen(name) + 1);
strcpy(current_tanzaku->name, name);
}
if (description != NULL) {
current_tanzaku->description = realloc(current_tanzaku->description, strlen(description) + 1);
strcpy(current_tanzaku->description, description);
}
sappyou->modified_ts = time(NULL);
current_tanzaku->modified_ts = sappyou->modified_ts;
return 0;
}
-206
View File
@@ -1,206 +0,0 @@
#include <stdint.h>
#include <malloc.h>
#include <string.h>
#include <time.h>
#include "core_func.h"
const Sasa HOLE_SASA = {HOLE_ID, 0, NULL};
// Sasahyou file signature: 七夕笹表
const uint16_t SASAHYOU_SIG[4] = {L'', L'', L'', L''};
int sasahyou_init(Sasahyou *sasahyou) {
sasahyou->created_ts = time(NULL);
sasahyou->modified_ts = sasahyou->created_ts;
sasahyou->size = 0;
sasahyou->database = NULL;
sasahyou->hole_cnt = 0;
sasahyou->holes = NULL;
sasahyou->file = NULL;
return 0;
}
int sasahyou_free(Sasahyou *sasahyou) {
sasahyou->created_ts = 0;
sasahyou->modified_ts = 0;
sasahyou->size = 0;
sasahyou->hole_cnt = 0;
if (sasahyou->database != NULL) {
for (Sasa *current_sasa = sasahyou->database + sasahyou->size - 1;
current_sasa >= sasahyou->database; current_sasa--) {
free(current_sasa->path);
}
free(sasahyou->database);
sasahyou->database = NULL;
}
free(sasahyou->holes);
sasahyou->holes = NULL;
if (sasahyou->file != NULL) {
fclose(sasahyou->file);
sasahyou->file = NULL;
}
return 0;
}
int sasahyou_load(Sasahyou *sasahyou) {
if (sasahyou->file == NULL ||
(sasahyou->file = freopen(NULL, "rb", sasahyou->file)) == NULL) {
return 1;
}
Sasahyou temp;
sasahyou_init(&temp);
temp.file = sasahyou->file;
uint16_t signature[4];
if (fread(signature, 2, 4, temp.file) != 4 ||
memcmp(signature, SASAHYOU_SIG, 8) != 0 ||
fread(&temp.created_ts, 8, 1, temp.file) != 1 ||
fread(&temp.modified_ts, 8, 1, temp.file) != 1 ||
fread(&temp.size, 8, 1, temp.file) != 1 ||
fread(&temp.hole_cnt, 8, 1, temp.file) != 1) {
return 1;
}
temp.database = calloc(temp.size, sizeof(Sasa));
temp.holes = calloc(temp.hole_cnt, sizeof(Sasa *));
size_t max_path_len = SIZE_MAX;
Sasa *current_sasa = temp.database;
for (uint64_t i = 0, r = temp.hole_cnt; i < temp.size; i++, current_sasa++) {
if (fgetc(temp.file) != 0) {
current_sasa->id = i;
if (fread(&current_sasa->created_ts, 8, 1, temp.file) != 1 ||
getdelim(&current_sasa->path, &max_path_len, 0, temp.file) == -1) {
temp.file = NULL;
sasahyou_free(&temp);
return 1;
}
} else {
current_sasa->id = HOLE_ID;
if (r == 0) {
temp.file = NULL;
sasahyou_free(&temp);
return 1;
}
r--;
temp.holes[r] = current_sasa;
}
}
if (fflush(temp.file) == 0) {
sasahyou->file = NULL;
sasahyou_free(sasahyou);
*sasahyou = temp;
return 0;
}
temp.file = NULL;
sasahyou_free(&temp);
return 1;
}
int sasahyou_save(Sasahyou *sasahyou) {
if (sasahyou->file == NULL ||
(sasahyou->file = freopen(NULL, "wb", sasahyou->file)) == NULL ||
fwrite(SASAHYOU_SIG, 2, 4, sasahyou->file) != 4 ||
fwrite(&sasahyou->created_ts, 8, 1, sasahyou->file) != 1 ||
fwrite(&sasahyou->modified_ts, 8, 1, sasahyou->file) != 1 ||
fwrite(&sasahyou->size, 8, 1, sasahyou->file) != 1 ||
fwrite(&sasahyou->hole_cnt, 8, 1, sasahyou->file) != 1 ||
fflush(sasahyou->file) != 0) {
return 1;
}
Sasa *current_sasa = sasahyou->database;
for (uint64_t i = 0; i < sasahyou->size; i++, current_sasa++) {
if (current_sasa->id != HOLE_ID) {
if (fputc(0xff, sasahyou->file) == EOF ||
fwrite(&current_sasa->created_ts, 8, 1, sasahyou->file) != 1 ||
fputs(current_sasa->path, sasahyou->file) == EOF ||
fputc(0, sasahyou->file) == EOF) {
return 1;
}
} else if (fputc(0, sasahyou->file) == EOF) {
return 1;
}
}
return fflush(sasahyou->file);
}
int sasahyou_open(Sasahyou *sasahyou, const char *path) {
if (path == NULL) {
return 1;
}
if (sasahyou->file == NULL && (sasahyou->file = fopen(path, "rb")) == NULL ||
sasahyou->file != NULL && (sasahyou->file = freopen(path, "rb", sasahyou->file)) == NULL) {
return 1;
}
return sasahyou_load(sasahyou);
}
int sasahyou_dump(Sasahyou *sasahyou, const char *path) {
if (path == NULL) {
return 1;
}
if (sasahyou->file == NULL && (sasahyou->file = fopen(path, "wb")) == NULL ||
sasahyou->file != NULL && (sasahyou->file = freopen(path, "wb", sasahyou->file)) == NULL) {
return 1;
}
return sasahyou_save(sasahyou);
}
Sasa sasa_add(Sasahyou *sasahyou, const char *path) {
if (path == NULL || sasahyou->size == -1 && sasahyou->hole_cnt == 0) {
return HOLE_SASA;
}
Sasa newbie;
newbie.created_ts = time(NULL);
newbie.path = malloc(strlen(path) + 1);
strcpy(newbie.path, path);
if (sasahyou->hole_cnt > 0) {
sasahyou->hole_cnt--;
Sasa **hole_ptr = sasahyou->holes + sasahyou->hole_cnt;
newbie.id = *hole_ptr - sasahyou->database;
**hole_ptr = newbie;
sasahyou->holes = reallocarray(sasahyou->holes, sasahyou->hole_cnt, sizeof(Sasa *));
} else {
newbie.id = sasahyou->size;
sasahyou->size++;
sasahyou->database = reallocarray(sasahyou->database, sasahyou->size, sizeof(Sasa));
sasahyou->database[newbie.id] = newbie;
}
sasahyou->modified_ts = newbie.created_ts;
return newbie;
}
int sasa_rem(Sasahyou *sasahyou, uint64_t sasa_id) {
if (sasa_id == HOLE_ID || sasa_id >= sasahyou->size) {
return 1;
}
Sasa *current_sasa = sasahyou->database + sasa_id;
if (current_sasa->id == HOLE_ID) {
return 1;
}
current_sasa->id = HOLE_ID;
free(current_sasa->path);
current_sasa->path = NULL;
if (sasa_id == sasahyou->size - 1) {
sasahyou->size--;
sasahyou->database = reallocarray(sasahyou->database, sasahyou->size, sizeof(Sasa));
} else {
sasahyou->hole_cnt++;
sasahyou->holes = reallocarray(sasahyou->holes, sasahyou->hole_cnt, sizeof(Sasa *));
sasahyou->holes[sasahyou->hole_cnt - 1] = current_sasa;
}
sasahyou->modified_ts = time(NULL);
return 0;
}
int sasa_upd(Sasahyou *sasahyou, uint64_t sasa_id, const char *path) {
if (sasa_id == HOLE_ID || sasa_id >= sasahyou->size || path == NULL) {
return 1;
}
Sasa *current_sasa = sasahyou->database + sasa_id;
if (current_sasa->id == HOLE_ID) {
return 1;
}
current_sasa->path = realloc(current_sasa->path, strlen(path) + 1);
strcpy(current_sasa->path, path);
sasahyou->modified_ts = time(NULL);
return 0;
}
-208
View File
@@ -1,208 +0,0 @@
#include <stdint.h>
#include <malloc.h>
#include <string.h>
#include <time.h>
#include "core_func.h"
const Kazari HOLE_KAZARI = {HOLE_ID, HOLE_ID, 0};
// Shoppyou file signature: 七夕飾表
static const uint16_t SHOPPYOU_SIG[4] = {L'', L'', L'', L''};
int shoppyou_init(Shoppyou *shoppyou) {
shoppyou->created_ts = time(NULL);
shoppyou->modified_ts = shoppyou->created_ts;
shoppyou->size = 0;
shoppyou->database = NULL;
shoppyou->hole_cnt = 0;
shoppyou->holes = NULL;
shoppyou->file = NULL;
return 0;
}
int shoppyou_free(Shoppyou *shoppyou) {
shoppyou->created_ts = 0;
shoppyou->modified_ts = 0;
shoppyou->size = 0;
shoppyou->hole_cnt = 0;
free(shoppyou->database);
shoppyou->database = NULL;
free(shoppyou->holes);
shoppyou->holes = NULL;
if (shoppyou->file != NULL) {
fclose(shoppyou->file);
shoppyou->file = NULL;
}
return 0;
}
int shoppyou_load(Shoppyou *shoppyou) {
if (shoppyou->file == NULL ||
(shoppyou->file = freopen(NULL, "rb", shoppyou->file)) == NULL) {
return 1;
}
Shoppyou temp;
shoppyou_init(&temp);
temp.file = shoppyou->file;
uint16_t signature[4];
if (fread(signature, 2, 4, temp.file) != 4 ||
memcmp(signature, SHOPPYOU_SIG, 8) != 0 ||
fread(&temp.created_ts, 8, 1, temp.file) != 1 ||
fread(&temp.modified_ts, 8, 1, temp.file) != 1 ||
fread(&temp.size, 8, 1, temp.file) != 1) {
return 1;
}
temp.database = calloc(temp.size, sizeof(Kazari));
Kazari *current_kazari = temp.database;
for (uint64_t i = 0; i < temp.size; i++, current_kazari++) {
if (fread(&current_kazari->created_ts, 8, 1, temp.file) != 1 ||
fread(&current_kazari->sasa_id, 8, 1, temp.file) != 1 ||
fread(&current_kazari->tanzaku_id, 8, 1, temp.file) != 1) {
temp.file = NULL;
shoppyou_free(&temp);
return 1;
}
}
if (fflush(temp.file) == 0) {
shoppyou->file = NULL;
shoppyou_free(shoppyou);
*shoppyou = temp;
return 0;
}
temp.file = NULL;
shoppyou_free(&temp);
return 1;
}
int shoppyou_save(Shoppyou *shoppyou) {
if (shoppyou->file == NULL ||
(shoppyou->file = freopen(NULL, "wb", shoppyou->file)) == NULL ||
fwrite(SHOPPYOU_SIG, 2, 4, shoppyou->file) != 4 ||
fwrite(&shoppyou->created_ts, 8, 1, shoppyou->file) != 1 ||
fwrite(&shoppyou->modified_ts, 8, 1, shoppyou->file) != 1) {
return 1;
}
uint64_t size = shoppyou->size - shoppyou->hole_cnt;
if (fwrite(&size, 8, 1, shoppyou->file) != 1 ||
fflush(shoppyou->file) != 0) {
return 1;
}
Kazari *current_kazari = shoppyou->database;
for (uint64_t i = 0; i < shoppyou->size; i++, current_kazari++) {
if (shoppyou->database[i].sasa_id != HOLE_ID && shoppyou->database[i].tanzaku_id != HOLE_ID) {
if (fwrite(&current_kazari->created_ts, 8, 1, shoppyou->file) != 1 ||
fwrite(&current_kazari->sasa_id, 8, 1, shoppyou->file) != 1 ||
fwrite(&current_kazari->tanzaku_id, 8, 1, shoppyou->file) != 1) {
return 1;
}
}
}
return fflush(shoppyou->file);
}
int shoppyou_open(Shoppyou *shoppyou, const char *path) {
if (path == NULL) {
return 1;
}
if (shoppyou->file == NULL && (shoppyou->file = fopen(path, "rb")) == NULL ||
shoppyou->file != NULL && (shoppyou->file = freopen(path, "rb", shoppyou->file)) == NULL) {
return 1;
}
return shoppyou_load(shoppyou);
}
int shoppyou_dump(Shoppyou *shoppyou, const char *path) {
if (path == NULL) {
return 1;
}
if (shoppyou->file == NULL && (shoppyou->file = fopen(path, "wb")) == NULL ||
shoppyou->file != NULL && (shoppyou->file = freopen(path, "wb", shoppyou->file)) == NULL) {
return 1;
}
return shoppyou_save(shoppyou);
}
int kazari_add(Shoppyou *shoppyou, uint64_t sasa_id, uint64_t tanzaku_id) {
if (sasa_id == HOLE_ID || tanzaku_id == HOLE_ID || shoppyou->size == -1 && shoppyou->hole_cnt == 0) {
return 1;
}
Kazari newbie;
newbie.created_ts = time(NULL);
newbie.sasa_id = sasa_id;
newbie.tanzaku_id = tanzaku_id;
if (shoppyou->hole_cnt > 0) {
shoppyou->hole_cnt--;
**(shoppyou->holes + shoppyou->hole_cnt) = newbie;
shoppyou->holes = reallocarray(shoppyou->holes, shoppyou->hole_cnt, sizeof(Kazari *));
} else {
shoppyou->size++;
shoppyou->database = reallocarray(shoppyou->database, shoppyou->size, sizeof(Kazari));
shoppyou->database[shoppyou->size - 1] = newbie;
}
shoppyou->modified_ts = newbie.created_ts;
return 0;
}
int kazari_rem(Shoppyou *shoppyou, uint64_t sasa_id, uint64_t tanzaku_id) {
if (sasa_id == HOLE_ID || tanzaku_id == HOLE_ID) {
return 1;
}
Kazari *current_kazari = shoppyou->database;
for (uint64_t i = 0; i < shoppyou->size; i++, current_kazari++) {
if (current_kazari->sasa_id == sasa_id && current_kazari->tanzaku_id == tanzaku_id) {
current_kazari->sasa_id = HOLE_ID;
current_kazari->tanzaku_id = HOLE_ID;
shoppyou->hole_cnt++;
shoppyou->holes = reallocarray(shoppyou->holes, shoppyou->hole_cnt, sizeof(Kazari *));
shoppyou->holes[shoppyou->hole_cnt - 1] = current_kazari;
shoppyou->modified_ts = time(NULL);
break;
}
}
return 0;
}
int kazari_rem_by_sasa(Shoppyou *shoppyou, uint64_t sasa_id) {
if (sasa_id == HOLE_ID) {
return 1;
}
Kazari *current_kazari = shoppyou->database;
_Bool changed = 0;
for (uint64_t i = 0; i < shoppyou->size; i++, current_kazari++) {
if (current_kazari->sasa_id == sasa_id) {
current_kazari->sasa_id = HOLE_ID;
current_kazari->tanzaku_id = HOLE_ID;
shoppyou->hole_cnt++;
shoppyou->holes = reallocarray(shoppyou->holes, shoppyou->hole_cnt, sizeof(Kazari *));
shoppyou->holes[shoppyou->hole_cnt - 1] = current_kazari;
changed = 1;
}
}
if (changed) {
shoppyou->modified_ts = time(NULL);
}
return 0;
}
int kazari_rem_by_tanzaku(Shoppyou *shoppyou, uint64_t tanzaku_id) {
if (tanzaku_id == HOLE_ID) {
return 1;
}
Kazari *current_kazari = shoppyou->database;
_Bool changed = 0;
for (uint64_t i = 0; i < shoppyou->size; i++, current_kazari++) {
if (current_kazari->tanzaku_id == tanzaku_id) {
current_kazari->sasa_id = HOLE_ID;
current_kazari->tanzaku_id = HOLE_ID;
shoppyou->hole_cnt++;
shoppyou->holes = reallocarray(shoppyou->holes, shoppyou->hole_cnt, sizeof(Kazari *));
shoppyou->holes[shoppyou->hole_cnt - 1] = current_kazari;
changed = 1;
}
}
if (changed) {
shoppyou->modified_ts = time(NULL);
}
return 0;
}
-199
View File
@@ -1,199 +0,0 @@
#include <malloc.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include "../core/core_func.h"
#include "../../include/tanabata.h"
int tanabata_init(Tanabata *tanabata) {
if (sasahyou_init(&tanabata->sasahyou) != 0 ||
sappyou_init(&tanabata->sappyou) != 0 ||
shoppyou_init(&tanabata->shoppyou) != 0) {
return 1;
}
tanabata->sappyou.size = 1;
tanabata->sappyou.database = malloc(sizeof(Tanzaku));
tanabata->sappyou.database->id = 0;
tanabata->sappyou.database->created_ts = tanabata->sappyou.created_ts;
tanabata->sappyou.database->modified_ts = tanabata->sappyou.created_ts;
tanabata->sappyou.database->name = malloc(9);
tanabata->sappyou.database->description = malloc(30);
strcpy(tanabata->sappyou.database->name, "FAVORITE");
strcpy(tanabata->sappyou.database->description, "Special tanzaku for favorites");
tanabata->sasahyou_mod = 0;
tanabata->sappyou_mod = 0;
tanabata->shoppyou_mod = 0;
return 0;
}
int tanabata_free(Tanabata *tanabata) {
if (sasahyou_free(&tanabata->sasahyou) != 0 ||
sappyou_free(&tanabata->sappyou) != 0 ||
shoppyou_free(&tanabata->shoppyou) != 0) {
return 1;
}
tanabata->sasahyou_mod = 0;
tanabata->sappyou_mod = 0;
tanabata->shoppyou_mod = 0;
return 0;
}
int tanabata_weed(Tanabata *tanabata) {
uint64_t hole_cnt = 0, new_id;
Kazari *current_kazari;
Sasa *current_sasa = tanabata->sasahyou.database;
for (uint64_t i = 0; i < tanabata->sasahyou.size; i++, current_sasa++) {
if (current_sasa->id != HOLE_ID) {
if (hole_cnt > 0) {
new_id = current_sasa->id - hole_cnt;
for (current_kazari = tanabata->shoppyou.database + tanabata->shoppyou.size - 1;
current_kazari >= tanabata->shoppyou.database; current_kazari--) {
if (current_kazari->sasa_id == current_sasa->id) {
current_kazari->sasa_id = new_id;
}
}
current_sasa->id = new_id;
*(current_sasa - hole_cnt) = *current_sasa;
}
} else {
kazari_rem_by_sasa(&tanabata->shoppyou, current_sasa->id);
hole_cnt++;
}
}
if (hole_cnt > 0) {
tanabata->sasahyou.size -= hole_cnt;
tanabata->sasahyou.hole_cnt = 0;
free(tanabata->sasahyou.holes);
tanabata->sasahyou.holes = NULL;
tanabata->sasahyou.database = reallocarray(tanabata->sasahyou.database, tanabata->sasahyou.size,
sizeof(Sasa));
tanabata->sasahyou.modified_ts = time(NULL);
}
hole_cnt = 0;
Tanzaku *current_tanzaku = tanabata->sappyou.database;
for (uint64_t i = 0; i < tanabata->sappyou.size; i++, current_tanzaku++) {
if (current_tanzaku->id != HOLE_ID) {
if (hole_cnt > 0) {
new_id = current_tanzaku->id - hole_cnt;
for (current_kazari = tanabata->shoppyou.database + tanabata->shoppyou.size - 1;
current_kazari >= tanabata->shoppyou.database; current_kazari--) {
if (current_kazari->tanzaku_id == current_tanzaku->id) {
current_kazari->tanzaku_id = new_id;
}
}
current_tanzaku->id = new_id;
*(current_tanzaku - hole_cnt) = *current_tanzaku;
} else {
hole_cnt++;
}
}
}
if (hole_cnt > 0) {
tanabata->sappyou.size -= tanabata->sappyou.hole_cnt;
tanabata->sappyou.hole_cnt = 0;
free(tanabata->sappyou.holes);
tanabata->sappyou.holes = NULL;
tanabata->sappyou.database = reallocarray(tanabata->sappyou.database, tanabata->sappyou.size,
sizeof(Tanzaku));
tanabata->sappyou.modified_ts = time(NULL);
}
hole_cnt = 0;
current_kazari = tanabata->shoppyou.database;
for (uint64_t i = 0; i < tanabata->shoppyou.size; i++, current_kazari++) {
if (current_kazari->sasa_id != HOLE_ID && current_kazari->tanzaku_id != HOLE_ID &&
current_kazari->sasa_id < tanabata->sasahyou.size &&
current_kazari->tanzaku_id < tanabata->sappyou.size) {
if (hole_cnt > 0) {
*(current_kazari - hole_cnt) = *current_kazari;
}
} else {
hole_cnt++;
}
}
if (hole_cnt > 0) {
tanabata->shoppyou.size -= tanabata->shoppyou.hole_cnt;
tanabata->shoppyou.hole_cnt = 0;
free(tanabata->shoppyou.holes);
tanabata->shoppyou.holes = NULL;
tanabata->shoppyou.database = reallocarray(tanabata->shoppyou.database, tanabata->shoppyou.size,
sizeof(Kazari));
tanabata->shoppyou.modified_ts = time(NULL);
}
return 0;
}
int tanabata_load(Tanabata *tanabata) {
if (sasahyou_load(&tanabata->sasahyou) != 0 ||
sappyou_load(&tanabata->sappyou) != 0 ||
shoppyou_load(&tanabata->shoppyou) != 0) {
return 1;
}
tanabata->sasahyou_mod = tanabata->sasahyou.modified_ts;
tanabata->sappyou_mod = tanabata->sappyou.modified_ts;
tanabata->shoppyou_mod = tanabata->shoppyou.modified_ts;
return 0;
}
int tanabata_save(Tanabata *tanabata) {
if (tanabata->sasahyou_mod != tanabata->sasahyou.modified_ts && sasahyou_save(&tanabata->sasahyou) != 0 ||
tanabata->sappyou_mod != tanabata->sappyou.modified_ts && sappyou_save(&tanabata->sappyou) != 0 ||
tanabata->shoppyou_mod != tanabata->shoppyou.modified_ts && shoppyou_save(&tanabata->shoppyou) != 0) {
return 1;
}
tanabata->sasahyou_mod = tanabata->sasahyou.modified_ts;
tanabata->sappyou_mod = tanabata->sappyou.modified_ts;
tanabata->shoppyou_mod = tanabata->shoppyou.modified_ts;
return 0;
}
int tanabata_open(Tanabata *tanabata, const char *path) {
if (path == NULL) {
return 1;
}
struct stat st;
if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
return 1;
}
size_t pathlen = strlen(path);
char *file_path = malloc(pathlen + 10);
strcpy(file_path, path);
if (sasahyou_open(&tanabata->sasahyou, strcpy(file_path + pathlen, "/sasahyou") - pathlen) != 0 ||
sappyou_open(&tanabata->sappyou, strcpy(file_path + pathlen, "/sappyou") - pathlen) != 0 ||
shoppyou_open(&tanabata->shoppyou, strcpy(file_path + pathlen, "/shoppyou") - pathlen) != 0) {
free(file_path);
return 1;
}
free(file_path);
tanabata->sasahyou_mod = tanabata->sasahyou.modified_ts;
tanabata->sappyou_mod = tanabata->sappyou.modified_ts;
tanabata->shoppyou_mod = tanabata->shoppyou.modified_ts;
return 0;
}
int tanabata_dump(Tanabata *tanabata, const char *path) {
if (path == NULL) {
return 1;
}
struct stat st;
if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
return 1;
}
size_t pathlen = strlen(path);
char *file_path = malloc(pathlen + 10);
strcpy(file_path, path);
if (tanabata->sasahyou_mod != tanabata->sasahyou.modified_ts &&
sasahyou_dump(&tanabata->sasahyou, strcpy(file_path + pathlen, "/sasahyou") - pathlen) != 0 ||
tanabata->sappyou_mod != tanabata->sappyou.modified_ts &&
sappyou_dump(&tanabata->sappyou, strcpy(file_path + pathlen, "/sappyou") - pathlen) != 0 ||
tanabata->shoppyou_mod != tanabata->shoppyou.modified_ts &&
shoppyou_dump(&tanabata->shoppyou, strcpy(file_path + pathlen, "/shoppyou") - pathlen) != 0) {
free(file_path);
return 1;
}
free(file_path);
tanabata->sasahyou_mod = tanabata->sasahyou.modified_ts;
tanabata->sappyou_mod = tanabata->sappyou.modified_ts;
tanabata->shoppyou_mod = tanabata->shoppyou.modified_ts;
return 0;
}
-68
View File
@@ -1,68 +0,0 @@
#include <malloc.h>
#include "../core/core_func.h"
#include "../../include/tanabata.h"
int tanabata_kazari_add(Tanabata *tanabata, uint64_t sasa_id, uint64_t tanzaku_id) {
if (sasa_id >= tanabata->sasahyou.size || tanzaku_id >= tanabata->sappyou.size ||
tanabata->shoppyou.size == -1 && tanabata->shoppyou.hole_cnt == 0) {
return 1;
}
if (tanabata->shoppyou.size - tanabata->shoppyou.hole_cnt > 0) {
Kazari *current_kazari = tanabata->shoppyou.database + tanabata->shoppyou.size - 1;
for (; current_kazari >= tanabata->shoppyou.database; current_kazari--) {
if (current_kazari->sasa_id == sasa_id && current_kazari->tanzaku_id == tanzaku_id) {
return 1;
}
}
}
return kazari_add(&tanabata->shoppyou, sasa_id, tanzaku_id);
}
int tanabata_kazari_rem(Tanabata *tanabata, uint64_t sasa_id, uint64_t tanzaku_id) {
return kazari_rem(&tanabata->shoppyou, sasa_id, tanzaku_id);
}
Tanzaku *tanabata_tanzaku_get_by_sasa(Tanabata *tanabata, uint64_t sasa_id) {
if (sasa_id == HOLE_ID || sasa_id >= tanabata->sasahyou.size ||
tanabata->shoppyou.size - tanabata->shoppyou.hole_cnt == 0) {
return NULL;
}
Tanzaku *tanzaku_list = NULL;
uint64_t tanzaku_count = 0;
Tanzaku temp;
Kazari *current_kazari = tanabata->shoppyou.database;
for (uint64_t i = 0; i < tanabata->shoppyou.size; i++, current_kazari++) {
if (current_kazari->sasa_id == sasa_id &&
(temp = tanabata_tanzaku_get(tanabata, current_kazari->tanzaku_id)).id != HOLE_ID) {
tanzaku_count++;
tanzaku_list = reallocarray(tanzaku_list, tanzaku_count, sizeof(Tanzaku));
tanzaku_list[tanzaku_count - 1] = temp;
}
}
tanzaku_list = reallocarray(tanzaku_list, tanzaku_count + 1, sizeof(Tanzaku));
tanzaku_list[tanzaku_count] = HOLE_TANZAKU;
return tanzaku_list;
}
Sasa *tanabata_sasa_get_by_tanzaku(Tanabata *tanabata, uint64_t tanzaku_id) {
if (tanzaku_id == HOLE_ID || tanzaku_id >= tanabata->sappyou.size ||
tanabata->shoppyou.size - tanabata->shoppyou.hole_cnt == 0) {
return NULL;
}
Sasa *sasa_list = NULL;
uint64_t sasa_count = 0;
Sasa temp;
Kazari *current_kazari = tanabata->shoppyou.database;
for (uint64_t i = 0; i < tanabata->shoppyou.size; i++, current_kazari++) {
if (current_kazari->tanzaku_id == tanzaku_id &&
(temp = tanabata_sasa_get(tanabata, current_kazari->sasa_id)).id != HOLE_ID) {
sasa_count++;
sasa_list = reallocarray(sasa_list, sasa_count, sizeof(Sasa));
sasa_list[sasa_count - 1] = temp;
}
}
sasa_list = reallocarray(sasa_list, sasa_count + 1, sizeof(Sasa));
sasa_list[sasa_count] = HOLE_SASA;
return sasa_list;
}
-25
View File
@@ -1,25 +0,0 @@
#include "../core/core_func.h"
#include "../../include/tanabata.h"
Sasa tanabata_sasa_add(Tanabata *tanabata, const char *path) {
return sasa_add(&tanabata->sasahyou, path);
}
int tanabata_sasa_rem(Tanabata *tanabata, uint64_t sasa_id) {
if (sasa_rem(&tanabata->sasahyou, sasa_id) == 0 &&
kazari_rem_by_sasa(&tanabata->shoppyou, sasa_id) == 0) {
return 0;
}
return 1;
}
int tanabata_sasa_upd(Tanabata *tanabata, uint64_t sasa_id, const char *path) {
return sasa_upd(&tanabata->sasahyou, sasa_id, path);
}
Sasa tanabata_sasa_get(Tanabata *tanabata, uint64_t sasa_id) {
if (sasa_id == HOLE_ID || sasa_id >= tanabata->sasahyou.size) {
return HOLE_SASA;
}
return tanabata->sasahyou.database[sasa_id];
}
-25
View File
@@ -1,25 +0,0 @@
#include "../core/core_func.h"
#include "../../include/tanabata.h"
Tanzaku tanabata_tanzaku_add(Tanabata *tanabata, const char *name, const char *description) {
return tanzaku_add(&tanabata->sappyou, name, description);
}
int tanabata_tanzaku_rem(Tanabata *tanabata, uint64_t tanzaku_id) {
if (tanzaku_rem(&tanabata->sappyou, tanzaku_id) == 0 &&
kazari_rem_by_tanzaku(&tanabata->shoppyou, tanzaku_id) == 0) {
return 0;
}
return 1;
}
int tanabata_tanzaku_upd(Tanabata *tanabata, uint64_t tanzaku_id, const char *name, const char *description) {
return tanzaku_upd(&tanabata->sappyou, tanzaku_id, name, description);
}
Tanzaku tanabata_tanzaku_get(Tanabata *tanabata, uint64_t tanzaku_id) {
if (tanzaku_id == HOLE_ID || tanzaku_id >= tanabata->sappyou.size) {
return HOLE_TANZAKU;
}
return tanabata->sappyou.database[tanzaku_id];
}
-74
View File
@@ -1,74 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../../include/tdbms-client.h"
int main(int argc, char **argv) {
if (argc == 1 || strcmp(argv[1], "-h") == 0) {
printf("Tanabata Database Management client\n\n"
"Usage\n"
" tdb [DB_NAME [REQUEST_CODE [REQUEST_BODY]]]\n\n"
"Request codes:\n"
" 0\tDB stats\n"
" 3\tDB init\n"
" 2\tDB load\n"
" 4\tDB save\n"
" 6\tDB edit\n"
" 1\tDB remove soft\n"
" 5\tDB remove hard\n"
" 7\tDB weed\n"
" 16\tSasa get\n"
" 40\tSasa get by tanzaku\n"
" 18\tSasa add\n"
" 20\tSasa update\n"
" 17\tSasa remove\n"
" 32\tTanzaku get\n"
" 24\tTanzaku get by sasa\n"
" 34\tTanzaku add\n"
" 36\tTanzaku update\n"
" 33\tTanzaku remove\n"
" 8\tKazari get\n"
" 10\tKazari add\n"
" 26\tKazari add single sasa to multiple tanzaku\n"
" 42\tKazari add single tanzaku to multiple sasa\n"
" 9\tKazari remove\n"
" 25\tKazari remove single sasa to multiple tanzaku\n"
" 41\tKazari remove single tanzaku to multiple sasa\n");
return 0;
}
char *db_name, request_code, *request_body;
if (argc < 4) {
request_body = "";
} else {
request_body = argv[3];
}
if (argc < 3) {
request_code = 0;
} else {
char *endptr;
request_code = (char) strtol(argv[2], &endptr, 0);
if (*endptr != 0) {
fprintf(stderr, "FATAL: invalid request code '%s'\n", argv[2]);
return 1;
}
}
if (argc < 2) {
db_name = "";
} else {
db_name = argv[1];
}
int socket_fd = tdbms_connect("UNIX", "/tmp/tdbms.sock");
if (socket_fd < 0) {
fprintf(stderr, "FATAL: failed to connect to TDBMS server\n");
return 1;
}
char *response = tdb_query(socket_fd, db_name, request_code, request_body);
if (response == NULL) {
fprintf(stderr, "FATAL: failed to execute request\n");
return 1;
}
printf("%s\n", response);
tdbms_close(socket_fd);
return 0;
}
-90
View File
@@ -1,90 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "../../include/tdbms-client.h"
int tdbms_connect(const char *domain, const char *addr) {
int socket_fd;
struct sockaddr_un sockaddr;
int domain_code;
if (strcmp(domain, "UNIX") == 0) {
domain_code = AF_UNIX;
} else {
fprintf(stderr, "ERROR: unexpected socket domain '%s'\n", domain);
return -1;
}
if (strlen(addr) > sizeof(sockaddr.sun_path) - 1) {
fprintf(stderr, "ERROR: too long socket address\n");
return -1;
}
socket_fd = socket(domain_code, SOCK_STREAM, 0);
if (socket_fd < 0) {
fprintf(stderr, "ERROR: failed to initialize socket\n");
return -1;
}
bzero(&sockaddr, sizeof(sockaddr));
sockaddr.sun_family = domain_code;
strcpy(sockaddr.sun_path, addr);
if (connect(socket_fd, (const struct sockaddr *) &sockaddr, sizeof(sockaddr)) < 0) {
fprintf(stderr, "ERROR: failed to connect the socket\n");
return -1;
}
return socket_fd;
}
int tdbms_close(int socket_fd) {
return close(socket_fd);
}
char *tdb_query(int socket_fd, const char *db_name, char request_code, const char *request_body) {
if (socket_fd < 0 || db_name == NULL || request_body == NULL) {
return NULL;
}
size_t req_size = 1 + strlen(db_name) + 1 + strlen(request_body) + 1, resp_size;
ssize_t nread, nwrite;
char *request = malloc(req_size);
char *buffer = request;
*buffer = request_code;
buffer++;
strcpy(buffer, db_name);
buffer += strlen(db_name) + 1;
strcpy(buffer, request_body);
for (buffer = request; (nwrite = write(socket_fd, buffer, req_size)) > 0;) {
buffer += nwrite;
req_size -= nwrite;
if (req_size == 0) {
nwrite = write(socket_fd, "\4", 1);
break;
}
}
free(request);
if (nwrite <= 0) {
fprintf(stderr, "ERROR: failed to send request to server\n");
return NULL;
}
char *response = malloc(BUFSIZ);
resp_size = BUFSIZ;
buffer = malloc(BUFSIZ);
for (off_t offset = 0; (nread = read(socket_fd, buffer, BUFSIZ)) > 0;) {
if (offset + nread > resp_size) {
resp_size += BUFSIZ;
response = realloc(response, resp_size);
}
memcpy(response + offset, buffer, nread);
offset += nread;
if (response[offset - 1] == EOT) {
break;
}
}
free(buffer);
if (nread < 0) {
fprintf(stderr, "ERROR: failed to get server response\n");
free(response);
return NULL;
}
return response;
}
-92
View File
@@ -1,92 +0,0 @@
#!/bin/bash
# This script performs the installation of the Tanabata DBMS server
if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi
cd "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
getent group tanabata &>/dev/null || groupadd -g 42776 tanabata
id tanabata &>/dev/null || useradd -u 42776 -g 42776 tanabata
if [ ! "$(id -nG 42776 | grep -w tanabata)" ]; then
echo "FATAL: failed to create user and group 'tanabata'"
exit 1
fi
if [ ! -d /etc/tanabata ]; then
mkdir /etc/tanabata
if [ ! -d /etc/tanabata ]; then
echo "FATAL: failed to create directory '/etc/tanabata'"
exit 1
fi
fi
chown 42776:42776 /etc/tanabata
chmod 2755 /etc/tanabata
if [ ! -d /var/lib/tanabata ]; then
mkdir /var/lib/tanabata
if [ ! -d /var/lib/tanabata ]; then
echo "FATAL: failed to create directory '/var/lib/tanabata'"
exit 1
fi
fi
chown 42776:42776 /var/lib/tanabata
chmod 2755 /var/lib/tanabata
if [ ! -d /var/lib/tanabata/tdbms ]; then
mkdir /var/lib/tanabata/tdbms
if [ ! -d /var/lib/tanabata/tdbms ]; then
echo "FATAL: failed to create directory '/var/lib/tanabata/tdbms'"
exit 1
fi
fi
chown 42776:42776 /var/lib/tanabata/tdbms
chmod 2755 /var/lib/tanabata/tdbms
if [ ! -d /var/log/tanabata ]; then
mkdir /var/log/tanabata
if [ ! -d /var/log/tanabata ]; then
echo "FATAL: failed to create directory '/var/log/tanabata'"
exit 1
fi
fi
chown 42776:42776 /var/log/tanabata
chmod 2775 /var/log/tanabata
if [ -d ../build ]; then
rm -r ../build/*
else
mkdir ../build
if [ -d ../build ]; then
echo "FATAL: failed to create build directory"
exit 1
fi
fi
if ! (cmake -S .. -B ../build && cmake --build ../build --target tdbms); then
echo "FATAL: failed to build TDBMS server"
exit 1
fi
mv -f ../build/tdbms /usr/bin/
chown 0:0 /usr/bin/tdbms
chmod 0755 /usr/bin/tdbms
if ! cp ./tdbms.service /etc/systemd/system/; then
echo "FATAL: failed to copy 'tdbms.service' to '/etc/systemd/system'"
exit 1
fi
chown 0:0 /etc/systemd/system/tdbms.service
chmod 0644 /etc/systemd/system/tdbms.service
if ! (cmake -S .. -B ../build && cmake --build ../build --target tdb); then
echo "FATAL: failed to build TDB CLI client"
exit 1
fi
mv -f ../build/tdb /usr/bin/
chown 42776 /usr/bin/tdb
chmod 4755 /usr/bin/tdb
echo "TDBMS server successfully installed."
echo "Start it with 'systemctl start tdbms'"
File diff suppressed because it is too large Load Diff
-14
View File
@@ -1,14 +0,0 @@
[Unit]
Description=Tanabata Database Management System service
After=network.target
AssertPathIsDirectory=/var/lib/tanabata/tdbms
AssertPathIsDirectory=/var/log/tanabata
[Service]
Type=simple
Restart=no
User=tanabata
ExecStart=/usr/bin/tdbms
[Install]
WantedBy=multi-user.target
-609
View File
@@ -1,609 +0,0 @@
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include "../../include/tanabata.h"
// TFM configuration directory
#define TFM_CONFIG_DIR "/etc/tfm/"
// Stylization macros
#define TABLE_HEADER(s) ""s""
#define HIGHLIGHT(s) ""s""
#define SUCCESS(s) ""s""
#define ERROR(s) ""s""
#define DT_FORMAT "%F %T"
static Tanabata tanabata;
// Print the list of all sasa
void print_sasa_all() {
printf(TABLE_HEADER(" Sasa ID\tFile path")"\n");
for (uint64_t i = 0; i < tanabata.sasahyou.size; i++) {
if (tanabata.sasahyou.database[i].id != HOLE_ID) {
printf("%16lx\t%s\n", tanabata.sasahyou.database[i].id, tanabata.sasahyou.database[i].path);
}
}
}
// Print the list of all tanzaku
void print_tanzaku_all() {
printf(TABLE_HEADER(" Tanzaku ID\tName")"\n");
for (uint64_t i = 0; i < tanabata.sappyou.size; i++) {
if (tanabata.sappyou.database[i].id != HOLE_ID) {
printf("%16lx\t%s\n", tanabata.sappyou.database[i].id, tanabata.sappyou.database[i].name);
}
}
}
// Sasa view menu handler
int menu_view_sasa(const char *arg) {
if (arg == NULL) {
return 1;
}
if (strcmp(arg, ".") == 0) {
print_sasa_all();
return 0;
}
char *endptr;
uint64_t sasa_id = strtoull(arg, &endptr, 16);
if (*endptr == 0) {
Sasa current_sasa = tanabata_sasa_get(&tanabata, sasa_id);
if (current_sasa.id != HOLE_ID) {
char datetime[20];
strftime(datetime, 20, DT_FORMAT,
localtime((const time_t *) &current_sasa.created_ts));
printf(HIGHLIGHT("Sasa ID")" %lx\n"
HIGHLIGHT("File path")" %s\n"
HIGHLIGHT("Added datetime")" %s\n\n",
sasa_id, current_sasa.path, datetime);
Tanzaku *related_tanzaku = tanabata_tanzaku_get_by_sasa(&tanabata, current_sasa.id);
if (related_tanzaku != NULL) {
printf(HIGHLIGHT("↓ Related tanzaku ↓")"\n"
HIGHLIGHT(" Tanzaku ID\tName")"\n");
for (Tanzaku *current_tanzaku = related_tanzaku;
current_tanzaku->id != HOLE_ID; current_tanzaku++) {
printf("%16lx\t%s\n", current_tanzaku->id, current_tanzaku->name);
}
printf(HIGHLIGHT("↑ Related tanzaku ↑")"\n");
} else {
printf(HIGHLIGHT("No related tanzaku")"\n");
}
return 0;
}
fprintf(stderr, ERROR("No sasa with this ID")"\n");
return 1;
}
fprintf(stderr, ERROR("Invalid ID")"\n");
return 1;
}
// Tanzaku view menu handler
int menu_view_tanzaku(const char *arg) {
if (arg == NULL) {
return 1;
}
if (strcmp(arg, ".") == 0) {
print_tanzaku_all();
return 0;
}
char *endptr;
uint64_t tanzaku_id = strtoull(arg, &endptr, 16);
if (*endptr == 0) {
Tanzaku current_tanzaku = tanabata_tanzaku_get(&tanabata, tanzaku_id);
if (current_tanzaku.id != HOLE_ID) {
char datetime[20];
strftime(datetime, 20, DT_FORMAT,
localtime((const time_t *) &current_tanzaku.created_ts));
printf(HIGHLIGHT("Tanzaku ID")" %lx\n"
HIGHLIGHT("Name")" %s\n"
HIGHLIGHT("Created datetime")" %s\n",
tanzaku_id, current_tanzaku.name, datetime);
strftime(datetime, 20, DT_FORMAT,
localtime((const time_t *) &current_tanzaku.modified_ts));
printf(HIGHLIGHT("Modified datetime")" %s\n\n", datetime);
if (*current_tanzaku.description != 0) {
printf(HIGHLIGHT("↓ Description ↓")"\n"
"%s\n"
HIGHLIGHT("↑ Description ↑")"\n\n", current_tanzaku.description);
} else {
printf(HIGHLIGHT("No description")"\n\n");
}
Sasa *related_sasa = tanabata_sasa_get_by_tanzaku(&tanabata, tanzaku_id);
if (related_sasa != NULL) {
printf(HIGHLIGHT("↓ Related sasa ↓")"\n"
HIGHLIGHT(" Sasa ID\tFile path")"\n");
for (Sasa *current_sasa = related_sasa;
current_sasa->id != HOLE_ID; current_sasa++) {
printf("%16lx\t%s\n", current_sasa->id, current_sasa->path);
}
printf(HIGHLIGHT("↑ Related sasa ↑")"\n");
} else {
printf(HIGHLIGHT("No related sasa")"\n");
}
return 0;
}
fprintf(stderr, ERROR("No tanzaku with this ID")"\n");
return 1;
}
fprintf(stderr, ERROR("Invalid ID")"\n");
return 1;
}
// Sasa add menu handler
int menu_add_sasa(const char *arg) {
if (arg == NULL) {
return 1;
}
if (tanabata.sasahyou.size == -1 && tanabata.sasahyou.hole_cnt == 0) {
fprintf(stderr, ERROR("Failed to add file to database: sasahyou is full")"\n");
return 1;
}
if (tanabata_sasa_add(&tanabata, arg) == 0 &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully added file to database")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to add file to database")"\n");
return 1;
}
// Tanzaku add menu handler
int menu_add_tanzaku(const char *arg) {
if (arg == NULL) {
return 1;
}
if (tanabata.sappyou.size == -1 && tanabata.sappyou.hole_cnt == 0) {
fprintf(stderr, ERROR("Failed to add tanzaku: sappyou is full")"\n");
return 1;
}
if (*arg != 0) {
char description[4096];
printf(HIGHLIGHT("Enter tanzaku description:")"\n");
fgets(description, 4096, stdin);
description[strlen(description) - 1] = 0;
if (tanabata_tanzaku_add(&tanabata, arg, description) == 0 &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully added tanzaku to database")"\n");
return 0;
}
}
fprintf(stderr, ERROR("Failed to add tanzaku to database")"\n");
return 1;
}
// Kazari add menu handler
int menu_add_kazari(char *arg) {
if (arg == NULL) {
return 1;
}
if (tanabata.shoppyou.size == -1 && tanabata.shoppyou.hole_cnt == 0) {
fprintf(stderr, ERROR("Failed to add kazari: shoppyou is full")"\n");
return 1;
}
char *left = arg, *right = "\0", *endptr;
for (size_t i = 0; i < strlen(arg); i++) {
if (arg[i] == '-') {
arg[i] = 0;
right = arg + i + 1;
break;
}
}
if (*left == 0 || *right == 0) {
fprintf(stderr, ERROR("Failed to add kazari: invalid argument")"\n");
return 1;
}
uint64_t sasa_id = strtoull(left, &endptr, 16);
if (*endptr != 0) {
fprintf(stderr, ERROR("Failed to add kazari: invalid sasa ID")"\n");
return 1;
}
uint64_t tanzaku_id = strtoull(right, &endptr, 16);
if (*endptr != 0) {
fprintf(stderr, ERROR("Failed to add kazari: invalid tanzaku ID")"\n");
return 1;
}
if (tanabata_kazari_add(&tanabata, sasa_id, tanzaku_id) == 0 &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully added kazari")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to add kazari")"\n");
return 1;
}
// Sasa remove menu handler
int menu_rem_sasa(const char *arg) {
if (arg == NULL) {
return 1;
}
char *endptr;
uint64_t sasa_id = strtoull(arg, &endptr, 16);
if (*endptr == 0) {
if (tanabata_sasa_rem(&tanabata, sasa_id) == 0 &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully removed sasa")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to remove sasa")"\n");
return 1;
}
fprintf(stderr, ERROR("Invalid ID")"\n");
return 1;
}
// Tanzaku remove menu handler
int menu_rem_tanzaku(const char *arg) {
if (arg == NULL) {
return 1;
}
char *endptr;
uint64_t tanzaku_id = strtoull(arg, &endptr, 16);
if (*endptr == 0) {
if (tanabata_tanzaku_rem(&tanabata, tanzaku_id) == 0 &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully removed tanzaku")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to remove tanzaku")"\n");
return 1;
}
fprintf(stderr, ERROR("Invalid ID")"\n");
return 1;
}
// Kazari remove menu handler
int menu_rem_kazari(char *arg) {
if (arg == NULL) {
return 1;
}
char *left = arg, *right = "\0", *endptr;
for (size_t i = 0; i < strlen(arg); i++) {
if (arg[i] == '-') {
arg[i] = 0;
right = arg + i + 1;
break;
}
}
if (*left == 0 || *right == 0) {
fprintf(stderr, ERROR("Failed to remove kazari: invalid argument")"\n");
return 1;
}
uint64_t sasa_id = strtoull(left, &endptr, 16);
if (*endptr != 0) {
fprintf(stderr, ERROR("Failed to remove kazari: invalid sasa ID")"\n");
return 1;
}
uint64_t tanzaku_id = strtoull(right, &endptr, 16);
if (*endptr != 0) {
fprintf(stderr, ERROR("Failed to remove kazari: invalid tanzaku ID")"\n");
return 1;
}
if (tanabata_kazari_rem(&tanabata, sasa_id, tanzaku_id) == 0 &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully removed kazari")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to remove kazari")"\n");
return 1;
}
// Sasa update menu handler
int menu_upd_sasa(const char *arg) {
if (arg == NULL) {
return 1;
}
char *endptr;
uint64_t sasa_id = strtoull(arg, &endptr, 16);
if (*endptr == 0) {
char *path = malloc(4096);
printf(HIGHLIGHT("Enter the new file path (leave blank to keep current):")"\n");
fgets(path, 4096, stdin);
if (*path == '\n') {
free(path);
path = NULL;
} else {
path[strlen(path) - 1] = 0;
}
if (tanabata_sasa_upd(&tanabata, sasa_id, path) == 0 &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully updated sasa")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to update sasa")"\n");
return 1;
}
fprintf(stderr, ERROR("Invalid ID")"\n");
return 1;
}
// Tanzaku update menu handler
int menu_upd_tanzaku(const char *arg) {
if (arg == NULL) {
return 1;
}
char *endptr;
uint64_t tanzaku_id = strtoull(arg, &endptr, 16);
if (*endptr == 0) {
char *name = malloc(4096), *description = malloc(4096);
printf(HIGHLIGHT("Enter the new name of tanzaku (leave blank to keep current):")"\n");
fgets(name, 4096, stdin);
if (*name == '\n') {
free(name);
name = NULL;
} else {
name[strlen(name) - 1] = 0;
}
printf(HIGHLIGHT("Enter the new description of tanzaku (leave blank to keep current):")"\n");
fgets(description, 4096, stdin);
if (*description == '\n') {
free(description);
description = NULL;
} else {
description[strlen(description) - 1] = 0;
}
if (tanabata_tanzaku_upd(&tanabata, tanzaku_id, name, description) == 0 &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully updated tanzaku")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to update tanzaku")"\n");
return 1;
}
fprintf(stderr, ERROR("Invalid ID")"\n");
return 1;
}
int main(int argc, char **argv) {
if (argc == 1) {
fprintf(stderr, ERROR("No options provided")"\n");
return 1;
}
char *tanabata_path;
FILE *config = fopen(TFM_CONFIG_DIR"/config", "r");
if (config == NULL) {
tanabata_path = NULL;
struct stat st;
if (stat(TFM_CONFIG_DIR, &st) == -1) {
if (mkdir(TFM_CONFIG_DIR, 0755) != 0) {
fprintf(stderr, ERROR("Failed to create %s directory. "
"Try again with 'sudo' or check your permissions")"\n", TFM_CONFIG_DIR);
return 1;
}
}
config = fopen(TFM_CONFIG_DIR"/config", "w");
if (config == NULL) {
fprintf(stderr, ERROR("Failed to create config file. "
"Try again with 'sudo' or check your permissions")"\n");
return 1;
}
} else {
fseek(config, 0L, SEEK_END);
long fsize = ftell(config);
rewind(config);
if (fsize == 0) {
tanabata_path = NULL;
} else {
tanabata_path = malloc(fsize + 1);
if (fgets(tanabata_path, INT32_MAX, config) == NULL) {
fprintf(stderr, ERROR("Failed to read config file")"\n");
return 1;
}
}
}
const char *shortopts = "hI:O:isuef:t:c:wV";
char *abspath = NULL;
int opt;
_Bool opt_i = 0;
_Bool opt_s = 0;
_Bool opt_u = 0;
_Bool opt_e = 0;
_Bool opt_f = 0;
_Bool opt_t = 0;
_Bool opt_c = 0;
_Bool opt_w = 0;
char *opt_f_arg;
char *opt_t_arg;
char *opt_c_arg;
while ((opt = getopt(argc, argv, shortopts)) != -1) {
switch (opt) {
case 'h':
printf(
HIGHLIGHT("(C) Masahiko AMANO aka H1K0, 2022—present")"\n"
HIGHLIGHT("(https://github.com/H1K0/tanabata)")"\n\n"
HIGHLIGHT("Usage:")"\n"
"tfm <options>\n\n"
HIGHLIGHT("Options:")"\n"
HIGHLIGHT("-h")" Print this help and exit\n"
HIGHLIGHT("-I <dir>")" Initialize new Tanabata database in directory <dir>\n"
HIGHLIGHT("-O <dir>")" Open existing Tanabata database from directory <dir>\n"
HIGHLIGHT("-i")" View database info\n"
HIGHLIGHT("-s")" Set or add\n"
HIGHLIGHT("-u")" Unset or remove\n"
HIGHLIGHT("-e")" Edit or update\n"
HIGHLIGHT("-f <sasa_id or path>")" File-sasa menu\n"
HIGHLIGHT("-t <tanzaku_id or name>")" Tanzaku menu\n"
HIGHLIGHT("-c <sasa_id>-<tanzaku_id>")" Kazari menu "
"(can only be used with the '-s' or '-u' option)\n"
HIGHLIGHT("-w")" Weed (defragment) database\n"
HIGHLIGHT("-V")" Print version and exit\n\n"
);
if (tanabata_path != NULL) {
printf(HIGHLIGHT("Current database location: %s")"\n", tanabata_path);
} else {
printf(HIGHLIGHT("No database connected")"\n");
}
return 0;
case 'V':
printf("1.0.0\n");
return 0;
case 'I':
abspath = realpath(optarg, abspath);
if (abspath == NULL) {
fprintf(stderr, ERROR("Invalid path")"\n");
return 1;
}
if (tanabata_init(&tanabata) == 0 &&
tanabata_dump(&tanabata, abspath) == 0) {
config = freopen(NULL, "w", config);
if (config == NULL) {
fprintf(stderr, ERROR("Failed to update config file. "
"Try again with 'sudo' or check your permissions")"\n");
return 1;
}
fputs(abspath, config);
fclose(config);
printf(SUCCESS("Successfully initialized Tanabata database")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to initialize Tanabata database")"\n");
return 1;
case 'O':
abspath = realpath(optarg, abspath);
if (abspath == NULL) {
fprintf(stderr, ERROR("Invalid path")"\n");
return 1;
}
if (tanabata_open(&tanabata, abspath) == 0) {
config = freopen(NULL, "w", config);
if (config == NULL) {
fprintf(stderr, ERROR("Failed to update config file. "
"Try again with 'sudo' or check your permissions")"\n");
return 1;
}
fputs(abspath, config);
fclose(config);
printf(SUCCESS("Successfully opened Tanabata database")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to open Tanabata database")"\n");
return 1;
case 'i':
opt_i = 1;
break;
case 's':
opt_s = 1;
break;
case 'u':
opt_u = 1;
break;
case 'e':
opt_e = 1;
break;
case 'f':
opt_f = 1;
opt_f_arg = optarg;
break;
case 't':
opt_t = 1;
opt_t_arg = optarg;
break;
case 'c':
opt_c = 1;
opt_c_arg = optarg;
break;
case 'w':
opt_w = 1;
break;
case '?':
return 1;
default:
break;
}
}
if (tanabata_path == NULL) {
fprintf(stderr, ERROR("No connected database")"\n");
return 1;
}
if (tanabata_open(&tanabata, tanabata_path) != 0) {
fprintf(stderr, ERROR("Failed to load database")"\n");
return 1;
}
fclose(config);
if (opt_i) {
char datetime[20];
printf(HIGHLIGHT("Current database location: %s")"\n\n"
HIGHLIGHT("SASAHYOU")"\n", tanabata_path);
strftime(datetime, 20, DT_FORMAT,
localtime((const time_t *) &tanabata.sasahyou.created_ts));
printf(" "HIGHLIGHT("Created")" %s\n", datetime);
strftime(datetime, 20, DT_FORMAT,
localtime((const time_t *) &tanabata.sasahyou.modified_ts));
printf(" "HIGHLIGHT("Last modified")" %s\n"
" "HIGHLIGHT("Number of sasa")" %lu\n"
" "HIGHLIGHT("Number of holes")" %lu\n\n"
HIGHLIGHT("SAPPYOU")"\n", datetime, tanabata.sasahyou.size, tanabata.sasahyou.hole_cnt);
strftime(datetime, 20, DT_FORMAT,
localtime((const time_t *) &tanabata.sappyou.created_ts));
printf(" "HIGHLIGHT("Created")" %s\n", datetime);
strftime(datetime, 20, DT_FORMAT,
localtime((const time_t *) &tanabata.sappyou.modified_ts));
printf(" "HIGHLIGHT("Last modified")" %s\n"
" "HIGHLIGHT("Number of tanzaku")" %lu\n"
" "HIGHLIGHT("Number of holes")" %lu\n\n"
HIGHLIGHT("SHOPPYOU")"\n", datetime, tanabata.sappyou.size, tanabata.sappyou.hole_cnt);
strftime(datetime, 20, DT_FORMAT,
localtime((const time_t *) &tanabata.shoppyou.created_ts));
printf(" "HIGHLIGHT("Created")" %s\n", datetime);
strftime(datetime, 20, DT_FORMAT,
localtime((const time_t *) &tanabata.shoppyou.modified_ts));
printf(" "HIGHLIGHT("Last modified")" %s\n"
" "HIGHLIGHT("Number of kazari")" %lu\n"
" "HIGHLIGHT("Number of holes")" %lu\n",
datetime, tanabata.shoppyou.size, tanabata.shoppyou.hole_cnt);
return 0;
}
free(tanabata_path);
if (opt_w) {
if (tanabata_weed(&tanabata) == 0 &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully weeded database")"\n");
return 0;
}
fprintf(stderr, ERROR("Failed to weed database")"\n");
return 1;
}
if (opt_s && opt_u) {
opt_s = 0;
opt_u = 0;
}
if (opt_s) {
if (opt_f) {
return menu_add_sasa(opt_f_arg);
}
if (opt_t) {
return menu_add_tanzaku(opt_t_arg);
}
if (opt_c) {
return menu_add_kazari(opt_c_arg);
}
} else if (opt_u) {
if (opt_f) {
return menu_rem_sasa(opt_f_arg);
}
if (opt_t) {
return menu_rem_tanzaku(opt_t_arg);
}
if (opt_c) {
return menu_rem_kazari(opt_c_arg);
}
} else if (opt_e) {
if (opt_f) {
return menu_upd_sasa(opt_f_arg);
}
if (opt_t) {
return menu_upd_tanzaku(opt_t_arg);
}
} else {
if (opt_f) {
return menu_view_sasa(opt_f_arg);
}
if (opt_t) {
return menu_view_tanzaku(opt_t_arg);
}
}
return 0;
}
-59
View File
@@ -1,59 +0,0 @@
#!/bin/bash
# This script performs the installation of Tanabata web server
if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi
cd "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
../tdbms/install.sh || exit 1
usermod -a -G tanabata www-data
if [ ! -d /var/lib/tanabata/tweb ]; then
mkdir /var/lib/tanabata/tweb
if [ ! -d /var/lib/tanabata/tweb ]; then
echo "FATAL: failed to create directory '/var/lib/tanabata/tweb'"
exit 1
fi
fi
chown 42776:42776 /var/lib/tanabata/tweb
chmod 2755 /var/lib/tanabata/tweb
if [ -d ../build ]; then
rm -r ../build/*
else
mkdir ../build
if [ -d ../build ]; then
echo "FATAL: failed to create build directory"
exit 1
fi
fi
cd ./server
echo "Building Tweb server..."
if ! go build -o ../build; then
echo "FATAL: failed to build Tweb server"
exit 1
fi
cd ..
mv -f ../build/tweb /usr/bin/
chown 0:0 /usr/bin/tweb
chmod 0755 /usr/bin/tweb
if ! cp ./tweb.service /etc/systemd/system/; then
echo "FATAL: failed to copy 'tweb.service' to '/etc/systemd/system'"
exit 1
fi
chown 0:0 /etc/systemd/system/tweb.service
chmod 0644 /etc/systemd/system/tweb.service
if ! cp -r ./public/* /srv/www/tanabata/; then
echo "FATAL: failed to copy public files to '/srv/www/tanabata'"
exit 1
fi
echo "Tweb server successfully installed."
echo "Start it with 'systemctl start tweb'"
-51
View File
@@ -1,51 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Authentication | Tanabata</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<link rel="stylesheet" href="/css/auth.css">
<script src="/js/jquery-3.6.0.min.js"></script>
</head>
<body>
<header>
<h1>Welcome to Tanabata!</h1>
</header>
<main>
<div class="contents-wrapper">
<form id="auth">
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" class="form-control" maxlength="32" placeholder="Password">
<div class="invalid-feedback">Invalid password!</div>
<div class="valid-feedback">Authorization success!</div>
</div>
<div class="form-group button-flex">
<button type="submit" class="btn btn-primary" id="submit">Submit</button>
<a href="/" class="btn btn-secondary">Back to home</a>
</div>
</form>
</div>
</main>
<script src="js/auth.js"></script>
</body>
</html>
-2
View File
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/images/ms-icon-70x70.png"/><square150x150logo src="/images/ms-icon-150x150.png"/><square310x310logo src="/images/ms-icon-310x310.png"/><TileColor>#5c913b</TileColor></tile></msapplication></browserconfig>
-3
View File
@@ -1,3 +0,0 @@
.btn-secondary {
display: none;
}
File diff suppressed because one or more lines are too long
-151
View File
@@ -1,151 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Epilogue&family=Secular+One&display=swap');
html,
body {
width: 100%;
min-height: 100vh;
margin: 0;
padding: 10px;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #eee;
background-color: #2d2b40;
background-image: url(/images/bg-1920x1440-dark.webp);
background-size: 100% auto;
background-repeat: no-repeat;
}
header {
margin: 0;
margin-top: 2vw;
padding: 0;
text-align: center;
}
h1 {
margin: 12px 0;
padding: 0;
color: inherit;
font-family: Epilogue, sans-serif;
font-size: 10vmin;
text-shadow: 0 0 8px #555;
text-align: center;
cursor: default;
}
h1 a {
color: inherit;
text-decoration: inherit;
}
h1 a:hover {
color: inherit;
text-decoration: inherit;
}
main {
margin: 0;
display: flex;
flex-direction: column;
background-color: #3348;
box-shadow: 0 0 0.5vw black;
border-radius: 16px;
width: 80vw;
max-width: 700px;
transition: 0.3s;
overflow: hidden;
}
main:hover {
background-color: #334;
box-shadow: 0 0 1vw black;
}
.contents-wrapper {
margin: 0;
padding: 2vw 2vw;
flex: 1 1 auto;
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: space-around;
flex-wrap: wrap;
}
h2 {
margin: 0;
padding: 14px 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: hsla(270, 30%, 60%, 0.6);
border-bottom: 0;
color: #111;
font-family: Secular One, sans-serif;
font-size: 5.5vmin;
text-shadow: 2.5px 2px 0.5px #ddd;
text-align: center;
cursor: default;
}
h3 {
margin: 0;
margin-top: 0.5vw;
font-family: Secular One, sans-serif;
font-size: 3vw;
}
form {
margin: 0;
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.form-select {
display: block;
width: 100%;
padding: .375rem 2.25rem .375rem .75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none
}
.form-control,
.form-control:focus {
color: #ddd !important;
background-color: #445;
}
.form-control::placeholder,
.form-control::-webkit-input-placeholder {
color: #bbb !important;
}
td {
vertical-align: top;
}
.button-flex {
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
row-gap: 10px;
}
-46
View File
@@ -1,46 +0,0 @@
html,
body {
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
}
main {
position: relative;
width: 100%;
height: 100%;
max-width: 100vw;
background-color: #2c3034;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
main:hover {
background-color: #2c3034;
}
.contents-wrapper {
padding: 0;
width: 100%;
height: 100%;
box-shadow: inset -5px 5px 5px #1111, inset -5px -5px 5px #1111;
overflow-y: scroll;
overflow-x: hidden;
}
.contents-wrapper:after {
content: "";
flex: auto;
}
.button-flex {
position: sticky;
position: -webkit-sticky;
bottom: 0;
left: 0;
right: 0;
margin: 0;
padding-top: .5rem;
padding-bottom: .8rem;
background-color: #334;
}
-194
View File
@@ -1,194 +0,0 @@
html,
body {
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
}
main {
position: relative;
width: 100%;
height: 100%;
max-width: 100vw;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.contents-wrapper {
width: 100%;
height: 100%;
justify-content: space-between;
align-content: flex-start;
align-items: flex-start;
box-shadow: inset -5px 5px 5px #1111, inset -5px -5px 5px #1111;
overflow-y: scroll;
overflow-x: hidden;
}
.contents-wrapper:after {
content: "";
flex: auto;
}
.menu-wrapper {
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 0;
background-color: #0008;
}
.menu {
margin: 0;
width: 100%;
max-width: 120vmin;
display: flex;
flex-direction: column;
justify-content: flex-start;
background-color: #334;
box-shadow: 0 0 0.5vw black;
border-radius: 0;
height: 100%;
transition: 0.3s;
overflow-x: hidden;
overflow-y: scroll;
}
.preview {
position: relative;
width: 100%;
}
#preview {
width: 100%;
max-height: 60vh;
object-fit: contain;
object-position: center;
}
.file-nav-btn {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
#file-prev {
right: 50%;
}
#file-next {
left: 50%;
}
form {
flex: 1 1 auto;
padding: 2vw 2vw;
}
.form-group.row {
margin-left: 0;
margin-right: 0;
}
.col-form-label {
flex: 0 0 auto;
margin-right: 10px;
}
.col-form-input {
flex: 1 1 auto;
min-width: 200px;
}
.list {
flex: 1 1 auto;
height: 50vh;
display: flex;
flex-direction: row;
justify-content: space-between;
align-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
padding: 8px 0;
box-shadow: inset -5px 5px 5px #1111, inset -5px -5px 5px #1111;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
overflow-x: hidden;
}
.list:after {
content: "";
flex: auto;
}
.sasa {
position: relative;
margin: 8px;
padding: 0;
width: 160px;
height: 160px;
border-radius: 20px;
overflow: hidden;
cursor: pointer;
}
.sasa .thumb {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.sasa .overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #0002;
}
.sasa:hover .overlay {
background-color: #0004;
}
.sasa.selected .overlay {
background-color: #0006;
}
.tanzaku {
margin: 5px;
padding: 7px;
border: 1px solid #555;
border-radius: 7px;
cursor: default;
}
.tanzaku:hover,
.tanzaku.selected:hover {
background-color: #0006;
}
.tanzaku.selected {
background-color: #0004;
}
.button-flex {
position: sticky;
position: -webkit-sticky;
bottom: 0;
left: 0;
right: 0;
margin: 0;
padding-top: .5rem;
padding-bottom: .8rem;
background-color: #334;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 KiB

-39
View File
@@ -1,39 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home | Tanabata</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<script src="/js/jquery-3.6.0.min.js"></script>
</head>
<body>
<h1>Welcome to Tanabata!</h1>
<main>
<h2>Select Tanabata service</h2>
<div class="contents-wrapper button-flex">
<a href="/auth" class="btn btn-primary">Authorize</a>
<a href="/tfm" class="btn btn-primary">File Manager</a>
<a href="/tdbms" class="btn btn-primary">Database Management</a>
</div>
</main>
</body>
</html>
-37
View File
@@ -1,37 +0,0 @@
$("#auth").on("submit", function submit(e) {
e.preventDefault();
var input_password = $("#password");
let password = input_password.val();
input_password.val("");
$.ajax({
url: "/AUTH",
type: "POST",
contentType: "text/plain",
data: password,
dataType: "json",
success: function (resp) {
if (resp.status) {
input_password.removeClass("is-invalid");
input_password.addClass("is-valid");
$(".btn-secondary").css("display", "block");
} else {
input_password.removeClass("is-valid");
input_password.addClass("is-invalid");
}
},
failure: function (err) {
alert(err);
}
});
});
$(document).keyup(function (e) {
switch (e.key) {
case "Esc":
case "Escape":
location.href = "/";
break;
default:
return;
}
});
-8
View File
@@ -1,8 +0,0 @@
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e("object"==typeof exports?require("jquery"):jQuery)}(function(e){var i=/\+/g;function o(e){return t.raw?e:encodeURIComponent(e)}function r(e){return t.raw?e:decodeURIComponent(e)}function n(o,r){var n=t.raw?o:function e(o){0===o.indexOf('"')&&(o=o.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return o=decodeURIComponent(o.replace(i," ")),t.json?JSON.parse(o):o}catch(r){}}(o);return e.isFunction(r)?r(n):n}var t=e.cookie=function(i,c,u){if(void 0!==c&&!e.isFunction(c)){if("number"==typeof(u=e.extend({},t.defaults,u)).expires){var a,s=u.expires,f=u.expires=new Date;f.setTime(+f+864e5*s)}return document.cookie=[o(i),"=",(a=c,o(t.json?JSON.stringify(a):String(a))),u.expires?"; expires="+u.expires.toUTCString():"",u.path?"; path="+u.path:"",u.domain?"; domain="+u.domain:"",u.secure?"; secure":""].join("")}for(var p=i?void 0:{},d=document.cookie?document.cookie.split("; "):[],v=0,x=d.length;v<x;v++){var k=d[v].split("="),l=r(k.shift()),j=k.join("=");if(i&&i===l){p=n(j,c);break}i||void 0===(j=n(j))||(p[l]=j)}return p};t.defaults={},e.removeCookie=function(i,o){return void 0!==e.cookie(i)&&(e.cookie(i,"",e.extend({},o,{expires:-1})),!e.cookie(i))}});
-2
View File
@@ -1,2 +0,0 @@
/*! jQuery & Zepto Lazy v1.7.10 - http://jquery.eisbehr.de/lazy - MIT&GPL-2.0 license - Copyright 2012-2018 Daniel 'Eisbehr' Kern */
!function(t,e){"use strict";function r(r,a,i,u,l){function f(){L=t.devicePixelRatio>1,i=c(i),a.delay>=0&&setTimeout(function(){s(!0)},a.delay),(a.delay<0||a.combined)&&(u.e=v(a.throttle,function(t){"resize"===t.type&&(w=B=-1),s(t.all)}),u.a=function(t){t=c(t),i.push.apply(i,t)},u.g=function(){return i=n(i).filter(function(){return!n(this).data(a.loadedName)})},u.f=function(t){for(var e=0;e<t.length;e++){var r=i.filter(function(){return this===t[e]});r.length&&s(!1,r)}},s(),n(a.appendScroll).on("scroll."+l+" resize."+l,u.e))}function c(t){var i=a.defaultImage,o=a.placeholder,u=a.imageBase,l=a.srcsetAttribute,f=a.loaderAttribute,c=a._f||{};t=n(t).filter(function(){var t=n(this),r=m(this);return!t.data(a.handledName)&&(t.attr(a.attribute)||t.attr(l)||t.attr(f)||c[r]!==e)}).data("plugin_"+a.name,r);for(var s=0,d=t.length;s<d;s++){var A=n(t[s]),g=m(t[s]),h=A.attr(a.imageBaseAttribute)||u;g===N&&h&&A.attr(l)&&A.attr(l,b(A.attr(l),h)),c[g]===e||A.attr(f)||A.attr(f,c[g]),g===N&&i&&!A.attr(E)?A.attr(E,i):g===N||!o||A.css(O)&&"none"!==A.css(O)||A.css(O,"url('"+o+"')")}return t}function s(t,e){if(!i.length)return void(a.autoDestroy&&r.destroy());for(var o=e||i,u=!1,l=a.imageBase||"",f=a.srcsetAttribute,c=a.handledName,s=0;s<o.length;s++)if(t||e||A(o[s])){var g=n(o[s]),h=m(o[s]),b=g.attr(a.attribute),v=g.attr(a.imageBaseAttribute)||l,p=g.attr(a.loaderAttribute);g.data(c)||a.visibleOnly&&!g.is(":visible")||!((b||g.attr(f))&&(h===N&&(v+b!==g.attr(E)||g.attr(f)!==g.attr(F))||h!==N&&v+b!==g.css(O))||p)||(u=!0,g.data(c,!0),d(g,h,v,p))}u&&(i=n(i).filter(function(){return!n(this).data(c)}))}function d(t,e,r,i){++z;var o=function(){y("onError",t),p(),o=n.noop};y("beforeLoad",t);var u=a.attribute,l=a.srcsetAttribute,f=a.sizesAttribute,c=a.retinaAttribute,s=a.removeAttribute,d=a.loadedName,A=t.attr(c);if(i){var g=function(){s&&t.removeAttr(a.loaderAttribute),t.data(d,!0),y(T,t),setTimeout(p,1),g=n.noop};t.off(I).one(I,o).one(D,g),y(i,t,function(e){e?(t.off(D),g()):(t.off(I),o())})||t.trigger(I)}else{var h=n(new Image);h.one(I,o).one(D,function(){t.hide(),e===N?t.attr(C,h.attr(C)).attr(F,h.attr(F)).attr(E,h.attr(E)):t.css(O,"url('"+h.attr(E)+"')"),t[a.effect](a.effectTime),s&&(t.removeAttr(u+" "+l+" "+c+" "+a.imageBaseAttribute),f!==C&&t.removeAttr(f)),t.data(d,!0),y(T,t),h.remove(),p()});var m=(L&&A?A:t.attr(u))||"";h.attr(C,t.attr(f)).attr(F,t.attr(l)).attr(E,m?r+m:null),h.complete&&h.trigger(D)}}function A(t){var e=t.getBoundingClientRect(),r=a.scrollDirection,n=a.threshold,i=h()+n>e.top&&-n<e.bottom,o=g()+n>e.left&&-n<e.right;return"vertical"===r?i:"horizontal"===r?o:i&&o}function g(){return w>=0?w:w=n(t).width()}function h(){return B>=0?B:B=n(t).height()}function m(t){return t.tagName.toLowerCase()}function b(t,e){if(e){var r=t.split(",");t="";for(var a=0,n=r.length;a<n;a++)t+=e+r[a].trim()+(a!==n-1?",":"")}return t}function v(t,e){var n,i=0;return function(o,u){function l(){i=+new Date,e.call(r,o)}var f=+new Date-i;n&&clearTimeout(n),f>t||!a.enableThrottle||u?l():n=setTimeout(l,t-f)}}function p(){--z,i.length||z||y("onFinishedAll")}function y(t,e,n){return!!(t=a[t])&&(t.apply(r,[].slice.call(arguments,1)),!0)}var z=0,w=-1,B=-1,L=!1,T="afterLoad",D="load",I="error",N="img",E="src",F="srcset",C="sizes",O="background-image";"event"===a.bind||o?f():n(t).on(D+"."+l,f)}function a(a,o){var u=this,l=n.extend({},u.config,o),f={},c=l.name+"-"+ ++i;return u.config=function(t,r){return r===e?l[t]:(l[t]=r,u)},u.addItems=function(t){return f.a&&f.a("string"===n.type(t)?n(t):t),u},u.getItems=function(){return f.g?f.g():{}},u.update=function(t){return f.e&&f.e({},!t),u},u.force=function(t){return f.f&&f.f("string"===n.type(t)?n(t):t),u},u.loadAll=function(){return f.e&&f.e({all:!0},!0),u},u.destroy=function(){return n(l.appendScroll).off("."+c,f.e),n(t).off("."+c),f={},e},r(u,l,a,f,c),l.chainable?a:u}var n=t.jQuery||t.Zepto,i=0,o=!1;n.fn.Lazy=n.fn.lazy=function(t){return new a(this,t)},n.Lazy=n.lazy=function(t,r,i){if(n.isFunction(r)&&(i=r,r=[]),n.isFunction(i)){t=n.isArray(t)?t:[t],r=n.isArray(r)?r:[r];for(var o=a.prototype.config,u=o._f||(o._f={}),l=0,f=t.length;l<f;l++)(o[t[l]]===e||n.isFunction(o[t[l]]))&&(o[t[l]]=i);for(var c=0,s=r.length;c<s;c++)u[r[c]]=t[0]}},a.prototype.config={name:"lazy",chainable:!0,autoDestroy:!0,bind:"load",threshold:500,visibleOnly:!1,appendScroll:t,scrollDirection:"both",imageBase:null,defaultImage:"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==",placeholder:null,delay:-1,combined:!1,attribute:"data-src",srcsetAttribute:"data-srcset",sizesAttribute:"data-sizes",retinaAttribute:"data-retina",loaderAttribute:"data-loader",imageBaseAttribute:"data-imagebase",removeAttribute:!0,handledName:"handled",loadedName:"loaded",effect:"show",effectTime:0,enableThrottle:!0,throttle:250,beforeLoad:e,afterLoad:e,onError:e,onFinishedAll:e},n(t).on("load",function(){o=!0})}(window);
-52
View File
@@ -1,52 +0,0 @@
$(document).on("click", "#btn-save", function (e) {
e.preventDefault();
if (db_name == null) {
return;
}
let resp = tdb_query(db_name, 4);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
alert("Successfully saved!");
});
$(document).on("click", "#btn-reload", function (e) {
e.preventDefault();
if (db_name == null) {
return;
}
if (!confirm("All unsaved changes will be lost permanently. Are you sure?")) {
return;
}
let resp = tdb_query(db_name, 2);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
localStorage["sasahyou_mts"] = sasahyou_mts = 0;
localStorage["sappyou_mts"] = sappyou_mts = 0;
localStorage["shoppyou_mts"] = shoppyou_mts = 0;
alert("Successfully reloaded database!");
});
$(document).on("click", "#btn-remove", function (e) {
e.preventDefault();
if (db_name == null) {
return;
}
if (!confirm(`Are you sure want to remove database "${db_name}"?`)) {
return;
}
let resp = tdb_query(db_name, 1);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
localStorage.removeItem("db_name");
db_name = null;
localStorage["sasahyou_mts"] = sasahyou_mts = 0;
localStorage["sappyou_mts"] = sappyou_mts = 0;
localStorage["shoppyou_mts"] = shoppyou_mts = 0;
alert("Successfully removed database!");
});
-7
View File
@@ -1,7 +0,0 @@
var db_name = localStorage["db_name"];
$(window).on("load", function (e) {
if (db_name != null) {
$(".db_name").text(db_name);
}
});
-8
View File
@@ -1,8 +0,0 @@
db_name = localStorage["db_name"];
if (db_name == null) {
location.href = "/tdbms/settings";
}
$(window).on("load", function (e) {
$(".db_name").text(db_name);
});
-20
View File
@@ -1,20 +0,0 @@
$(document).on("submit", "#newdb", function (e) {
e.preventDefault();
let newdb_name = $("#newdb-name").val(), newdb_path = $("#newdb-path").val();
let resp = tdb_query(newdb_name, 3);
if (resp == null || !resp.status) {
alert("Failed to initialize database!");
return;
}
resp = tdb_query(newdb_name, 4, newdb_path);
if (resp == null || !resp.status) {
alert("Failed to save database!");
return;
}
resp = tdb_query(newdb_name, 6, "path=" + newdb_path);
if (resp == null || !resp.status) {
alert("Failed to finalize database!");
return;
}
alert("Successfully added database!");
});
-9
View File
@@ -1,9 +0,0 @@
$(window).on("load", function (e) {
sappyou_load();
sappyou.every(tanzaku => {
$("#content").append(
`<tr><td>${tanzaku.id}</td><td>${new Date(tanzaku.cts * 1000).toLocaleDateString()} ${new Date(tanzaku.cts * 1000).toLocaleTimeString()}</td><td>${new Date(tanzaku.mts * 1000).toLocaleDateString()} ${new Date(tanzaku.mts * 1000).toLocaleTimeString()}</td><td>${tanzaku.name}</td><td>${tanzaku.desc}</td></tr>`
);
return true;
});
});
-9
View File
@@ -1,9 +0,0 @@
$(window).on("load", function (e) {
sasahyou_load();
sasahyou.every(sasa => {
$("#content").append(
`<tr><td>${sasa.id}</td><td>${new Date(sasa.cts * 1000).toLocaleDateString()} ${new Date(sasa.cts * 1000).toLocaleTimeString()}</td><td>${sasa.path}</td></tr>`
);
return true;
});
});
-71
View File
@@ -1,71 +0,0 @@
function settings_load() {
if (db_name != null) {
$(`#db_name option[value="${db_name}"]`).prop("selected", true);
} else {
$("#db_name option[value=\"\"]").prop("selected", true);
}
if (sort_sasa != null) {
let sort_s = sort_sasa;
if (sort_s[0] === '!') {
sort_s = sort_s.slice(1);
}
if (sort_s[0] === '-') {
$("#sasa-reverse").prop("checked", true);
sort_s = sort_s.slice(1);
}
$(`#sasa-by-${sort_s}`).prop("checked", true);
}
if (sort_tanzaku != null) {
let sort_t = sort_tanzaku;
if (sort_t[0] === '!') {
sort_t = sort_t.slice(1);
}
if (sort_t[0] === '-') {
$("#tanzaku-reverse").prop("checked", true);
sort_t = sort_t.slice(1);
}
$(`#tanzaku-by-${sort_t}`).prop("checked", true);
}
}
$(window).on("load", function () {
let resp = tdb_query();
if (resp == null || !resp.status) {
alert("Failed to fetch databases");
throw new Error("Failed to fetch databases");
}
resp.data.every(tdb => {
$("#db_name").append($("<option>", {
value: tdb.name,
text: tdb.name
}));
return true;
});
settings_load();
});
$(document).on("reset", "#settings", function (e) {
e.preventDefault();
settings_load();
});
$(document).on("submit", "#settings", function (e) {
e.preventDefault();
let db_name_input = $("#db_name");
let db_name_val = db_name_input.val();
if (db_name_val !== db_name) {
localStorage["db_name"] = db_name = db_name_val;
localStorage["sasahyou_mts"] = sasahyou_mts = 0;
localStorage["sappyou_mts"] = sappyou_mts = 0;
localStorage["shoppyou_mts"] = shoppyou_mts = 0;
}
let sort_s = ($("#sasa-reverse")[0].checked ? '-' : '') + $("input[type=radio][name=sort-sasa]:checked").attr("id").slice(8);
let sort_t = ($("#tanzaku-reverse")[0].checked ? '-' : '') + $("input[type=radio][name=sort-tanzaku]:checked").attr("id").slice(11);
if (sort_s !== sort_sasa && '!' + sort_s !== sort_sasa) {
localStorage["sort_sasa"] = sort_sasa = '!' + sort_s;
}
if (sort_t !== sort_tanzaku && '!' + sort_t !== sort_tanzaku) {
localStorage["sort_tanzaku"] = sort_tanzaku = '!' + sort_t;
}
alert("Successfully updated settings!");
});
-9
View File
@@ -1,9 +0,0 @@
$(window).on("load", function (e) {
shoppyou_load();
shoppyou.every(kazari => {
$("#content").append(
`<tr><td>${new Date(kazari.cts * 1000).toLocaleDateString()} ${new Date(kazari.cts * 1000).toLocaleTimeString()}</td><td>${kazari.sasa_id}</td><td>${kazari.tanzaku_id}</td></tr>`
);
return true;
});
});
-16
View File
@@ -1,16 +0,0 @@
$(window).on("load", function (e) {
let resp = tdb_query(db_name);
if (resp == null || !resp.status) {
alert("Failed to fetch database");
throw new Error("Failed to fetch database");
}
$("#stats-sasahyou").append(
`<tr><td>${new Date(resp.data[0].sasahyou.cts * 1000).toLocaleDateString()} ${new Date(resp.data[0].sasahyou.cts * 1000).toLocaleTimeString()}</td><td>${new Date(resp.data[0].sasahyou.mts * 1000).toLocaleDateString()} ${new Date(resp.data[0].sasahyou.mts * 1000).toLocaleTimeString()}</td><td>${resp.data[0].sasahyou.size}</td><td>${resp.data[0].sasahyou.holes}</td></tr>`
);
$("#stats-sappyou").append(
`<tr><td>${new Date(resp.data[0].sappyou.cts * 1000).toLocaleDateString()} ${new Date(resp.data[0].sappyou.cts * 1000).toLocaleTimeString()}</td><td>${new Date(resp.data[0].sappyou.mts * 1000).toLocaleDateString()} ${new Date(resp.data[0].sappyou.mts * 1000).toLocaleTimeString()}</td><td>${resp.data[0].sappyou.size}</td><td>${resp.data[0].sappyou.holes}</td></tr>`
);
$("#stats-shoppyou").append(
`<tr><td>${new Date(resp.data[0].shoppyou.cts * 1000).toLocaleDateString()} ${new Date(resp.data[0].shoppyou.cts * 1000).toLocaleTimeString()}</td><td>${new Date(resp.data[0].shoppyou.mts * 1000).toLocaleDateString()} ${new Date(resp.data[0].shoppyou.mts * 1000).toLocaleTimeString()}</td><td>${resp.data[0].shoppyou.size}</td><td>${resp.data[0].shoppyou.holes}</td></tr>`
);
});
-204
View File
@@ -1,204 +0,0 @@
var db_name = null;
var sasahyou = localStorage["sasahyou"],
sappyou = localStorage["sappyou"],
shoppyou = localStorage["shoppyou"];
var sort_sasa = localStorage["sort_sasa"],
sort_tanzaku = localStorage["sort_tanzaku"];
if (sasahyou != null) {
sasahyou = JSON.parse(sasahyou);
}
if (sappyou != null) {
sappyou = JSON.parse(sappyou);
}
if (shoppyou != null) {
shoppyou = JSON.parse(shoppyou);
}
var sasahyou_mts = localStorage["sasahyou_mts"],
sappyou_mts = localStorage["sappyou_mts"],
shoppyou_mts = localStorage["shoppyou_mts"];
if (sasahyou_mts != null) {
sasahyou_mts = parseInt(sasahyou_mts);
}
if (sappyou_mts != null) {
sappyou_mts = parseInt(sappyou_mts);
}
if (shoppyou_mts != null) {
shoppyou_mts = parseInt(shoppyou_mts);
}
if (sort_sasa == null) {
localStorage["sort_sasa"] = sort_sasa = "id";
}
if (sort_tanzaku == null) {
localStorage["sort_tanzaku"] = sort_tanzaku = "id";
}
function tdb_query(trdb, trc, trb) {
if (trb == null) {
trb = "";
}
if (trc == null) {
trc = 0;
}
if (trdb == null) {
trdb = "";
}
let output = null;
$.ajax({
url: "/TDBMS",
type: "POST",
contentType: "application/json",
data: `{"trdb":${JSON.stringify(trdb)},"trc":${trc},"trb":${JSON.stringify(trb)}}`,
dataType: "json",
async: false,
statusCode: {
401: function () {
location.href = "/auth";
throw new Error("Unauthorized TDBMS request");
}
},
success: function (resp) {
output = resp;
},
failure: function (err) {
alert(err);
}
});
return output;
}
function sasahyou_load() {
let db_info = tdb_query(db_name);
if (db_info == null || !db_info.status) {
alert("Failed to fetch database");
throw new Error("Failed to fetch database");
}
if (sasahyou == null || sasahyou_mts !== db_info.data[0].sasahyou.mts) {
let resp = tdb_query(db_name, 16);
if (resp == null || !resp.status) {
alert("Failed to get sasahyou");
throw new Error("Failed to get sasahyou");
}
sasahyou = resp.data;
localStorage["sasahyou_mts"] = sasahyou_mts = db_info.data[0].sasahyou.mts;
localStorage["sasahyou"] = JSON.stringify(sasahyou);
if (sort_sasa[0] !== '!') {
sort_sasa = '!' + sort_sasa;
}
}
sasahyou_sort();
}
function sappyou_load() {
let db_info = tdb_query(db_name);
if (db_info == null || !db_info.status) {
alert("Failed to fetch database");
throw new Error("Failed to fetch database");
}
if (sappyou == null || sappyou_mts !== db_info.data[0].sappyou.mts) {
let resp = tdb_query(db_name, 32);
if (resp == null || !resp.status) {
alert("Failed to get sappyou");
throw new Error("Failed to get sappyou");
}
sappyou = resp.data;
localStorage["sappyou_mts"] = sappyou_mts = db_info.data[0].sappyou.mts;
localStorage["sappyou"] = JSON.stringify(sappyou);
if (sort_tanzaku[0] !== '!') {
sort_tanzaku = '!' + sort_tanzaku;
}
}
sappyou_sort();
}
function shoppyou_load() {
let db_info = tdb_query(db_name);
if (db_info == null || !db_info.status) {
alert("Failed to fetch database");
throw new Error("Failed to fetch database");
}
if (shoppyou == null || shoppyou_mts !== db_info.data[0].shoppyou.mts) {
let resp = tdb_query(db_name, 8);
if (resp == null || !resp.status) {
alert("Failed to get shoppyou");
throw new Error("Failed to get shoppyou");
}
shoppyou = resp.data;
localStorage["shoppyou_mts"] = shoppyou_mts = db_info.data[0].shoppyou.mts;
localStorage["shoppyou"] = JSON.stringify(shoppyou);
}
}
function sasahyou_sort() {
if (sort_sasa[0] !== '!') {
return;
}
let sort = localStorage["sort_sasa"] = sort_sasa = sort_sasa.slice(1);
let order = 1;
if (sort[0] === '-') {
order = -1;
sort = sort.slice(1);
}
sasahyou.sort((lhs, rhs) => {
let l = lhs[sort], r = rhs[sort];
if (l > r) {
return order;
}
if (l < r) {
return -order;
}
return 0;
});
localStorage["sasahyou"] = JSON.stringify(sasahyou);
}
function sappyou_sort() {
if (sort_tanzaku[0] !== '!') {
return;
}
let sort = localStorage["sort_tanzaku"] = sort_tanzaku = sort_tanzaku.slice(1);
let order = 1;
if (sort[0] === '-') {
order = -1;
sort = sort.slice(1);
}
if (sort === "nkazari") {
shoppyou_load();
shoppyou.every(kazari => {
sappyou.every((tanzaku, index) => {
if (tanzaku.id === kazari.tanzaku_id) {
if (tanzaku.nkazari == null) {
sappyou[index].nkazari = 1;
} else {
sappyou[index].nkazari++;
}
return false;
}
return true;
});
return true;
});
sappyou.every((tanzaku, index) => {
if (tanzaku.nkazari == null) {
sappyou[index].nkazari = 0;
}
return true;
});
}
sappyou.sort((lhs, rhs) => {
if (lhs.id === 0) {
return -1;
}
if (rhs.id === 0) {
return 1;
}
let l = lhs[sort], r = rhs[sort];
if (l > r) {
return order;
}
if (l < r) {
return -order;
}
return 0;
});
localStorage["sappyou"] = JSON.stringify(sappyou);
}
-36
View File
@@ -1,36 +0,0 @@
var db_name = localStorage["tfm_db_name"];
if (db_name == null) {
location.href = "/tfm/settings";
}
$(document).on("click", "#btn-save", function (e) {
e.preventDefault();
if (db_name == null) {
return;
}
let resp = tdb_query(db_name, 4);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
alert("Successfully saved!");
});
$(document).on("click", "#btn-reload", function (e) {
e.preventDefault();
if (db_name == null) {
return;
}
if (!confirm("All unsaved changes will be lost permanently. Are you sure?")) {
return;
}
let resp = tdb_query(db_name, 2);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
localStorage["sasahyou_mts"] = sasahyou_mts = 0;
localStorage["sappyou_mts"] = sappyou_mts = 0;
localStorage["shoppyou_mts"] = shoppyou_mts = 0;
alert("Successfully reloaded database!");
});
-30
View File
@@ -1,30 +0,0 @@
$(window).on("load", function () {
$(function () {
$(".thumb").Lazy({
scrollDirection: "vertical",
effect: "fadeIn",
visibleOnly: true,
appendScroll: $(".contents-wrapper")[0],
});
});
sasahyou_load();
sasahyou.forEach((sasa) => {
$(".contents-wrapper").append(`<div class="item sasa" sid="${sasa.id}" title="${sasa.path.split('/').slice(-1)}"><img class="thumb" data-src="${"/thumbs/" + sasa.path}"><div class="overlay"></div></div>`);
$("#menu-tag-view .list").append(`<div class="list-item sasa" sid="${sasa.id}" title="${sasa.path.split('/').slice(-1)}"><img class="thumb" data-src="${"/thumbs/" + sasa.path}"><div class="overlay"></div></div>`);
});
sappyou_load();
sappyou.forEach((tanzaku) => {
$("#menu-file-view .list").append(`<div class="list-item tanzaku" tid="${tanzaku.id}">${tanzaku.name}</div>`);
});
});
$(document).on("submit", "#menu-add form", function (e) {
e.preventDefault();
let resp = tdb_query(db_name, 18, $("#new-name").val());
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
menu_add_close();
location.reload(true);
});
-369
View File
@@ -1,369 +0,0 @@
db_name = localStorage["tfm_db_name"];
if (db_name == null) {
location.href = "/tfm/settings";
}
sort_sasa = localStorage["sort_files"];
sort_tanzaku = localStorage["sort_tags"];
if (sort_sasa == null) {
localStorage["sort_files"] = sort_sasa = "id";
}
if (sort_tanzaku == null) {
localStorage["sort_tags"] = sort_tanzaku = "id";
}
var current_sasa = null, current_tanzaku = null;
var current_sasa_index = -1;
var menu_count = 0;
function menu_view_file_open() {
if (menu_count > 1) {
return;
}
menu_count++;
$("#menu-file-view .selected").removeClass("selected");
$("#menu-file-view").css("display", "flex");
$("#preview").attr("src", "/preview/" + current_sasa.path);
$("#file-name").val(decodeURI(current_sasa.path));
$("#menu-file-view .list-item").css("display", "");
$("#btn-full").attr("href", "/files/" + current_sasa.path);
let resp = tdb_query(db_name, 24, '' + current_sasa.id);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
resp.data.forEach(tanzaku => {
$(`.list-item[tid="${tanzaku.id}"]`).addClass("selected");
});
if ($("#file-selection-filter")[0].checked) {
$("#menu-file-view .list-item:not(.selected)").css("display", "none");
} else {
$("#menu-file-view .list-item:not(.selected)").css("display", "block");
}
}
function menu_view_tag_open() {
if (menu_count > 1) {
return;
}
menu_count++;
$(function () {
$("#menu-tag-view .thumb").Lazy({
scrollDirection: "vertical",
effect: "fadeIn",
visibleOnly: true,
appendScroll: $("#menu-tag-view .list")[0],
});
});
$("#menu-tag-view .selected").removeClass("selected");
$("#menu-tag-view").css("display", "flex");
$("#menu-tag-view .list-item").css("display", "");
$("#tag-name").val(decodeURI(current_tanzaku.name));
$("#description").val(current_tanzaku.desc);
let resp = tdb_query(db_name, 40, '' + current_tanzaku.id);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
resp.data.forEach(sasa => {
$(`.list-item[sid="${sasa.id}"]`).addClass("selected");
});
if ($("#tag-selection-filter")[0].checked) {
$("#menu-tag-view .list-item:not(.selected)").css("display", "none");
} else {
$("#menu-tag-view .list-item:not(.selected)").css("display", "block");
}
}
function menu_view_file_close() {
menu_count--;
$("#menu-file-view").css("display", "none");
$("#menu-file-view .list-item").removeClass("selected").css("display", "");
$("#file-name").val("");
$("#text-filter").val("");
current_sasa_index = -1;
}
function menu_view_tag_close() {
menu_count--;
$("#menu-tag-view").css("display", "none");
$("#menu-tag-view .list-item").removeClass("selected").css("display", "");
$("#tag-name").val("");
$("#description").val("");
}
function menu_add_open() {
$(".menu-wrapper").css("display", "flex");
$("#menu-add").css("display", "flex");
}
function menu_add_close() {
$(".menu-wrapper").css("display", "none");
$("#menu-add").css("display", "none");
$("#new-name").val("");
$("#new-description").val("");
}
function file_next() {
if (current_sasa_index === sasahyou.length - 1) {
menu_view_file_close();
return;
}
current_sasa_index++;
current_sasa = sasahyou[current_sasa_index];
menu_count--;
menu_view_file_open();
}
function file_prev() {
if (current_sasa_index === 0) {
menu_view_file_close();
return;
}
current_sasa_index--;
current_sasa = sasahyou[current_sasa_index];
menu_count--;
menu_view_file_open();
}
$(document).keyup(function (e) {
switch (e.key) {
case "Esc":
case "Escape":
$(".selected").removeClass("selected");
break;
case "Left":
case "ArrowLeft":
if (current_sasa_index >= 0) {
file_prev();
}
break;
case "Right":
case "ArrowRight":
if (current_sasa_index >= 0) {
file_next();
}
break;
default:
return;
}
});
$(document).on("selectstart", ".sasa,.tanzaku", function (e) {
e.preventDefault();
});
$(document).on("click", ".item", function (e) {
let wasSelected = $(this).hasClass("selected");
if (!e.ctrlKey) {
$(".item.selected").removeClass("selected");
}
if (wasSelected) {
$(this).removeClass("selected");
} else {
$(this).addClass("selected");
}
});
$(document).on("dblclick", ".sasa", function (e) {
e.preventDefault();
let id = parseInt($(this).attr("sid"));
current_sasa_index = 0;
sasahyou.every(sasa => {
if (sasa.id === id) {
current_sasa = sasa;
return false;
}
current_sasa_index++;
return true;
});
menu_view_file_open();
});
$(document).on("dblclick", ".tanzaku", function (e) {
e.preventDefault();
let id = parseInt($(this).attr("tid"));
sappyou.every(tanzaku => {
if (tanzaku.id === id) {
current_tanzaku = tanzaku;
return false;
}
return true;
});
menu_view_tag_open();
});
$(document).on("click", "#btn-new", function (e) {
e.preventDefault();
menu_add_open();
});
$(document).on("click", ".list-item", function (e) {
if ($(this).hasClass("selected")) {
$(this).removeClass("selected");
} else {
$(this).addClass("selected");
}
});
$(document).on("click", "#file-selection-filter", function (e) {
let notselected = $("#menu-file-view .list-item:not(.selected)");
if (this.checked) {
notselected.css("display", "none");
} else {
notselected.css("display", "block");
}
});
$(document).on("click", "#tag-selection-filter", function (e) {
let notselected = $("#menu-tag-view .list-item:not(.selected)");
if (this.checked) {
notselected.css("display", "none");
} else {
notselected.css("display", "block");
}
});
$(document).on("input", "#text-filter", function (e) {
let filter = $(this).val().toLowerCase();
let unfiltered;
if ($("#file-selection-filter")[0].checked) {
unfiltered = $(".list-item.selected");
} else {
unfiltered = $(".list-item");
}
if (filter === "") {
unfiltered.css("display", "");
return;
}
unfiltered.each((index, element) => {
let current = $(element);
if (current.text().toLowerCase().includes(filter)) {
current.css("display", "");
} else {
current.css("display", "none");
}
});
});
$(document).on("reset", "#menu-file-view form", function (e) {
e.preventDefault();
menu_view_file_close();
});
$(document).on("reset", "#menu-tag-view form", function (e) {
e.preventDefault();
menu_view_tag_close();
});
$(document).on("reset", "#menu-add form", function (e) {
e.preventDefault();
menu_add_close();
});
$(document).on("submit", "#menu-file-view form", function (e) {
e.preventDefault();
let resp = tdb_query(db_name, 24, '' + current_sasa.id);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
let toadd = "", toremove = "";
resp.data.forEach(tanzaku => {
let current = $(`.list-item[tid="${tanzaku.id}"]`);
if (!current.hasClass("selected")) {
toremove += ' ' + tanzaku.id;
}
});
$(".list-item.tanzaku.selected").each(function (index, element) {
let tid = parseInt($(element).attr("tid"));
if (resp.data.find(t => t.id === tid) == null) {
toadd += ' ' + tid;
}
});
let status = true;
if (toadd !== "") {
resp = tdb_query(db_name, 26, '' + current_sasa.id + toadd);
status = (resp != null && resp.status);
}
if (toremove !== "") {
resp = tdb_query(db_name, 25, '' + current_sasa.id + toremove);
status = (resp != null && resp.status);
}
if (status) {
alert("Saved changes!");
} else {
alert("Something went wrong!");
}
});
$(document).on("submit", "#menu-tag-view form", function (e) {
e.preventDefault();
let resp;
let name = $("#tag-name").val(),
desc = $("#description").val();
if (name !== current_tanzaku.name || desc !== current_tanzaku.desc) {
resp = tdb_query(db_name, 36, '' + current_tanzaku.id + ' ' + name + '\n' + desc);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
current_tanzaku.name = name;
current_tanzaku.desc = desc;
$(`.tanzaku[tid=${current_tanzaku.id}]`).text(name);
}
resp = tdb_query(db_name, 40, '' + current_tanzaku.id);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
let toadd = "", toremove = "";
resp.data.forEach(sasa => {
let current = $(`.list-item[sid="${sasa.id}"]`);
if (!current.hasClass("selected")) {
toremove += ' ' + sasa.id;
}
});
$(".list-item.sasa.selected").each(function (index, element) {
let sid = parseInt($(element).attr("sid"));
if (resp.data.find(s => s.id === sid) == null) {
toadd += ' ' + sid;
}
});
let status = true;
if (toadd !== "") {
resp = tdb_query(db_name, 42, '' + current_tanzaku.id + toadd);
status = (resp != null && resp.status);
}
if (toremove !== "") {
resp = tdb_query(db_name, 41, '' + current_tanzaku.id + toremove);
status = (resp != null && resp.status);
}
if (status) {
alert("Saved changes!");
} else {
alert("Something went wrong!");
}
});
$(document).on("click", "#btn-remove", function (e) {
e.preventDefault();
if (!confirm("This tag will be removed permanently. Are you sure?")) {
return;
}
let resp = tdb_query(db_name, 33, '' + current_tanzaku.id);
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
menu_add_close();
location.reload(true);
});
$(document).on("click", "#file-next", function (e) {
e.preventDefault();
file_next();
});
$(document).on("click", "#file-prev", function (e) {
e.preventDefault();
file_prev();
});
-99
View File
@@ -1,99 +0,0 @@
var db_name = localStorage["tfm_db_name"];
sort_sasa = localStorage["sort_files"];
sort_tanzaku = localStorage["sort_tags"];
if (sort_sasa == null) {
localStorage["sort_files"] = sort_sasa = "id";
}
if (sort_tanzaku == null) {
localStorage["sort_tags"] = sort_tanzaku = "id";
}
function settings_load() {
if (db_name != null) {
$(`#db_name option[value="${db_name}"]`).prop("selected", true);
} else {
$("#db_name option[value=\"\"]").prop("selected", true);
}
if (sort_sasa != null) {
let sort_s = sort_sasa;
if (sort_s[0] === '!') {
sort_s = sort_s.slice(1);
}
if (sort_s[0] === '-') {
$("#files-reverse").prop("checked", true);
sort_s = sort_s.slice(1);
}
$(`#files-by-${sort_s}`).prop("checked", true);
}
if (sort_tanzaku != null) {
let sort_t = sort_tanzaku;
if (sort_t[0] === '!') {
sort_t = sort_t.slice(1);
}
if (sort_t[0] === '-') {
$("#tags-reverse").prop("checked", true);
sort_t = sort_t.slice(1);
}
$(`#tags-by-${sort_t}`).prop("checked", true);
}
}
$(window).on("load", function () {
let resp = tdb_query();
if (resp == null || !resp.status) {
alert("Failed to fetch databases");
throw new Error("Failed to fetch databases");
}
resp.data.every(tdb => {
$("#db_name").append($("<option>", {
value: tdb.name,
text: tdb.name
}));
return true;
});
settings_load();
});
$(document).on("reset", "#settings", function (e) {
e.preventDefault();
settings_load();
});
$(document).on("submit", "#settings", function (e) {
e.preventDefault();
let db_name_input = $("#db_name");
let db_name_val = db_name_input.val();
if (db_name_val !== db_name) {
let resp = tdb_query();
if (resp == null || !resp.status) {
alert("Failed to fetch databases");
return;
}
let found = false;
resp.data.every(db => {
if (db.name === db_name_val) {
localStorage["tfm_db_name"] = db_name = db_name_val;
found = true;
db_name_input.removeClass("is-invalid");
localStorage["sasahyou_mts"] = sasahyou_mts = 0;
localStorage["sappyou_mts"] = sappyou_mts = 0;
localStorage["shoppyou_mts"] = shoppyou_mts = 0;
return false;
}
return true;
});
if (!found) {
db_name_input.addClass("is-invalid");
return;
}
}
let sort_f = ($("#files-reverse")[0].checked ? '-' : '') + $("input[type=radio][name=sort-files]:checked").attr("id").slice(9);
let sort_t = ($("#tags-reverse")[0].checked ? '-' : '') + $("input[type=radio][name=sort-tags]:checked").attr("id").slice(8);
if (sort_f !== sort_sasa && '!' + sort_f !== sort_sasa) {
localStorage["sort_files"] = sort_sasa = '!' + sort_f;
}
if (sort_t !== sort_tanzaku && '!' + sort_t !== sort_tanzaku) {
localStorage["sort_tags"] = sort_tanzaku = '!' + sort_t;
}
alert("Successfully updated settings!");
});
-39
View File
@@ -1,39 +0,0 @@
$(window).on("load", function () {
sappyou_load();
sappyou.forEach((tanzaku) => {
$(".contents-wrapper").append(`<div class="item tanzaku" tid="${tanzaku.id}">${tanzaku.name}</div>`);
$("#menu-file-view .list").append(`<div class="list-item tanzaku" tid="${tanzaku.id}">${tanzaku.name}</div>`);
});
sasahyou_load();
sasahyou.forEach((sasa) => {
$("#menu-tag-view .list").append(`<div class="list-item sasa" sid="${sasa.id}" title="${sasa.path.split('/').slice(-1)}"><img class="thumb" data-src="${"/thumbs/" + sasa.path}"><div class="overlay"></div></div>`);
});
});
$(document).on("input", "#text-filter-all", function (e) {
let filter = $(this).val().toLowerCase();
let unfiltered = $(".item");
if (filter === "") {
unfiltered.css("display", "");
return;
}
unfiltered.each((index, element) => {
let current = $(element);
if (current.text().toLowerCase().includes(filter)) {
current.css("display", "");
} else {
current.css("display", "none");
}
});
});
$(document).on("submit", "#menu-add form", function (e) {
e.preventDefault();
let resp = tdb_query(db_name, 34, $("#new-name").val() + '\n' + $("#new-description").val());
if (resp == null || !resp.status) {
alert("Something went wrong!");
return;
}
menu_add_close();
location.reload(true);
});
-53
View File
@@ -1,53 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Database Management | Tanabata</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/tdbms.js"></script>
<script src="/js/tdbms-load.js"></script>
<script src="/js/tdbms-database.js"></script>
</head>
<body>
<h1>Tanabata Database Management</h1>
<main>
<h2><span>TDB: 「<span class="db_name"><i>none</i></span></span></h2>
<div class="contents-wrapper button-flex">
<a href="/tdbms/stats" class="btn btn-primary">Stats</a>
<a href="/tdbms/sasahyou" class="btn btn-primary">Sasahyou</a>
<a href="/tdbms/sappyou" class="btn btn-primary">Sappyou</a>
<a href="/tdbms/shoppyou" class="btn btn-primary">Shoppyou</a>
</div>
<div class="contents-wrapper button-flex">
<button class="btn btn-outline-success" id="btn-save">Save database</button>
<button class="btn btn-outline-warning" id="btn-reload">Reload database</button>
<a href="/tdbms/new" class="btn btn-outline-info">Create database</a>
<button class="btn btn-outline-danger" id="btn-remove">Remove database</button>
</div>
<div class="contents-wrapper button-flex">
<a href="/" class="btn btn-secondary">Home</a>
<a href="/tdbms/settings" class="btn btn-secondary">Settings</a>
</div>
</main>
</body>
</html>
-53
View File
@@ -1,53 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New database | Tanabata Database Management</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/tdbms.js"></script>
<script src="/js/tdbms-load.js"></script>
<script src="/js/tdbms-database.js"></script>
</head>
<body>
<h1>TDBMS: add new database</h1>
<main>
<div class="contents-wrapper">
<form id="newdb">
<div class="form-group">
<label for="newdb-name">Name</label>
<input type="text" name="newdb-name" class="form-control" id="newdb-name">
</div>
<div class="form-group">
<label for="newdb-path">Location on server</label>
<input type="text" name="newdb-path" class="form-control" id="newdb-path">
</div>
<div class="form-group button-flex">
<button type="submit" class="btn btn-primary">Submit</button>
<a href="/tdbms" class="btn btn-outline-secondary">TDBMS home</a>
</div>
</form>
</div>
</main>
<script src="/js/tdbms-newdb.js"></script>
</body>
</html>
-49
View File
@@ -1,49 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sappyou | Tanabata Database Management</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<link rel="stylesheet" href="/css/tdbms.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/tdbms.js"></script>
<script src="/js/tdbms-load.js"></script>
</head>
<body>
<h1><a href="/tdbms"><span class="db_name"></span>: sappyou</a></h1>
<main>
<div class="contents-wrapper">
<table class="table table-striped table-dark" id="content">
<tr>
<th>ID</th>
<th>Ctime</th>
<th>Mtime</th>
<th>Name</th>
<th>Description</th>
</tr>
</table>
</div>
</main>
<script src="/js/tdbms-management.js"></script>
<script src="/js/tdbms-sappyou.js"></script>
</body>
</html>
-47
View File
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sasahyou | Tanabata Database Management</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<link rel="stylesheet" href="/css/tdbms.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/tdbms.js"></script>
<script src="/js/tdbms-load.js"></script>
</head>
<body>
<h1><a href="/tdbms"><span class="db_name"></span>: sasahyou</a></h1>
<main>
<div class="contents-wrapper">
<table class="table table-striped table-dark" id="content">
<tr>
<th>ID</th>
<th>Ctime</th>
<th>Name</th>
</tr>
</table>
</div>
</main>
<script src="/js/tdbms-management.js"></script>
<script src="/js/tdbms-sasahyou.js"></script>
</body>
</html>
-111
View File
@@ -1,111 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings | Tanabata Database Management</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/tdbms.js"></script>
<script src="/js/tdbms-load.js"></script>
</head>
<body>
<h1>TDBMS: Settings</h1>
<main>
<div class="contents-wrapper">
<form id="settings">
<div class="form-group">
<label for="db_name">Database name</label>
<select name="db_name" id="db_name" class="form-control form-select form-select-sm">
<option value=""></option>
</select>
</div>
<table class="form-group">
<tr>
<td>
<fieldset class="form-group">
<legend>Sasa sorting</legend>
<div class="form-check">
<input type="radio" name="sort-sasa" id="sasa-by-id">
<label for="sasa-by-id">By ID</label>
</div>
<div class="form-check">
<input type="radio" name="sort-sasa" id="sasa-by-cts">
<label for="sasa-by-cts">By ctime</label>
</div>
<div class="form-check">
<input type="radio" name="sort-sasa" id="sasa-by-path">
<label for="sasa-by-path">By name</label>
</div>
</fieldset>
</td>
<td>
<fieldset class="form-group">
<legend>Tanzaku sorting</legend>
<div class="form-check">
<input type="radio" name="sort-tanzaku" id="tanzaku-by-id">
<label for="tanzaku-by-id">By ID</label>
</div>
<div class="form-check">
<input type="radio" name="sort-tanzaku" id="tanzaku-by-cts">
<label for="tanzaku-by-cts">By ctime</label>
</div>
<div class="form-check">
<input type="radio" name="sort-tanzaku" id="tanzaku-by-mts">
<label for="tanzaku-by-mts">By mtime</label>
</div>
<div class="form-check">
<input type="radio" name="sort-tanzaku" id="tanzaku-by-name">
<label for="tanzaku-by-name">By name</label>
</div>
<div class="form-check">
<input type="radio" name="sort-tanzaku" id="tanzaku-by-nkazari">
<label for="tanzaku-by-nkazari">By kazari count</label>
</div>
</fieldset>
</td>
</tr>
<tr>
<td>
<div class="form-group form-check">
<input type="checkbox" name="sort-sasa-reverse" class="form-check-input" id="sasa-reverse">
<label class="form-check-label" for="sasa-reverse">Reverse sorting</label>
</div>
</td>
<td>
<div class="form-group form-check">
<input type="checkbox" name="sort-tanzaku-reverse" class="form-check-input" id="tanzaku-reverse">
<label class="form-check-label" for="tanzaku-reverse">Reverse sorting</label>
</div>
</td>
</tr>
</table>
<div class="button-flex">
<button type="submit" class="btn btn-primary">Apply</button>
<a href="/tdbms" class="btn btn-outline-secondary">TDBMS home</a>
<button type="reset" class="btn btn-danger">Reset</button>
</div>
</form>
</div>
</main>
<script src="/js/tdbms-settings.js"></script>
</body>
</html>
-47
View File
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shoppyou | Tanabata Database Management</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<link rel="stylesheet" href="/css/tdbms.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/tdbms.js"></script>
<script src="/js/tdbms-load.js"></script>
</head>
<body>
<h1><a href="/tdbms"><span class="db_name"></span>: shoppyou</a></h1>
<main>
<div class="contents-wrapper">
<table class="table table-striped table-dark" id="content">
<tr>
<th>Ctime</th>
<th>Sasa ID</th>
<th>Tanzaku ID</th>
</tr>
</table>
</div>
</main>
<script src="/js/tdbms-management.js"></script>
<script src="/js/tdbms-shoppyou.js"></script>
</body>
</html>
-67
View File
@@ -1,67 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stats | Tanabata Database Management</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<link rel="stylesheet" href="/css/tdbms.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/tdbms.js"></script>
<script src="/js/tdbms-load.js"></script>
</head>
<body>
<h1><a href="/tdbms"><span class="db_name"></span>: stats</a></h1>
<main>
<div class="contents-wrapper">
<h3>Sasahyou</h3>
<table class="table table-striped table-dark" id="stats-sasahyou">
<tr>
<th>Ctime</th>
<th>Mtime</th>
<th>Number of sasa</th>
<th>Number of holes</th>
</tr>
</table>
<h3>Sappyou</h3>
<table class="table table-striped table-dark" id="stats-sappyou">
<tr>
<th>Ctime</th>
<th>Mtime</th>
<th>Number of tanzaku</th>
<th>Number of holes</th>
</tr>
</table>
<h3>Shoppyou</h3>
<table class="table table-striped table-dark" id="stats-shoppyou">
<tr>
<th>Ctime</th>
<th>Mtime</th>
<th>Number of kazari</th>
<th>Number of holes</th>
</tr>
</table>
</div>
</main>
<script src="/js/tdbms-management.js"></script>
<script src="/js/tdbms-stats.js"></script>
</body>
</html>
-117
View File
@@ -1,117 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Files | Tanabata File Manager</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<link rel="stylesheet" href="/css/tfm.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/jquery.lazy.min.js"></script>
<script src="/js/tdbms.js"></script>
<script src="/js/tfm-management.js"></script>
</head>
<body>
<h1><a href="/tfm" title="TFM: Home">TFM: Files</a></h1>
<main>
<div class="contents-wrapper">
<button class="btn btn-outline-secondary sasa" id="btn-new"><b>NEW</b></button>
</div>
</main>
<div class="menu-wrapper" id="menu-file-view">
<div class="menu">
<div class="preview">
<img id="preview">
<div class="file-nav-btn" id="file-prev"></div>
<div class="file-nav-btn" id="file-next"></div>
</div>
<form>
<div class="form-group row">
<label class="col-form-label" for="file-name">File name</label>
<div class="col-form-input">
<input type="text" name="name" class="form-control" id="file-name">
</div>
</div>
<div class="form-group row">
<label class="col-form-label" for="text-filter">Tag filter</label>
<div class="col-form-input">
<input type="text" name="text-filter" class="form-control" id="text-filter">
</div>
</div>
<div class="form-group form-check">
<input type="checkbox" name="selection-filter" class="form-check-input" id="file-selection-filter" checked>
<label class="form-check-label" for="file-selection-filter">Show only selected</label>
</div>
<div class="form-group list"></div>
<div class="form-group button-flex">
<button type="submit" class="btn btn-primary">Confirm</button>
<a target="_blank" class="btn btn-outline-info" id="btn-full">View full</a>
<button type="reset" class="btn btn-outline-danger">Close</button>
</div>
</form>
</div>
</div>
<div class="menu-wrapper" id="menu-tag-view">
<div class="menu">
<form>
<div class="form-group row">
<label class="col-form-label" for="tag-name">Tag name</label>
<div class="col-form-input">
<input type="text" name="name" class="form-control" id="tag-name">
</div>
</div>
<div class="form-group">
<label for="description">Tag description</label>
<textarea class="form-control" name="description" id="description" rows="2"></textarea>
</div>
<div class="form-group form-check">
<input type="checkbox" name="selection-filter" class="form-check-input" id="tag-selection-filter">
<label class="form-check-label" for="tag-selection-filter">Show only selected</label>
</div>
<div class="form-group list"></div>
<div class="form-group button-flex">
<button type="submit" class="btn btn-primary">Confirm</button>
<button class="btn btn-danger" id="btn-remove">Remove</button>
<button type="reset" class="btn btn-outline-danger">Close</button>
</div>
</form>
</div>
</div>
<div class="menu-wrapper" id="menu-add">
<div class="menu">
<h2>Add new file</h2>
<form>
<div class="form-group row">
<label class="col-form-label" for="new-name">File name</label>
<div class="col-form-input">
<input type="text" name="new-name" class="form-control" id="new-name">
</div>
</div>
<div class="form-group button-flex">
<button type="submit" class="btn btn-primary">Add</button>
<button type="reset" class="btn btn-outline-danger">Reset</button>
</div>
</form>
</div>
</div>
<script src="/js/tfm-files.js"></script>
</body>
</html>
-48
View File
@@ -1,48 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home | Tanabata File Manager</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/tdbms.js"></script>
</head>
<body>
<h1>Tanabata File Manager</h1>
<main>
<h2>Come on, what to do?</h2>
<div class="contents-wrapper button-flex">
<a href="/tfm/files" class="btn btn-primary">Files</a>
<a href="/tfm/tags" class="btn btn-primary">Tags</a>
</div>
<div class="contents-wrapper button-flex">
<button class="btn btn-outline-success" id="btn-save">Save database</button>
<button class="btn btn-outline-warning" id="btn-reload">Reload database</button>
</div>
<div class="contents-wrapper button-flex">
<a href="/" class="btn btn-secondary">Home</a>
<a href="/tfm/settings" class="btn btn-secondary">Settings</a>
</div>
</main>
<script src="/js/tfm-database.js"></script>
</body>
</html>
-110
View File
@@ -1,110 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings | Tanabata File Manager</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/tdbms.js"></script>
</head>
<body>
<h1>TFM: Settings</h1>
<main>
<div class="contents-wrapper">
<form id="settings">
<div class="form-group">
<label for="db_name">Database name</label>
<select name="db_name" id="db_name" class="form-control form-select form-select-sm">
<option value=""></option>
</select>
</div>
<table class="form-group">
<tr>
<td>
<fieldset class="form-group">
<legend>Files sorting</legend>
<div class="form-check">
<input type="radio" name="sort-files" id="files-by-id">
<label for="files-by-id">By ID</label>
</div>
<div class="form-check">
<input type="radio" name="sort-files" id="files-by-cts">
<label for="files-by-cts">By ctime</label>
</div>
<div class="form-check">
<input type="radio" name="sort-files" id="files-by-path">
<label for="files-by-path">By name</label>
</div>
</fieldset>
</td>
<td>
<fieldset class="form-group">
<legend>Tags sorting</legend>
<div class="form-check">
<input type="radio" name="sort-tags" id="tags-by-id">
<label for="tags-by-id">By ID</label>
</div>
<div class="form-check">
<input type="radio" name="sort-tags" id="tags-by-cts">
<label for="tags-by-cts">By ctime</label>
</div>
<div class="form-check">
<input type="radio" name="sort-tags" id="tags-by-mts">
<label for="tags-by-mts">By mtime</label>
</div>
<div class="form-check">
<input type="radio" name="sort-tags" id="tags-by-name">
<label for="tags-by-name">By name</label>
</div>
<div class="form-check">
<input type="radio" name="sort-tags" id="tags-by-nkazari">
<label for="tags-by-nkazari">By kazari count</label>
</div>
</fieldset>
</td>
</tr>
<tr>
<td>
<div class="form-group form-check">
<input type="checkbox" name="sort-files-reverse" class="form-check-input" id="files-reverse">
<label class="form-check-label" for="files-reverse">Reverse sorting</label>
</div>
</td>
<td>
<div class="form-group form-check">
<input type="checkbox" name="sort-tags-reverse" class="form-check-input" id="tags-reverse">
<label class="form-check-label" for="tags-reverse">Reverse sorting</label>
</div>
</td>
</tr>
</table>
<div class="button-flex">
<button type="submit" class="btn btn-primary">Apply</button>
<a href="/tfm" class="btn btn-outline-secondary">TFM home</a>
<button type="reset" class="btn btn-danger">Reset</button>
</div>
</form>
</div>
</main>
<script src="/js/tfm-settings.js"></script>
</body>
</html>
-129
View File
@@ -1,129 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tags | Tanabata File Manager</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/images/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="manifest" href="/tanabata.webmanifest">
<meta name="msapplication-TileColor" content="#615880">
<meta name="msapplication-TileImage" content="/images/ms-icon-144x144.png">
<meta name="theme-color" content="#615880">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/general.css">
<link rel="stylesheet" href="/css/tfm.css">
<script src="/js/jquery-3.6.0.min.js"></script>
<script src="/js/jquery.lazy.min.js"></script>
<script src="/js/tdbms.js"></script>
<script src="/js/tfm-management.js"></script>
</head>
<body>
<h1><a href="/tfm" title="TFM: Home">TFM: Tags</a></h1>
<main>
<form>
<div class="form-group row">
<label class="col-form-label" for="text-filter-all">Tag filter</label>
<div class="col-form-input">
<input type="text" name="text-filter" class="form-control" id="text-filter-all">
</div>
</div>
</form>
<div class="contents-wrapper">
<button class="btn btn-outline-secondary tanzaku" id="btn-new"><b>NEW</b></button>
</div>
</main>
<div class="menu-wrapper" id="menu-tag-view">
<div class="menu">
<form>
<div class="form-group row">
<label class="col-form-label" for="tag-name">Tag name</label>
<div class="col-form-input">
<input type="text" name="name" class="form-control" id="tag-name">
</div>
</div>
<div class="form-group">
<label for="description">Tag description</label>
<textarea class="form-control" name="description" id="description" rows="2"></textarea>
</div>
<div class="form-group form-check">
<input type="checkbox" name="selection-filter" class="form-check-input" id="tag-selection-filter">
<label class="form-check-label" for="tag-selection-filter">Show only selected</label>
</div>
<div class="form-group list"></div>
<div class="form-group button-flex">
<button type="submit" class="btn btn-primary">Confirm</button>
<button class="btn btn-danger" id="btn-remove">Remove</button>
<button type="reset" class="btn btn-outline-danger">Close</button>
</div>
</form>
</div>
</div>
<div class="menu-wrapper" id="menu-file-view">
<div class="menu">
<div class="preview">
<img id="preview">
<div class="file-nav-btn" id="file-prev"></div>
<div class="file-nav-btn" id="file-next"></div>
</div>
<form>
<div class="form-group row">
<label class="col-form-label" for="file-name">File name</label>
<div class="col-form-input">
<input type="text" name="name" class="form-control" id="file-name">
</div>
</div>
<div class="form-group row">
<label class="col-form-label" for="text-filter">Tag filter</label>
<div class="col-form-input">
<input type="text" name="text-filter" class="form-control" id="text-filter">
</div>
</div>
<div class="form-group form-check">
<input type="checkbox" name="selection-filter" class="form-check-input" id="file-selection-filter" checked>
<label class="form-check-label" for="file-selection-filter">Show only selected</label>
</div>
<div class="form-group list" id="file-list"></div>
<div class="form-group button-flex">
<button type="submit" class="btn btn-primary">Confirm</button>
<a target="_blank" class="btn btn-outline-info" id="btn-full">View full</a>
<button type="reset" class="btn btn-outline-danger">Close</button>
</div>
</form>
</div>
</div>
<div class="menu-wrapper" id="menu-add">
<div class="menu">
<h2>New tag</h2>
<form>
<div class="form-group row">
<label class="col-form-label" for="new-name">Tag name</label>
<div class="col-form-input">
<input type="text" name="new-name" class="form-control" id="new-name">
</div>
</div>
<div class="form-group">
<label for="new-description">Tag description</label>
<textarea class="form-control" name="new-description" id="new-description" rows="10"></textarea>
</div>
<div class="form-group button-flex">
<button type="submit" class="btn btn-primary">Add</button>
<button type="reset" class="btn btn-outline-danger">Reset</button>
</div>
</form>
</div>
</div>
<script src="/js/tfm-tags.js"></script>
</body>
</html>
-3
View File
@@ -1,3 +0,0 @@
module tweb
go 1.13
-65
View File
@@ -1,65 +0,0 @@
package main
import (
"bytes"
"net"
)
// TDBMS connection struct
type TDBMSConnection struct {
domain string
addr string
conn net.Conn
}
// Connect to TDBMS server
func (tdbms *TDBMSConnection) Connect(domain, addr string) error {
c, err := net.Dial(domain, addr)
if err == nil {
tdbms.domain = domain
tdbms.addr = addr
tdbms.conn = c
}
return err
}
// Reconnect to TDBMS server
func (tdbms *TDBMSConnection) Reconnect() error {
tdbms.Close()
c, err := net.Dial(tdbms.domain, tdbms.addr)
if err == nil {
tdbms.conn = c
}
return err
}
// Close connection to TDBMS server
func (tdbms *TDBMSConnection) Close() error {
return tdbms.conn.Close()
}
// Execute a TDB request
func (tdbms *TDBMSConnection) Query(db_name string, trc uint8, request_body string) []byte {
var err error
buffer := []byte{trc}
buffer = append(buffer, db_name...)
buffer = append(buffer, 0)
buffer = append(buffer, request_body...)
buffer = append(buffer, 0, 4)
_, err = tdbms.conn.Write(buffer)
if err != nil {
return nil
}
var response []byte
var resp_size int = -1
buffer = make([]byte, 8192)
for resp_size <= 0 {
_, err = tdbms.conn.Read(buffer)
if err != nil {
return nil
}
response = append(response, buffer...)
resp_size = bytes.IndexByte(response, 4)
}
return response[:resp_size-1]
}
-13
View File
@@ -1,13 +0,0 @@
[Unit]
Description=Tanabata Project web server
After=tdbms.service
Requires=tdbms.service
[Service]
Type=simple
Restart=no
User=www-data
ExecStart=/usr/bin/tweb
[Install]
WantedBy=multi-user.target
-253
View File
@@ -1,253 +0,0 @@
package main
import (
"bytes"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"time"
)
type JSON struct {
Status bool `json:"status"`
Token string `json:"token,omitempty"`
TRC uint8 `json:"trc,omitempty"`
TRDB string `json:"trdb,omitempty"`
TRB string `json:"trb,omitempty"`
Data []map[string]interface{} `json:"data"`
}
var tdbms TDBMSConnection
const LOG_PATH = "/var/log/tanabata/tweb.log"
const TOKEN_VALIDTIME = 604800
var SID int64 = 0
var TOKEN = ""
func TokenGenerate(seed []byte) {
SID = time.Now().Unix()
value := SID
for i, char := range seed {
value += int64(char) << (i * 8)
}
TOKEN = fmt.Sprintf("%x", sha256.Sum256([]byte(strconv.FormatInt(value, 16))))
defer log.Println("Token generated")
}
func Auth(handler http.HandlerFunc, redirect bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
authorized := false
defer func() {
if authorized {
handler.ServeHTTP(w, r)
} else {
if redirect {
http.Redirect(w, r, "/auth", http.StatusSeeOther)
defer log.Println("Unauthorized request, redirecting to /auth")
} else {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
defer log.Println("Unauthorized request, status 401")
}
}
}()
token, err := r.Cookie("token")
if err == nil && time.Now().Unix()-SID < TOKEN_VALIDTIME && token.Value == TOKEN {
authorized = true
return
}
}
}
func HandlerAuth(w http.ResponseWriter, r *http.Request) {
var buffer = make([]byte, sha256.Size)
var response = JSON{Status: false}
var passhash = make([]byte, sha256.Size)
var hash [sha256.Size]byte
var passlen = sha256.Size
var err error
log.Println("Got authorization request")
passfile, err := os.Open("/etc/tanabata/.htpasswd")
if err != nil {
log.Fatalf("Failed to open password file: %s\n", err)
}
read, err := passfile.Read(passhash)
if err != nil {
log.Fatalf("Failed to read password file: %s\n", err)
}
if read != sha256.Size {
log.Fatalln("Invalid password file")
}
err = passfile.Close()
if err != nil {
log.Fatalf("Failed to close password file: %s\n", err)
}
_, err = r.Body.Read(buffer)
if err != nil && err != io.EOF {
http.Error(w, err.Error(), http.StatusBadRequest)
defer log.Println("Bad authorization request")
return
}
for i := 0; i < sha256.Size; i++ {
if buffer[i] == 0 {
passlen = i
break
}
}
hash = sha256.Sum256(buffer[:passlen])
if bytes.Equal(hash[:], passhash) {
log.Println("Password valid")
TokenGenerate(buffer)
response.Status = true
response.Token = TOKEN
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: TOKEN,
Expires: time.Now().Add(TOKEN_VALIDTIME * time.Second),
})
} else {
log.Println("Password invalid")
}
w.Header().Set("Content-Type", "application/json")
jsonData, err := json.Marshal(response)
if err != nil {
log.Fatalf("Failed to marshal json: %s\n", err)
}
_, err = w.Write(jsonData)
if err != nil {
log.Fatalf("Failed to send response: %s\n", err)
}
}
func HandlerTDBMS(w http.ResponseWriter, r *http.Request) {
var request JSON
var response []byte
var err error
log.Println("Got TDB request")
r.Body = http.MaxBytesReader(w, r.Body, 1048576)
json_decoder := json.NewDecoder(r.Body)
json_decoder.DisallowUnknownFields()
err = json_decoder.Decode(&request)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
defer log.Println("Bad TDB request")
return
}
response = tdbms.Query(request.TRDB, request.TRC, request.TRB)
if response == nil {
log.Println("Failed to get TDBMS response. Trying to reconnect to TDBMS server...")
err = tdbms.Reconnect()
if err != nil {
http.Error(w, "TDBMS is down", http.StatusServiceUnavailable)
defer log.Println("TDBMS is down")
return
}
response = tdbms.Query(request.TRDB, request.TRC, request.TRB)
if response == nil {
http.Error(w, "Failed to get TDBMS response", http.StatusInternalServerError)
defer log.Println("Failed to get TDBMS response")
return
}
}
log.Println("Got TDBMS response")
if strings.HasPrefix(strings.ToLower(request.TRDB), "tfm") && (request.TRC == 0b10000 || request.TRC == 0b101000) {
var json_response JSON
err = json.Unmarshal(response, &json_response)
if err != nil {
http.Error(w, "Bad TDBMS response", http.StatusInternalServerError)
defer log.Println("Bad TDBMS response")
return
}
for index, element := range json_response.Data {
path := strings.ReplaceAll(element["path"].(string), "/srv/data/tfm/", "")
splitpath := strings.Split(path, "/")
for pindex, pelement := range splitpath {
splitpath[pindex] = url.PathEscape(pelement)
}
path = strings.Join(splitpath, "/")
json_response.Data[index]["path"] = path
}
response, err = json.Marshal(json_response)
if err != nil {
log.Printf("Failed to marshal json: %s\n", err)
}
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_, err = w.Write(response)
if err != nil {
defer log.Printf("Failed to send TDBMS response: %s\n", err)
return
}
defer log.Println("TDBMS response sent")
}
func main() {
var err error
flog, err := os.OpenFile(LOG_PATH, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatalf("Failed to open log file: %s\n", err)
}
defer flog.Close()
log.SetOutput(flog)
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.Println("Connecting to TDBMS server...")
err = tdbms.Connect("unix", "/tmp/tdbms.sock")
if err != nil {
log.Fatalln("Failed to connect to TDBMS server")
}
defer func(tdbms TDBMSConnection) {
err := tdbms.Close()
if err != nil {
log.Fatalln(err)
}
}(tdbms)
log.Println("Initializing...")
server := &http.Server{
Addr: ":42776",
IdleTimeout: time.Minute,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
}
public_fs := http.FileServer(http.Dir("/srv/www/tanabata"))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/tfm" || r.URL.Path == "/tdbms" {
r.URL.Path += "/"
}
if r.URL.Path[len(r.URL.Path)-1] != '/' && path.Ext(r.URL.Path) == "" {
r.URL.Path += ".html"
}
public_fs.ServeHTTP(w, r)
})
http.HandleFunc("/AUTH", HandlerAuth)
http.HandleFunc("/TDBMS", Auth(HandlerTDBMS, false))
tfm_fs := http.FileServer(http.Dir("/srv/data/tfm"))
http.Handle("/files/", Auth(func(w http.ResponseWriter, r *http.Request) {
http.StripPrefix("/files", tfm_fs).ServeHTTP(w, r)
}, true))
http.Handle("/thumbs/", Auth(func(w http.ResponseWriter, r *http.Request) {
thumb_path := strings.Split(r.URL.Path, "/")
thumb_path[len(thumb_path)-1] = ".thumb-" + thumb_path[len(thumb_path)-1]
r.URL.Path = strings.Join(thumb_path, "/")
http.StripPrefix("/thumbs", tfm_fs).ServeHTTP(w, r)
}, true))
http.Handle("/preview/", Auth(func(w http.ResponseWriter, r *http.Request) {
http.StripPrefix("/preview", tfm_fs).ServeHTTP(w, r)
}, true))
log.Println("Running...")
err = server.ListenAndServeTLS("/etc/ssl/certs/web-global.crt", "/etc/ssl/private/web-global.key")
if errors.Is(err, http.ErrServerClosed) {
log.Fatalln("Server closed")
} else if err != nil {
log.Fatalf("Error starting server: %s\n", err)
}
}
+36
View File
@@ -0,0 +1,36 @@
body {
justify-content: center;
align-items: center;
}
.decoration {
position: absolute;
top: 0;
}
.decoration.left {
left: 0;
width: 20vw;
}
.decoration.right {
right: 0;
width: 20vw;
}
#auth {
max-width: 100%;
}
#auth h1 {
margin-bottom: 28px;
}
#auth .form-control {
margin: 14px 0;
border-radius: 14px;
}
#login {
margin-top: 20px;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+54
View File
@@ -0,0 +1,54 @@
html, body {
margin: 0;
padding: 0;
}
body {
background-color: #312F45;
color: #f0f0f0;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: stretch;
font-family: Epilogue;
overflow-x: hidden;
}
.btn {
height: 50px;
width: 100%;
border-radius: 14px;
font-size: 1.5rem;
font-weight: 500;
}
.btn-primary {
background-color: #9592B5;
border-color: #454261;
}
.btn-primary:hover {
background-color: #7D7AA4;
border-color: #454261;
}
.btn-danger {
background-color: #DB6060;
border-color: #851E1E;
}
.btn-danger:hover {
background-color: #D64848;
border-color: #851E1E;
}
.btn-row {
display: flex;
justify-content: space-between;
}
+388
View File
@@ -0,0 +1,388 @@
header, footer {
margin: 0;
padding: 10px;
box-sizing: border-box;
}
header {
padding: 20px;
box-shadow: 0 5px 5px #0004;
}
.icon-header {
height: .8em;
}
#select {
cursor: pointer;
}
.sorting {
position: relative;
display: flex;
justify-content: space-between;
}
#sorting {
cursor: pointer;
}
.highlighted {
color: #9999AD;
}
#icon-expand {
height: 10px;
}
#sorting-options {
position: absolute;
right: 0;
top: 114%;
padding: 4px 10px;
box-sizing: border-box;
background-color: #111118;
border-radius: 10px;
text-align: left;
box-shadow: 0 0 10px black;
z-index: 9999;
}
.sorting-option {
padding: 4px 0;
display: flex;
justify-content: space-between;
}
.sorting-option input[type="radio"] {
float: unset;
margin-left: 1.8em;
}
.filtering-wrapper {
margin-top: 10px;
}
.filtering-block {
position: absolute;
top: 128px;
left: 14px;
right: 14px;
padding: 14px;
background-color: #111118;
border-radius: 10px;
box-shadow: 0 0 10px 4px #0004;
z-index: 9998;
}
main {
margin: 0;
padding: 10px;
height: 100%;
display: flex;
justify-content: space-between;
align-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: scroll;
}
main:after {
content: "";
flex: auto;
}
.item-preview {
position: relative;
}
.item-selected:after {
content: "";
display: block;
position: absolute;
top: 10px;
right: 10px;
width: 100%;
height: 50%;
background-image: url("/static/images/icon-select.svg");
background-size: contain;
background-position: right;
background-repeat: no-repeat;
}
.file-preview {
margin: 1px 0;
padding: 0;
width: 160px;
height: 160px;
max-width: calc(33vw - 7px);
max-height: calc(33vw - 7px);
overflow: hidden;
cursor: pointer;
}
.file-thumb {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
}
.file-preview .overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #0002;
}
.file-preview:hover .overlay {
background-color: #0004;
}
.tag-preview, .filtering-token {
margin: 5px 5px;
padding: 5px 10px;
border-radius: 5px;
background-color: #444455;
cursor: pointer;
}
.category-preview {
margin: 5px 5px;
padding: 5px 10px;
border-radius: 5px;
background-color: #444455;
cursor: pointer;
}
.file {
margin: 0;
padding: 0;
min-width: 100vw;
min-height: 100vh;
max-width: 100vw;
max-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: black;
}
.file .preview-img {
max-width: 100vw;
max-height: 100vh;
}
.selection-manager {
position: fixed;
left: 10px;
right: 10px;
bottom: 65px;
box-sizing: border-box;
max-height: 40vh;
display: flex;
flex-direction: column;
padding: 15px 10px;
background-color: #181721;
border-radius: 10px;
box-shadow: 0 0 5px #0008;
}
.selection-manager hr {
margin: 5px 0;
}
.selection-header {
display: flex;
justify-content: space-between;
}
.selection-header > * {
cursor: pointer;
}
#selection-edit-tags {
color: #4DC7ED;
}
#selection-add-to-pool {
color: #F5E872;
}
#selection-delete {
color: #DB6060;
}
.selection-tags {
max-height: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
input[type="color"] {
width: 100%;
}
.tags-container, .filtering-operators, .filtering-tokens {
padding: 5px;
background-color: #212529;
border: 1px solid #495057;
border-radius: .375rem;
display: flex;
justify-content: space-between;
align-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
box-sizing: border-box;
}
.filtering-operators {
margin-bottom: 15px;
}
.tags-container, .filtering-tokens {
margin: 15px 0;
height: 200px;
overflow-x: hidden;
overflow-y: scroll;
}
.tags-container:after, .filtering-tokens:after {
content: "";
flex: auto;
}
.tags-container-selected {
height: 100px;
}
#files-filter {
margin-bottom: 0;
height: 56px;
}
.viewer-wrapper {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: #000a;
display: flex;
justify-content: center;
/* overflow-y: scroll;*/
}
.viewer-nav {
position: absolute;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.viewer-nav:hover {
background-color: #b4adff40;
}
.viewer-nav-prev {
left: 0;
right: 80vw;
}
.viewer-nav-next {
left: 80vw;
right: 0;
}
.viewer-nav-close {
left: 0;
right: 0;
bottom: unset;
height: 15vh;
}
.viewer-nav-icon {
width: 20px;
height: 32px;
}
.viewer-nav-close > .viewer-nav-icon {
width: 16px;
height: 16px;
}
#viewer {
width: 100%;
height: 100%;
max-width: 100%;
}
.sessions-wrapper {
padding: 14px;
background-color: #111118;
border-radius: 10px;
}
.btn-terminate {
height: 20px;
cursor: pointer;
}
footer {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #0007;
}
.nav {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
width: 18vw;
background: transparent;
border: 0;
border-radius: 10px;
outline: 0;
}
.nav.curr, .nav:hover {
background-color: #343249;
}
.navicon {
display: block;
height: 30px;
}
#loader {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #000a;
z-index: 9999;
}
.loader-wrapper {
padding: 15px;
border-radius: 12px;
background-color: white;
}
.loader-img {
max-width: 20vw;
max-height: 20vh;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Some files were not shown because too many files have changed in this diff Show More