315 Commits

Author SHA1 Message Date
H1K0 676b254187 chore: prepare for release 2.0.0 2023-02-22 08:45:48 +03:00
H1K0 c1a4d79481 fix(web): fix h3 font size 2023-02-22 08:38:20 +03:00
H1K0 cb2de0658f chore(tfm): remove -V option 2023-02-22 08:32:10 +03:00
H1K0 9795b4dc2d perf(dbms): minimize databases stats response 2023-02-21 00:35:21 +03:00
H1K0 e5abcaae0d chore(docs): update docs 2023-02-21 00:17:43 +03:00
H1K0 a2179c8ed3 fix(tfm): check file path when adding new 2023-02-20 23:53:37 +03:00
H1K0 6e105f6aa9 refactor(tfm): change config file location 2023-02-20 23:46:59 +03:00
H1K0 1c62fd8219 fix(tfm): fix CLI app 2023-02-18 01:01:02 +03:00
H1K0 90ac5324ea fix(web): fix tag view menu lazy load 2023-02-18 00:50:35 +03:00
H1K0 1243c4dc93 chore: add TDBMS CLI client to CMakeLists.txt 2023-02-17 23:57:22 +03:00
H1K0 d2867e2d99 feat(dbms): add help message to CLI client 2023-02-17 23:56:56 +03:00
H1K0 c9b921becf fix(web): fix tdb response checking in js 2023-02-17 23:51:45 +03:00
H1K0 880d4df453 style(web): add background to apple and android favicon 2023-02-17 20:16:11 +03:00
H1K0 3f5527db56 perf(web): optimize adding/removing tags to files 2023-02-17 13:29:57 +03:00
H1K0 832eb72bf4 feat(dbms): add new request codes
`trc_kazari_remove_single_sasa_to_multiple_tanzaku` and `trc_kazari_remove_single_tanzaku_to_multiple_sasa`
2023-02-17 11:34:27 +03:00
H1K0 53f09a5d82 feat(web): add new database adding interface 2023-02-15 22:15:18 +03:00
H1K0 b0e9d2631b fix(web): add missing viewport metas 2023-02-15 21:51:43 +03:00
H1K0 2c6b41c408 perf(web): improve button flex design 2023-02-15 21:46:09 +03:00
H1K0 fc4887ef2e fix(web): fix TFM database buttons behavior 2023-02-15 21:35:06 +03:00
H1K0 873ecf02ff feat(web): add database save, reload and remove buttons handling to TDBMS interface 2023-02-15 21:34:43 +03:00
H1K0 5b36a31b84 fix(web): fix design 2023-02-15 20:38:09 +03:00
H1K0 215f8d135a fix(web): fix settings handling 2023-02-15 20:11:30 +03:00
H1K0 f515dc3944 perf(web): return to home page when pressed escape on auth page 2023-02-15 19:32:05 +03:00
H1K0 8eb11f6035 fix(web): update server to handle database management interface 2023-02-15 15:31:32 +03:00
H1K0 1f3d015870 perf(web): change TFM header 2023-02-15 15:19:34 +03:00
H1K0 ba5418dc39 feat(web): add database management interface 2023-02-15 15:19:08 +03:00
H1K0 fb56cd6076 perf(web): change buttons design 2023-02-15 13:05:25 +03:00
H1K0 1a1f120777 style(web): a bit of code cleanup 2023-02-13 22:10:05 +03:00
H1K0 c9dc59ecec fix(web): fix authorization handling 2023-02-13 18:11:20 +03:00
H1K0 cc55219e46 feat(web): add handling tag description 2023-02-12 17:12:22 +03:00
H1K0 d133400103 perf(web): improve design 2023-02-12 16:57:38 +03:00
H1K0 fc90276399 fix(web): wrap button row 2023-02-12 16:42:48 +03:00
H1K0 bf88d346e4 feat(web): add renaming tag 2023-02-12 16:39:31 +03:00
H1K0 0ad59c40bd refactor(web): change database reload button id 2023-02-12 16:19:27 +03:00
H1K0 5f8cb21f03 fix(web): reset hyous mts on reloading database 2023-02-12 16:17:38 +03:00
H1K0 39215e02ce fix(web): save sorted hyou to localStorage 2023-02-12 16:08:43 +03:00
H1K0 14226db236 fix(web): reset hyous mts on changing database 2023-02-12 15:52:13 +03:00
H1K0 6f0359e99f style(web): simplify tdb_query() func 2023-02-12 15:51:16 +03:00
H1K0 e8e32c70f0 feat(web): add sorting sasa and tanzaku 2023-02-12 15:35:52 +03:00
H1K0 e72d371ff1 fix(web): fix settings page radio ids 2023-02-09 19:22:48 +03:00
H1K0 cd6cae7316 perf(web): improve tdbms.js 2023-02-09 19:18:38 +03:00
H1K0 87404e34d3 refactor(web): move common TDBMS functions to tdbms.js 2023-02-09 18:49:25 +03:00
H1K0 a1e6f3e9e1 perf(web): throw js error on unauthorized TDBMS request 2023-02-09 18:37:45 +03:00
H1K0 f7c8923199 perf(web): some improvements in buttons 2023-02-09 01:08:48 +03:00
H1K0 243621f3b2 feat(web): add TFM settings page
Only setting TFM database name by now
2023-02-09 00:45:54 +03:00
H1K0 9c07702409 perf(web): switch to dark theme 2023-02-07 02:18:37 +03:00
H1K0 84e3dd19e1 feat(dbms): introduce TDBMS CLI client app 2023-02-05 15:06:16 +03:00
H1K0 86dae4c264 perf(web): do not close menu on submit 2023-02-04 16:30:23 +03:00
H1K0 8423314b73 style(web): change theme color 2023-02-04 00:24:04 +03:00
H1K0 40cd9ca49f fix(web): fix and improve substring filtering 2023-02-03 23:39:39 +03:00
H1K0 6eb34f3b59 fix(web): fix lazy load on tag view menu 2023-02-03 23:33:59 +03:00
H1K0 223e174568 feat(web): new menu handling supports two view menus at once 2023-02-03 22:51:35 +03:00
H1K0 371b091c10 perf(web): improve text input positioning in file menu 2023-02-03 20:26:44 +03:00
H1K0 48475158f8 perf(web): reduce preview max height 2023-02-03 16:43:18 +03:00
H1K0 94105491af feat(web): redirect client when TDBMS response status is 401 2023-02-03 16:39:39 +03:00
H1K0 dc937444e5 perf(web): do not redirect unauthorized TDBMS requests 2023-02-03 16:34:28 +03:00
H1K0 dfe53d3e16 perf(web): remove highlighting file menu navigation buttons 2023-02-03 01:30:06 +03:00
H1K0 4d2326986f feat(web): try to reconnect to TDBMS server on failure to get response 2023-02-03 01:08:24 +03:00
H1K0 e7be627292 feat(web): make button-flex position fixed at the bottom of menu 2023-02-03 00:22:09 +03:00
H1K0 c960eb6730 feat(web): use arrow keys for file navigation 2023-02-02 23:56:35 +03:00
H1K0 560b255b69 feat(web): add prev/next navigation buttons to file view menu 2023-02-02 23:50:11 +03:00
H1K0 82cd625988 fix(web): fix view menu open and close functions 2023-02-02 23:34:57 +03:00
H1K0 b7ad148d27 refactor(web): move menu open/close actions into separate functions 2023-02-02 23:28:18 +03:00
H1K0 689e6f9136 perf(web): a tiny improvement in tdbms.js 2023-02-02 23:00:08 +03:00
H1K0 21f57e8767 fix(web): fix localStorage management 2023-02-02 22:59:32 +03:00
H1K0 7f5daee11e perf(web): improve menu sizing 2023-02-02 21:00:25 +03:00
H1K0 6cd7bb73fe perf(web): save hyous to localStorage 2023-02-02 18:18:11 +03:00
H1K0 ee787dab18 feat(dbms): return new tanzaku info on successful adding tanzaku 2023-02-02 16:20:52 +03:00
H1K0 24c9e39084 feat(core,lib): return new tanzaku when adding tanzaku or HOLE_TANZAKU on failure 2023-02-02 16:15:30 +03:00
H1K0 d8de645780 feat(dbms): return new sasa info on successful adding sasa 2023-02-02 16:12:28 +03:00
H1K0 2ba60d49e6 perf(core,lib): return new sasa when adding sasa or HOLE_SASA on failure 2023-02-02 16:09:21 +03:00
H1K0 8b86a34a71 feat(core,lib): return new sasa ID when adding sasa or HOLE_ID on failure 2023-02-02 15:53:02 +03:00
H1K0 53320831bb feat(web): lazy load file thumbs on tags menu 2023-02-02 13:44:44 +03:00
H1K0 8089c6ae68 feat(web): show image preview in file view menu 2023-02-02 00:05:04 +03:00
H1K0 ef373db8d5 perf(web): remove js cookie management 2023-02-01 20:28:53 +03:00
H1K0 e81ee683b7 style(web): a bit of code cleanup 2023-02-01 20:16:24 +03:00
H1K0 083eac3fee perf(web): use thumbnails instead of full sized images 2023-01-31 23:07:35 +03:00
H1K0 eaa7c2f2ce chore(web): a bit of html cleanup 2023-01-31 18:46:25 +03:00
H1K0 9226b6cf5a feat(web): introduce lazy loading file thumbs on files page 2023-01-31 18:45:57 +03:00
H1K0 9a2fa14a79 fix(lib): load database anyway 2023-01-31 16:46:24 +03:00
H1K0 4b7766695e feat(web): introduce tag removing 2023-01-31 16:24:03 +03:00
H1K0 b4b76fe271 chore(dbms): fix installer 2023-01-31 16:06:07 +03:00
H1K0 3a261d5d01 chore(web): add tweb server installation script and systemd service config 2023-01-31 16:05:39 +03:00
H1K0 3a5804d235 perf(web): temporary set thumbs directory equal to files dir 2023-01-31 15:59:13 +03:00
H1K0 a3d901a599 perf(web): reduce sasa margin 2023-01-31 15:52:31 +03:00
H1K0 440468bab5 feat(web): log time with microseconds 2023-01-31 15:41:06 +03:00
H1K0 c62d5fa512 fix(web): fix authorization 2023-01-31 15:28:28 +03:00
H1K0 0c03d3a791 perf(web): improve logging 2023-01-31 15:23:46 +03:00
H1K0 515beac231 fix(web): fix authorization 2023-01-31 15:20:35 +03:00
H1K0 27800737e3 feat(web): log to file 2023-01-31 15:00:20 +03:00
H1K0 50d9555d2d perf(web): change server configuration 2023-01-31 14:39:44 +03:00
H1K0 a3e2062de7 fix(dbms): fix socket file permissions 2023-01-31 14:28:39 +03:00
H1K0 fefff3ce45 feat(web): introduce saving and discarding changes 2023-01-31 02:08:42 +03:00
H1K0 70ac14a17a refactor(web): rename tfm.js to tfm-management.js 2023-01-31 01:57:28 +03:00
H1K0 32000c2b9f refactor(web): remove unused variables 2023-01-31 01:55:05 +03:00
H1K0 70f593997d perf(web): improve form submit handling 2023-01-31 01:54:33 +03:00
H1K0 9cf6daefb8 chore(web): change auth submit element from input to button 2023-01-31 01:49:07 +03:00
H1K0 0c84504d2c feat(web): introduce adding new tag to database 2023-01-31 01:43:59 +03:00
H1K0 789e24b675 fix(web): fix bug in TDB query handling 2023-01-31 01:40:36 +03:00
H1K0 52b54ba792 feat(web): introduce adding new file (path) to database 2023-01-31 01:21:30 +03:00
H1K0 b535faa4c7 perf(web): some improvements in design 2023-01-31 01:03:59 +03:00
H1K0 ca0bd0726a feat(web): add text filter to tags page 2023-01-30 23:31:06 +03:00
H1K0 478e67cc95 perf(web): improve contents list design 2023-01-30 23:28:17 +03:00
H1K0 e9f848d39f perf(web): make list elements justified 2023-01-30 22:03:48 +03:00
H1K0 9eccd06395 perf(web): make headers' font size adaptive 2023-01-30 22:02:38 +03:00
H1K0 59e6b3ba8b fix(web): fix scrolling on mobile 2023-01-30 21:54:13 +03:00
H1K0 9f94387fb0 fix(web): fix list height 2023-01-30 21:53:07 +03:00
H1K0 5a78facf76 fix(dbms): escape TDB names in response 2023-01-30 20:23:39 +03:00
H1K0 e9c0c6340a perf(dbms): improve TDBMS installer script 2023-01-30 18:30:08 +03:00
H1K0 d72348e639 refactor(web): change filters ids 2023-01-29 23:11:46 +03:00
H1K0 109cc1ac32 fix(web): text filtering works only with shown elements 2023-01-29 21:31:42 +03:00
H1K0 803f723015 fix(web): fix selection filtering 2023-01-29 21:21:34 +03:00
H1K0 08b18063aa fix(web): a little bug fix in server code 2023-01-29 21:12:58 +03:00
H1K0 591d598d24 perf(dbms): remove extra code line 2023-01-29 20:58:24 +03:00
H1K0 733780c17c feat(web): add filtering only selected items in list 2023-01-29 20:20:10 +03:00
H1K0 351e0be3ca feat(web): add TFM tags page 2023-01-29 19:18:13 +03:00
H1K0 58bd284101 fix(web): change file menu title 2023-01-29 19:16:37 +03:00
H1K0 efe4339b33 fix(web): fix list height 2023-01-29 19:15:36 +03:00
H1K0 a089d519ea chore(web): some changes in css 2023-01-29 18:52:57 +03:00
H1K0 0871d9209d fix(web): prevent default on double click 2023-01-29 18:50:27 +03:00
H1K0 552cb2653f fix(web): some little fixes in tfm-files.js 2023-01-29 18:49:58 +03:00
H1K0 8b852401a5 perf(web): don't load shoppyou on load 2023-01-29 18:26:16 +03:00
H1K0 20dc58f951 refactor(web): rename hyou load functions 2023-01-29 18:25:58 +03:00
H1K0 ccca7ac6b9 fix(web): fix TFM pages titles 2023-01-29 18:22:17 +03:00
H1K0 b618ecbd52 refactor(web): separate TFM files page 2023-01-29 18:20:39 +03:00
H1K0 9bfb9f89fe refactor(web): create /tfm directory for TFM pages 2023-01-29 16:57:17 +03:00
H1K0 8c2fba25f0 feat(web): adding/removing file tags 2023-01-29 16:43:11 +03:00
H1K0 6f3ac78fd0 fix(dbms): clear response when request requires no data 2023-01-29 16:17:07 +03:00
H1K0 bf4e127908 perf(web): redirect to /auth on fail to get TDBMS response 2023-01-29 16:00:02 +03:00
H1K0 c78e380d23 feat(web): add tanzaku filtering to sasa menu 2023-01-29 15:30:53 +03:00
H1K0 ed855ba9f9 perf(web): improve sasa menu design 2023-01-29 15:20:50 +03:00
H1K0 9159cd0aca refactor(web): move form and .button-flex styles to general.css 2023-01-29 13:55:17 +03:00
H1K0 5cb498112d feat(web): initialize sasa info menu 2023-01-29 02:45:39 +03:00
H1K0 660ff87af6 feat(web): add authorization page link to home page 2023-01-29 00:46:03 +03:00
H1K0 5725015408 feat(web): initialize TFM page
Only table of thumbs by now
2023-01-29 00:45:20 +03:00
H1K0 b55865b8e7 fix(web): escape file paths 2023-01-29 00:43:44 +03:00
H1K0 f5eb8dac06 feat(web): mask real file paths when working with TFM database 2023-01-28 21:51:56 +03:00
H1K0 94b55c8a87 perf(dbms): improve response structure 2023-01-28 21:41:51 +03:00
H1K0 8b9861de18 fix(dbms): escape slash in json 2023-01-28 19:56:14 +03:00
H1K0 607cd6df09 perf(web): improve authentication 2023-01-28 19:18:11 +03:00
H1K0 0a76f7fd8e feat(web): show 'Back to home' button on auth when valid token 2023-01-27 17:19:14 +03:00
H1K0 deb00436f5 perf(web): add validate function to js 2023-01-27 17:17:47 +03:00
H1K0 7c58f011ca fix(web): fix token.js 2023-01-27 17:00:35 +03:00
H1K0 46e67488f1 fix(web): initialize index.html and update styles 2023-01-27 16:53:24 +03:00
H1K0 b791c60b81 fix(web): update styles 2023-01-27 16:52:15 +03:00
H1K0 7e0e619950 chore(web): change from CRLF to LF in css 2023-01-27 15:29:58 +03:00
H1K0 aafaf2d32c fix(web): fix assets paths in html 2023-01-27 15:24:51 +03:00
H1K0 ebb3836930 feat(web): initialize frontend 2023-01-27 01:33:23 +03:00
H1K0 47ee5c4776 feat(web): add token validation handler 2023-01-27 01:21:18 +03:00
H1K0 9a4817d6ad refactor(web): move all server files into server directory 2023-01-27 01:11:24 +03:00
H1K0 2981a4b0ca chore(web): add go.mod 2023-01-27 01:06:58 +03:00
H1K0 01f2b24bfc chore: remove unused constants.h 2023-01-27 01:05:50 +03:00
H1K0 9d22038261 chore(dbms): rename 'unsaved' stats key to 'changed' 2023-01-27 00:43:57 +03:00
H1K0 540a039057 chore(dbms): separate date and time with space in logs 2023-01-27 00:41:45 +03:00
H1K0 94b3776ddf fix(dbms): escape JSON strings 2023-01-27 00:38:46 +03:00
H1K0 6dd7421050 fix(dbms): remove trailing comma from response 2023-01-26 23:38:48 +03:00
H1K0 9828e0f213 feat(web): add TDBMS client 2023-01-26 23:29:52 +03:00
H1K0 77d1044742 fix(dbms): remove hex numbers notation from json response 2023-01-26 23:05:06 +03:00
H1K0 b80eaf55ea chore(web): change assets paths 2023-01-26 21:51:11 +03:00
H1K0 a5728f7838 chore(dbms): add TRC bits enum to tdbms.h 2023-01-26 21:49:48 +03:00
H1K0 58868ff681 fix(dbms): put EOT char in the end of request/response 2023-01-26 17:29:01 +03:00
H1K0 23aae2c3b1 refactor(dbms): tdb_query returns response 2023-01-23 21:04:49 +03:00
H1K0 bf1a652b91 feat(web): improve token generation 2023-01-23 16:16:17 +03:00
H1K0 f8d988883f chore(dbms): add tdbms server installation script and systemd service config 2023-01-22 17:22:21 +03:00
H1K0 ca9bf96eec fix(tfm): fix CLI 2023-01-21 18:58:35 +03:00
H1K0 aa0f85397f fix(dbms): fix tdb list loading 2023-01-21 18:45:12 +03:00
H1K0 b1c6229968 fix(dbms): fix tdb load and save operations handling 2023-01-21 18:44:54 +03:00
H1K0 085b337a5e feat(dbms): save db list after adding/removing/editing any 2023-01-21 18:37:46 +03:00
H1K0 a389d959e0 perf(dbms): optimize request execution 2023-01-21 18:34:03 +03:00
H1K0 3783d8061b fix(dbms): send empty list instead of false status 2023-01-21 17:50:47 +03:00
H1K0 45ce484636 fix(lib): some bug fixes in kazari section 2023-01-21 17:48:18 +03:00
H1K0 9de309e142 feat(dbms): add kazari operatons handling 2023-01-21 17:44:15 +03:00
H1K0 a127cae637 fix(dbms): remove unused TRC 2023-01-21 17:32:44 +03:00
H1K0 f103e87020 fix(dbms): some little fixes 2023-01-21 16:38:13 +03:00
H1K0 66c1a17ad5 feat(dbms): add remove sasa by tanzaku and tanzaku by sasa operations 2023-01-21 16:19:37 +03:00
H1K0 ea720c449c feat(dbms): add tanzaku operations handling 2023-01-21 15:57:16 +03:00
H1K0 53277ee2ac fix(dbms): fix a little bug in client lib 2023-01-21 14:33:38 +03:00
H1K0 fae600d5d4 feat(dbms): send all tdb stats when got empty db name with trc_db_stats 2023-01-21 14:21:02 +03:00
H1K0 225b1be031 feat(dbms): add sasa operations handling 2023-01-21 02:16:37 +03:00
H1K0 7fa946dd02 fix(core): return 0 when removing non-existent kazari 2023-01-21 02:12:32 +03:00
H1K0 93c0087cbc fix(lib): a little fix in kazari section 2023-01-21 01:54:18 +03:00
H1K0 e130405459 fix(dbms): fix stats JSON and db remove functions 2023-01-21 00:48:01 +03:00
H1K0 871b76d43a init(dbms): introduce Tanabata database management system (TDBMS)
Only basic database operations, only Unix domain sockets by now
2023-01-20 22:50:01 +03:00
H1K0 08da5aeb79 fix(lib): a little bug fix in weed function 2023-01-20 22:03:36 +03:00
H1K0 cc795bae19 fix(lib): some bug fixes, optimization improves and code cleanup 2023-01-20 17:18:23 +03:00
H1K0 8768d55b48 feat(lib): remove sasa get/remove by path and tanzaku get/remove by name functions 2023-01-20 15:58:27 +03:00
H1K0 0db444b65c style(core): for loops stylization 2023-01-20 15:41:47 +03:00
H1K0 d90570962b perf(lib): remove file path validation 2023-01-20 13:14:45 +03:00
H1K0 20ae688a31 fix(core): a lot of bug fixes, optimization improves and some code cleanup 2023-01-20 02:26:21 +03:00
H1K0 7d97948e74 fix(core): a little bug fix in sappyou.c 2023-01-19 01:51:53 +03:00
H1K0 7bdcd3e495 fix(lib): a little bug fix in tanabata_init function 2023-01-19 00:51:04 +03:00
H1K0 bf49221141 fix(core): a little bug fix on loading/saving hyou 2023-01-18 23:50:24 +03:00
H1K0 3fe58e8cc2 chore: a bit of stylization to CMakeLists.txt 2023-01-14 17:29:22 +03:00
H1K0 b829e22710 chore: set cmake project name to Tanabata 2023-01-14 17:25:54 +03:00
H1K0 221e547db5 refactor(core): make core_func.h local 2023-01-14 17:13:52 +03:00
H1K0 d421eadde4 refactor: rearrange files 2023-01-14 15:12:12 +03:00
H1K0 a69d8ac042 fix(core): add tanabata struct to core.h 2023-01-13 15:40:20 +03:00
H1K0 44ef8c32c2 perf: add constants.h file with global constants 2023-01-13 02:21:19 +03:00
H1K0 36acd4208a chore(lib): remove extra includes 2023-01-13 02:19:25 +03:00
H1K0 62773539cd refactor(core,lib): move all core and lib structs and constants to a separate header file 2023-01-13 02:17:09 +03:00
H1K0 50b1300847 chore(core,lib): add #pragma once to header files 2023-01-13 00:28:14 +03:00
H1K0 7f31d62260 init(web): add http server on golang with authentication only
And remove CGI
2023-01-10 20:08:17 +03:00
H1K0 e4188c69a4 feat(cli): print related sasa/tanzaku ID when viewing particular tanzaku/sasa 2023-01-08 23:34:39 +03:00
H1K0 e9ff845c5b chore: a little fix in build.sh 2023-01-04 03:09:32 +03:00
H1K0 5019c54380 init(cgi): add Common Gateway Interface with authentication only 2023-01-04 01:55:15 +03:00
H1K0 091b1757c6 chore: prepare for 1.0.0 release 2022-12-31 17:22:08 +03:00
H1K0 2ee6e5e8cc docs: some corrections 2022-12-31 15:06:57 +03:00
H1K0 1f37f7a2b8 docs: some minor corrections 2022-12-30 19:26:01 +03:00
H1K0 90af6dada8 docs: create a section for the CLI manual 2022-12-30 19:13:15 +03:00
H1K0 6119ed684e chore(cli): remove datetime from list view 2022-12-30 18:43:45 +03:00
H1K0 32fea5cd2b fix(lib): check if hyou is empty on remove 2022-12-30 18:20:52 +03:00
H1K0 5daa0db847 perf(lib): weed sappyou before shoppyou 2022-12-30 18:12:07 +03:00
H1K0 94aa958471 perf(lib): weeding also removes kazari with invalid sasa or tanzaku ID 2022-12-30 18:09:50 +03:00
H1K0 c7e38851f2 chore(cli): move '\n' outside of stylization macros 2022-12-30 17:34:15 +03:00
H1K0 66f1c42fcc chore(cli): some minor interface improvements 2022-12-30 16:48:33 +03:00
H1K0 3939b30c15 fix(cli): correct output messages in the update menu 2022-12-30 16:46:27 +03:00
H1K0 092188ac5e docs: update and correct usage manual 2022-12-30 16:20:36 +03:00
H1K0 d19f2b09b2 feat(cli): add option to edit sasa or tanzaku 2022-12-30 15:20:59 +03:00
H1K0 a03355f8ce feat(cli): print tanzaku last modification datetime 2022-12-30 15:12:41 +03:00
H1K0 ff0cd4eea2 fix(core): update tanzaku last modification timestamp on tanzaku update 2022-12-30 15:11:25 +03:00
H1K0 df12b94b2a fix(cli): colorize uncolorized error message 2022-12-30 15:05:04 +03:00
H1K0 2fb8038a44 feat(lib): add sasa and tanzaku update functions 2022-12-30 14:57:03 +03:00
H1K0 0b0bc93a8d feat(core): add sasa and tanzaku update functions 2022-12-30 13:47:00 +03:00
H1K0 90e5761d3e perf(core): remove some extra instructions 2022-12-29 21:34:10 +03:00
H1K0 28ee348dad docs: update usage manual 2022-12-29 19:06:10 +03:00
H1K0 c4f2149291 feat(cli)!: significant interface improvements 2022-12-29 18:35:01 +03:00
H1K0 2d46b72929 style(core): code cleanup 2022-12-29 18:04:34 +03:00
H1K0 60f44fb82f fix(lib): fix sasa add and remove functions 2022-12-29 17:32:00 +03:00
H1K0 12424fb995 fix(lib): check if got NULL in all functions 2022-12-29 17:24:37 +03:00
H1K0 dd1d513f1e fix(core): check if got NULL in all functions 2022-12-29 17:22:50 +03:00
H1K0 6a927f4644 fix(lib): fix bugs in weeding 2022-12-29 15:50:59 +03:00
H1K0 e64e6f7f47 feat(lib): weeding also removes sasa with invalid file path 2022-12-29 15:32:25 +03:00
H1K0 32e73acd73 feat(lib): zero tanzaku ID is reserved for the special immutable FAVORITE tag 2022-12-29 15:22:13 +03:00
H1K0 3cea0b5fdb style(lib): code cleanup 2022-12-29 14:55:03 +03:00
H1K0 eb746994e3 chore: update version number in CMakeLists.txt 2022-12-29 14:42:25 +03:00
H1K0 dc5abcf9d9 feat(cli): add option -i to view database info 2022-12-28 22:01:48 +03:00
H1K0 7961554b69 docs: prepare for 0.1.3-dev release 2022-12-28 18:58:05 +03:00
H1K0 29df398a50 fix(cli): check if successfully saved database after weeding 2022-12-28 18:56:44 +03:00
H1K0 631716877b feat(cli): colorize success message 2022-12-28 18:47:25 +03:00
H1K0 07ee9d3c7a fix(cli): fix bug on initializing new database 2022-12-28 18:39:01 +03:00
H1K0 caa01e2fbc docs: add hyou definition 2022-12-28 18:23:28 +03:00
H1K0 57623cff61 perf(lib): tanabata struct holds the hyou last modification timestamps instead of bools 2022-12-28 18:22:16 +03:00
H1K0 873d6d487b perf(core): a little improvement in new kazari remove functions 2022-12-28 18:02:33 +03:00
H1K0 97c63aea1f perf(lib): use new kazari remove functions 2022-12-28 17:58:01 +03:00
H1K0 4724962cd3 feat(core): add functions to remove all kazari with a specific sasa/tanzaku ID 2022-12-28 17:05:09 +03:00
H1K0 d8c43c7855 perf(lib): do not save unchanged database files 2022-12-28 16:48:35 +03:00
H1K0 2d41da9a8c feat(cli): change style of table output 2022-12-28 15:34:21 +03:00
H1K0 45160154df chore: change minimum required cmake version to 3.16 2022-12-28 14:59:10 +03:00
H1K0 eeedbfa484 feat(cli): print human-readable datetime instead of Unix timestamp 2022-12-28 01:47:15 +03:00
H1K0 133ef0b3a5 fix(core): check every operation on files 2022-12-28 01:03:19 +03:00
H1K0 5eb4775867 style(cli): code cleanup 2022-12-28 00:14:55 +03:00
H1K0 575c277ff4 perf(cli): check if database is full before launching add menu 2022-12-27 23:50:23 +03:00
H1K0 5782b98550 style(lib): merge similar ifs 2022-12-27 22:59:59 +03:00
H1K0 54e598fd6d perf(lib): check if database is full before adding new record 2022-12-27 22:56:19 +03:00
H1K0 af1dcb2bf2 refactor: reorganize files 2022-12-27 22:47:57 +03:00
H1K0 e39003f4cc docs: add build guide 2022-12-27 21:41:55 +03:00
H1K0 9da4401406 chore: add build script 2022-12-27 21:35:29 +03:00
H1K0 b8a52790eb chore: change minimum required cmake version to 3.22 2022-12-27 20:44:01 +03:00
H1K0 7c773a7398 docs: prepare for 0.1.2-dev release 2022-12-26 11:53:22 +03:00
H1K0 4f57a32c77 perf(cli): correct error handling 2022-12-26 11:49:39 +03:00
H1K0 600b71ebf9 fix(cli): save database after adding/removing kazari 2022-12-26 11:48:32 +03:00
H1K0 219f5de50b fix(cli): print hex tanzaku ID 2022-12-25 13:55:33 +03:00
H1K0 227232d353 chore(ghp): set title and description 2022-12-25 13:48:21 +03:00
H1K0 3c1f0e64f9 chore(ghp): set theme 2022-12-25 13:27:52 +03:00
H1K0 30085b77f3 docs: prepare for 0.1.1-dev release 2022-12-25 13:21:58 +03:00
H1K0 24231a48ef docs(cli): correct help message 2022-12-25 13:09:45 +03:00
H1K0 828e4b5e81 refactor(cli): change config location to /etc/tfm/config 2022-12-25 12:27:41 +03:00
H1K0 bb5e5f209e fix(cli): a little bug fix 2022-12-25 12:27:21 +03:00
H1K0 d148ca3e19 perf(cli): add current database location output in help message 2022-12-25 12:19:01 +03:00
H1K0 72c0fa001f fix(cli): fix config and path problems 2022-12-25 02:57:57 +03:00
H1K0 bb54c2c919 docs: prepare for 0.1.0-dev release 2022-12-25 02:55:57 +03:00
H1K0 094d76cb8c fix(cli): if both -s and -u options are set then unset both of them 2022-12-25 02:11:59 +03:00
H1K0 02aeadf0f7 style(cli): print error message on fail to init/open database 2022-12-25 01:56:12 +03:00
H1K0 535e077b6c style(core): correct one comment in core.h 2022-12-25 01:38:48 +03:00
H1K0 6b5185200e init(cli): add command line interface 2022-12-25 01:21:48 +03:00
H1K0 b5b2cc7fae style(lib): a bit of code cleanup 2022-12-25 01:18:11 +03:00
H1K0 9cbb34512b fix(lib): minor fixes 2022-12-25 00:59:04 +03:00
H1K0 16c577ecb8 fix(lib): remove 0 instead of 1 when trying to add existing kazari 2022-12-25 00:19:23 +03:00
H1K0 abefaed60c fix(lib): fix database weed function 2022-12-24 23:55:07 +03:00
H1K0 7261f88dbd fix(core): reopen file on load/save 2022-12-24 20:46:39 +03:00
H1K0 3bca700ab6 perf(lib): some minor improvements 2022-12-24 20:02:50 +03:00
H1K0 159379cad2 perf(lib): remove all console outputs 2022-12-24 19:34:24 +03:00
H1K0 0547ef6abb perf(core): remove all console outputs 2022-12-24 19:25:07 +03:00
H1K0 f29858361c perf(core): improve remove functions 2022-12-24 17:33:46 +03:00
H1K0 f41a1d66f9 style(lib): correct error messages 2022-12-24 16:45:59 +03:00
H1K0 8dd4361846 fix(core): correct error handling 2022-12-24 00:19:46 +03:00
H1K0 83fe7fcb3c style(lib): a bit of code cleanup 2022-12-24 00:17:45 +03:00
H1K0 98d42f2653 feat(lib): add file path checking 2022-12-23 20:00:04 +03:00
H1K0 629e8e5037 fix(lib): check if kazari exists in kazari add function 2022-12-23 17:35:38 +03:00
H1K0 9ae1a2a8dc fix(lib): check for hole ID in get functions 2022-12-23 17:19:10 +03:00
H1K0 b9873a803c fix(core): a little bug fix 2022-12-23 16:59:55 +03:00
H1K0 d404378251 fix(core): change hole constants to extern class 2022-12-23 16:53:52 +03:00
H1K0 fca32fa558 feat(lib): add get tanzaku list by sasa and get sasa list by tanzaku functions 2022-12-23 16:33:10 +03:00
H1K0 26b4746f41 fix(core): change hole constants to static class 2022-12-23 16:22:48 +03:00
H1K0 f8424a70ca feat(lib): add sasa/tanzaku get functions 2022-12-23 15:45:56 +03:00
H1K0 177799964a fix(lib): update sasa/tanzaku add/remove functions 2022-12-23 15:35:18 +03:00
H1K0 91a53fa703 feat(core): remove tanzaku alias field 2022-12-23 15:14:17 +03:00
H1K0 68c17f0785 style(core): correct error messages 2022-12-23 14:46:06 +03:00
H1K0 1f454f6770 feat(core): leave remove functions only by ID 2022-12-23 14:29:33 +03:00
H1K0 cd9dddc6cd refactor(core): rename "content" field to "database" 2022-12-23 14:23:22 +03:00
H1K0 6739d60c0d refactor(core): define hole sasa/tanzaku/kazari constants 2022-12-23 14:01:17 +03:00
H1K0 cee515da52 refactor(core): change hole ID to -1 2022-12-23 13:21:04 +03:00
H1K0 99d6a2a95c style(core): correct comments in core.h 2022-12-23 13:16:27 +03:00
H1K0 c043bba958 perf(core): add hole management 2022-12-22 17:02:24 +03:00
H1K0 3660e2eb44 fix(core): some significant fixes 2022-12-22 15:19:48 +03:00
H1K0 4b6c328198 style(core): edit comments in core.h 2022-12-22 14:23:46 +03:00
H1K0 e1137785b6 style(core): a bit of code cleanup 2022-12-22 13:51:15 +03:00
H1K0 233e80ceda fix(lib): some fixes 2022-12-22 00:31:05 +03:00
H1K0 8c2eff54a7 feat(lib): add core kazari functions 2022-12-22 00:29:23 +03:00
H1K0 3bcbbdfeaa feat(lib): add core tanzaku functions 2022-12-22 00:26:57 +03:00
H1K0 4f770781df feat(lib): add core sasa functions 2022-12-22 00:22:56 +03:00
H1K0 b6c4837ffe init(lib): add Tanabata FM lib 2022-12-21 22:34:06 +03:00
H1K0 f075769648 style(core): edit comments in core.h 2022-12-21 17:55:36 +03:00
H1K0 6f02cdae10 feat(core): add new remove functions
Now sasa can be removed not only by ID, but also by file path, tanzaku can also be removed by name and alias.
2022-12-21 16:16:46 +03:00
H1K0 587415dc78 init(core): add core 2022-12-21 18:07:59 +03:00
110 changed files with 6258 additions and 1528 deletions
+57
View File
@@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.16)
project(Tanabata
VERSION 2.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 CLI client app
add_executable(tdb tdbms/cli/tdbms-cli.c ${TDBMS_CLIENT_SRC})
# Tanabata CLI app
add_executable(tfm ${CLI_SRC})
add_executable(test test.c ${TDBMS_CLIENT_SRC})
+64
View File
@@ -0,0 +1,64 @@
<h1 align="center">🎋 Tanabata Project 🎋</h1>
---
[![Release version][release-shield]][release-link]
## Contents
- [About](#about)
- [Glossary](#glossary)
- [Tanabata library](#tanabata-library)
- [Tanabata DBMS](#tanabata-dbms)
- [Tanabata FM](#tanabata-fm)
## About
Tanabata (_jp._ 七夕) is 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 Project is a software project that will let you enjoy the Tanabata festival. It allows you to store and organize your data as _sasa_ bamboos, on which you can hang almost any number of _tanzaku_, just like adding tags on it.
## Glossary
**Tanabata (_jp._ 七夕)** is a software package project for storing information and organizing it with tags.
**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.
**Sasahyou (_jp._ 笹表)** is a table of sasa.
**Sappyou (_jp._ 冊表)** is a table of tanzaku.
**Shoppyou (_jp._ 飾表)** is a table of kazari.
**TDB (Tanabata DataBase)** is a relational database that consists of three tables: _sasahyou_, _sappyou_ and _shoppyou_.
**TDBMS (Tanabata DataBase Management System)** is a management system for TDBs.
**TFM (Tanabata File Manager)** is a TDBMS-powered file manager.
**Tweb (Tanabata web)** is the web user interface for TDBMS and TFM.
## Tanabata library
Tanabata library is a C library for TDB operations. Documentation coming soon...
## Tanabata DBMS
Tanabata Database Management System is the management system for Tanabata databases. Documentation coming soon...
## Tanabata FM
Tanabata File Manager is the TDBMS-powered file manager. Full documentation is [here](docs/fm.md).
---
<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
+5
View File
@@ -0,0 +1,5 @@
title: Tanabata FM
description: A file manager that will let you enjoy the Tanabata festival!
remote_theme: pages-themes/merlot@v0.2.0
plugins:
- jekyll-remote-theme
-19
View File
@@ -1,19 +0,0 @@
module tanabata
go 1.23.0
toolchain go1.23.10
require github.com/jackc/pgx/v5 v5.7.5
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx v3.6.2+incompatible // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/text v0.24.0 // indirect
)
-32
View File
@@ -1,32 +0,0 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-121
View File
@@ -1,121 +0,0 @@
package domain
import (
"encoding/json"
"time"
"github.com/jackc/pgx/v5/pgtype"
)
type User struct {
Name string `json:"name"`
IsAdmin bool `json:"isAdmin"`
CanCreate bool `json:"canCreate"`
}
type MIME struct {
Name string `json:"name"`
Extension string `json:"extension"`
}
type (
CategoryCore struct {
ID *string `json:"id"`
Name *string `json:"name"`
Color *string `json:"color"`
}
CategoryItem struct {
CategoryCore
}
CategoryFull struct {
CategoryCore
CreatedAt time.Time `json:"createdAt"`
Creator User `json:"creator"`
Notes pgtype.Text `json:"notes"`
}
)
type (
FileCore struct {
ID string `json:"id"`
Name pgtype.Text `json:"name"`
MIME MIME `json:"mime"`
}
FileItem struct {
FileCore
CreatedAt time.Time `json:"createdAt"`
Creator User `json:"creator"`
}
FileFull struct {
FileCore
CreatedAt time.Time `json:"createdAt"`
Creator User `json:"creator"`
Notes pgtype.Text `json:"notes"`
Metadata json.RawMessage `json:"metadata"`
Viewed int `json:"viewed"`
}
)
type (
TagCore struct {
ID string `json:"id"`
Name string `json:"name"`
Color pgtype.Text `json:"color"`
}
TagItem struct {
TagCore
Category CategoryCore `json:"category"`
}
TagFull struct {
TagCore
Category CategoryCore `json:"category"`
CreatedAt time.Time `json:"createdAt"`
Creator User `json:"creator"`
Notes pgtype.Text `json:"notes"`
UsedIncl int `json:"usedIncl"`
UsedExcl int `json:"usedExcl"`
}
)
type Autotag struct {
TriggerTag TagCore `json:"triggerTag"`
AddTag TagCore `json:"addTag"`
IsActive bool `json:"isActive"`
}
type (
PoolCore struct {
ID string `json:"id"`
Name string `json:"name"`
}
PoolItem struct {
PoolCore
}
PoolFull struct {
PoolCore
CreatedAt time.Time `json:"createdAt"`
Creator User `json:"creator"`
Notes pgtype.Text `json:"notes"`
Viewed int `json:"viewed"`
}
)
type Session struct {
ID int `json:"id"`
UserAgent string `json:"userAgent"`
StartedAt time.Time `json:"startedAt"`
ExpiresAt time.Time `json:"expiresAt"`
LastActivity time.Time `json:"lastActivity"`
}
type Pagination struct {
Total int `json:"total"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Count int `json:"count"`
}
type Slice[T any] struct {
Pagination Pagination `json:"pagination"`
Data []T `json:"data"`
}
-46
View File
@@ -1,46 +0,0 @@
package domain
type ErrorCode string
const (
// File errors
ErrFileNotFound ErrorCode = "FILE_NOT_FOUND"
ErrMIMENotSupported ErrorCode = "MIME_NOT_SUPPORTEDF"
// Tag errors
ErrTagNotFound ErrorCode = "TAG_NOT_FOUND"
// General errors
ErrValidation ErrorCode = "VALIDATION_ERROR"
ErrInternal ErrorCode = "INTERNAL_SERVER_ERROR"
)
type DomainError struct {
Err error `json:"-"`
Code ErrorCode `json:"code"`
Message string `json:"message"`
Details []any `json:"-"`
}
func (e *DomainError) Error() string {
if e.Err != nil {
return e.Message + ": " + e.Err.Error()
}
return e.Message
}
func NewDomainError(err error, code ErrorCode, details ...any) *DomainError {
return &DomainError{
Err: err,
Code: code,
Details: details,
}
}
func NewUnexpectedError(err error) *DomainError {
return &DomainError{
Err: err,
Code: ErrInternal,
Message: "An unexpected error occured",
}
}
-17
View File
@@ -1,17 +0,0 @@
package domain
import (
"context"
"encoding/json"
"time"
)
type FileRepository interface {
GetAccess(ctx context.Context, user_id int, file_id string) (canView, canEdit bool, domainErr *DomainError)
GetSlice(ctx context.Context, user_id int, filter, sort string, limit, offset int) (files Slice[FileItem], domainErr *DomainError)
Get(ctx context.Context, user_id int, file_id string) (file FileFull, domainErr *DomainError)
Add(ctx context.Context, user_id int, name, mime string, datetime time.Time, notes string, metadata json.RawMessage) (file FileCore, domainErr *DomainError)
Update(ctx context.Context, user_id int, file_id string, updates map[string]interface{}) (domainErr *DomainError)
Delete(ctx context.Context, user_id int, file_id string) (domainErr *DomainError)
GetTags(ctx context.Context, user_id int, file_id string) (tags []TagItem, domainErr *DomainError)
}
@@ -1,50 +0,0 @@
package postgres
import (
"context"
"fmt"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"tanabata/internal/domain"
)
// Initialize PostgreSQL database driver
func New(dbURL string) (*pgxpool.Pool, error) {
poolConfig, err := pgxpool.ParseConfig(dbURL)
if err != nil {
return nil, fmt.Errorf("error while parsing connection string: %w", err)
}
poolConfig.MaxConns = 100
poolConfig.MinConns = 0
poolConfig.MaxConnLifetime = time.Hour
poolConfig.HealthCheckPeriod = 30 * time.Second
db, err := pgxpool.NewWithConfig(context.Background(), poolConfig)
if err != nil {
return nil, fmt.Errorf("error while initializing DB connections pool: %w", err)
}
return db, nil
}
// Transaction wrapper
func transaction(ctx context.Context, db *pgxpool.Pool, handler func(context.Context, pgx.Tx) *domain.DomainError) (domainErr *domain.DomainError) {
tx, err := db.Begin(ctx)
if err != nil {
domainErr = domain.NewUnexpectedError(err)
return
}
domainErr = handler(ctx, tx)
if domainErr != nil {
tx.Rollback(ctx)
return
}
err = tx.Commit(ctx)
if err != nil {
domainErr = domain.NewUnexpectedError(err)
}
return
}
@@ -1,313 +0,0 @@
package postgres
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
"tanabata/internal/domain"
)
type FileRepository struct {
db *pgxpool.Pool
}
func NewFileRepository(db *pgxpool.Pool) *FileRepository {
return &FileRepository{db: db}
}
// Check if user can view file
func (s *FileRepository) GetAccess(ctx context.Context, user_id int, file_id string) (canView, canEdit bool, domainErr *domain.DomainError) {
row := s.db.QueryRow(ctx, `
SELECT
COALESCE(a.view, FALSE) OR f.creator_id=$1 OR COALESCE(u.is_admin, FALSE),
COALESCE(a.edit, FALSE) OR f.creator_id=$1 OR COALESCE(u.is_admin, FALSE)
FROM data.files f
LEFT JOIN acl.files a ON a.file_id=f.id AND a.user_id=$1
LEFT JOIN system.users u ON u.id=$1
WHERE f.id=$2
`, user_id, file_id)
err := row.Scan(&canView, &canEdit)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
domainErr = domain.NewDomainError(err, domain.ErrFileNotFound, file_id)
return
}
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && (pgErr.Code == "22P02" || pgErr.Code == "22007") {
domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message)
return
}
domainErr = domain.NewUnexpectedError(err)
}
return
}
// Get a set of files
func (s *FileRepository) GetSlice(ctx context.Context, user_id int, filter, sort string, limit, offset int) (files domain.Slice[domain.FileItem], domainErr *domain.DomainError) {
filterCond, err := filterToSQL(filter)
if err != nil {
domainErr = domain.NewDomainError(err, domain.ErrValidation, "filter", err.Error())
return
}
sortExpr, err := sortToSQL(sort)
if err != nil {
domainErr = domain.NewDomainError(err, domain.ErrValidation, "sort param", err.Error())
return
}
// prepare query
query := `
SELECT
f.id,
f.name,
m.name,
m.extension,
uuid_extract_timestamp(f.id),
u.name,
u.is_admin
FROM data.files f
JOIN system.mime m ON m.id=f.mime_id
JOIN system.users u ON u.id=f.creator_id
WHERE NOT f.is_deleted AND (f.creator_id=$1 OR (SELECT view FROM acl.files WHERE file_id=f.id AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1)) AND
`
query += filterCond
queryCount := query
query += sortExpr
if limit >= 0 {
query += fmt.Sprintf(" LIMIT %d", limit)
}
if offset > 0 {
query += fmt.Sprintf(" OFFSET %d", offset)
}
// execute query
domainErr = transaction(ctx, s.db, func(ctx context.Context, tx pgx.Tx) (domainErr *domain.DomainError) {
rows, err := tx.Query(ctx, query, user_id)
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
switch pgErr.Code {
case "22P02", "22007":
domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message)
return
case "42P10":
domainErr = domain.NewDomainError(err, domain.ErrValidation, "sort field", sort[1:])
return
}
}
domainErr = domain.NewUnexpectedError(err)
return
}
defer rows.Close()
count := 0
for rows.Next() {
var file domain.FileItem
err = rows.Scan(&file.ID, &file.Name, &file.MIME.Name, &file.MIME.Extension, &file.CreatedAt, &file.Creator.Name, &file.Creator.IsAdmin)
if err != nil {
domainErr = domain.NewUnexpectedError(err)
return
}
files.Data = append(files.Data, file)
count++
}
err = rows.Err()
if err != nil {
domainErr = domain.NewUnexpectedError(err)
return
}
files.Pagination.Limit = limit
files.Pagination.Offset = offset
files.Pagination.Count = count
row := tx.QueryRow(ctx, fmt.Sprintf("SELECT COUNT(*) FROM (%s) tmp", queryCount), user_id)
err = row.Scan(&files.Pagination.Total)
if err != nil {
domainErr = domain.NewUnexpectedError(err)
}
return
})
return
}
// Get file
func (s *FileRepository) Get(ctx context.Context, user_id int, file_id string) (file domain.FileFull, domainErr *domain.DomainError) {
row := s.db.QueryRow(ctx, `
SELECT
f.id,
f.name,
m.name,
m.extension,
uuid_extract_timestamp(f.id),
u.name,
u.is_admin,
f.notes,
f.metadata,
(SELECT COUNT(*) FROM activity.file_views fv WHERE fv.file_id=$2 AND fv.user_id=$1)
FROM data.files f
JOIN system.mime m ON m.id=f.mime_id
JOIN system.users u ON u.id=f.creator_id
WHERE NOT f.is_deleted AND f.id=$2 AND (f.creator_id=$1 OR (SELECT view FROM acl.files WHERE file_id=$2 AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1))
`, user_id, file_id)
err := row.Scan(&file.ID, &file.Name, &file.MIME.Name, &file.MIME.Extension, &file.CreatedAt, &file.Creator.Name, &file.Creator.IsAdmin, &file.Notes, &file.Metadata, &file.Viewed)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
domainErr = domain.NewDomainError(err, domain.ErrFileNotFound, file_id)
return
}
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && (pgErr.Code == "22P02" || pgErr.Code == "22007") {
domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message)
return
}
domainErr = domain.NewUnexpectedError(err)
return
}
return
}
// Add file
func (s *FileRepository) Add(ctx context.Context, user_id int, name, mime string, datetime time.Time, notes string, metadata json.RawMessage) (file domain.FileCore, domainErr *domain.DomainError) {
var mime_id int
var extension string
row := s.db.QueryRow(ctx, "SELECT id, extension FROM system.mime WHERE name=$1", mime)
err := row.Scan(&mime_id, &extension)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
domainErr = domain.NewDomainError(err, domain.ErrMIMENotSupported, mime)
return
}
domainErr = domain.NewUnexpectedError(err)
return
}
row = s.db.QueryRow(ctx, `
INSERT INTO data.files (name, mime_id, datetime, creator_id, notes, metadata)
VALUES (NULLIF($1, ''), $2, $3, $4, NULLIF($5 ,''), $6)
RETURNING id
`, name, mime_id, datetime, user_id, notes, metadata)
err = row.Scan(&file.ID)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && (pgErr.Code == "22P02" || pgErr.Code == "22007") {
domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message)
return
}
domainErr = domain.NewUnexpectedError(err)
return
}
file.Name.String = name
file.Name.Valid = (name != "")
file.MIME.Name = mime
file.MIME.Extension = extension
return
}
// Update file
func (s *FileRepository) Update(ctx context.Context, user_id int, file_id string, updates map[string]interface{}) (domainErr *domain.DomainError) {
if len(updates) == 0 {
domainErr = domain.NewDomainError(errors.ErrUnsupported, domain.ErrValidation, "request body", "no fields provided for update")
return
}
writableFields := map[string]bool{
"name": true,
"datetime": true,
"notes": true,
"metadata": true,
}
query := "UPDATE data.files SET"
newValues := []interface{}{user_id}
count := 2
for field, value := range updates {
if !writableFields[field] {
domainErr = domain.NewDomainError(errors.ErrUnsupported, domain.ErrValidation, "field", field)
return
}
query += fmt.Sprintf(" %s=NULLIF($%d, '')", field, count)
newValues = append(newValues, value)
count++
}
query += fmt.Sprintf(
" WHERE id=$%d AND (creator_id=$1 OR (SELECT edit FROM acl.files WHERE file_id=$%d AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1))",
count, count)
newValues = append(newValues, file_id)
commandTag, err := s.db.Exec(ctx, query, newValues...)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && (pgErr.Code == "22P02" || pgErr.Code == "22007") {
domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message)
return
}
domainErr = domain.NewUnexpectedError(err)
return
}
if commandTag.RowsAffected() == 0 {
domainErr = domain.NewDomainError(err, domain.ErrFileNotFound, file_id)
return
}
return
}
// Delete file
func (s *FileRepository) Delete(ctx context.Context, user_id int, file_id string) (domainErr *domain.DomainError) {
commandTag, err := s.db.Exec(ctx,
"DELETE FROM data.files WHERE id=$2 AND (creator_id=$1 OR (SELECT edit FROM acl.files WHERE file_id=$2 AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1))",
user_id, file_id)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && (pgErr.Code == "22P02" || pgErr.Code == "22007") {
domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message)
return
}
domainErr = domain.NewUnexpectedError(err)
return
}
if commandTag.RowsAffected() == 0 {
domainErr = domain.NewDomainError(err, domain.ErrFileNotFound, file_id)
return
}
return
}
func (s *FileRepository) GetTags(ctx context.Context, user_id int, file_id string) (tags []domain.TagItem, domainErr *domain.DomainError) {
rows, err := s.db.Query(ctx, `
SELECT
t.id,
t.name,
t.color,
c.id,
c.name,
c.color
FROM data.tags t
LEFT JOIN data.categories c ON c.id=t.category_id
JOIN data.file_tag ft ON ft.tag_id=t.id AND ft.file_id=$2
JOIN data.files f ON f.id=$2
WHERE NOT f.is_deleted AND (f.creator_id=$1 OR (SELECT view FROM acl.files WHERE file_id=$2 AND user_id=$1) OR (SELECT is_admin FROM system.users WHERE id=$1))
`, user_id, file_id)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && (pgErr.Code == "22P02" || pgErr.Code == "22007") {
domainErr = domain.NewDomainError(err, domain.ErrValidation, "format", pgErr.Message)
return
}
domainErr = domain.NewUnexpectedError(err)
return
}
defer rows.Close()
for rows.Next() {
var tag domain.TagItem
err = rows.Scan(&tag.ID, &tag.Name, &tag.Color, &tag.Category.ID, &tag.Category.Name, &tag.Category.Color)
if err != nil {
domainErr = domain.NewUnexpectedError(err)
return
}
tags = append(tags, tag)
}
err = rows.Err()
if err != nil {
domainErr = domain.NewUnexpectedError(err)
}
return
}
@@ -1,82 +0,0 @@
package postgres
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
// Handle database error
func handleDBError(errIn error) (statusCode int, err error) {
if errIn == nil {
statusCode = http.StatusOK
return
}
if errors.Is(errIn, pgx.ErrNoRows) {
err = fmt.Errorf("not found")
statusCode = http.StatusNotFound
return
}
var pgErr *pgconn.PgError
if errors.As(errIn, &pgErr) {
switch pgErr.Code {
case "22P02", "22007": // Invalid data format
err = fmt.Errorf("%s", pgErr.Message)
statusCode = http.StatusBadRequest
return
case "23505": // Unique constraint violation
err = fmt.Errorf("already exists")
statusCode = http.StatusConflict
return
}
}
return http.StatusInternalServerError, errIn
}
// Convert "filter" URL param to SQL "WHERE" condition
func filterToSQL(filter string) (sql string, err error) {
// filterTokens := strings.Split(string(filter), ";")
sql = "(true)"
return
}
// Convert "sort" URL param to SQL "ORDER BY"
func sortToSQL(sort string) (sql string, err error) {
if sort == "" {
return
}
sortOptions := strings.Split(sort, ",")
sql = " ORDER BY "
for i, sortOption := range sortOptions {
sortOrder := sortOption[:1]
sortColumn := sortOption[1:]
// parse sorting order marker
switch sortOrder {
case "+":
sortOrder = "ASC"
case "-":
sortOrder = "DESC"
default:
err = fmt.Errorf("invalid sorting order mark: %q", sortOrder)
return
}
// validate sorting column
var n int
n, err = strconv.Atoi(sortColumn)
if err != nil || n < 0 {
err = fmt.Errorf("invalid sorting column: %q", sortColumn)
return
}
// add sorting option to query
if i > 0 {
sql += ","
}
sql += fmt.Sprintf("%s %s NULLS LAST", sortColumn, sortOrder)
}
return
}
@@ -1,44 +0,0 @@
package rest
import (
"fmt"
"net/http"
"tanabata/internal/domain"
)
type ErrorResponse struct {
Error string `json:"error"`
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
type ErrorMapper struct{}
func (m *ErrorMapper) MapError(err domain.DomainError) (int, ErrorResponse) {
switch err.Code {
case domain.ErrFileNotFound:
return http.StatusNotFound, ErrorResponse{
Error: "Not Found",
Code: string(err.Code),
Message: fmt.Sprintf("File %q not found", err.Details...),
}
case domain.ErrMIMENotSupported:
return http.StatusNotFound, ErrorResponse{
Error: "MIME not supported",
Code: string(err.Code),
Message: fmt.Sprintf("MIME not supported: %q", err.Details...),
}
case domain.ErrValidation:
return http.StatusNotFound, ErrorResponse{
Error: "Bad Request",
Code: string(err.Code),
Message: fmt.Sprintf("Invalid %s: %s", err.Details...),
}
}
return http.StatusInternalServerError, ErrorResponse{
Error: "Internal Server Error",
Code: string(err.Code),
Message: "An unexpected error occured",
}
}
+25
View File
@@ -0,0 +1,25 @@
#!/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"
-592
View File
@@ -1,592 +0,0 @@
--
-- PostgreSQL database dump
--
-- Dumped from database version 14.18 (Ubuntu 14.18-0ubuntu0.22.04.1)
-- Dumped by pg_dump version 17.4
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: acl; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA acl;
--
-- Name: activity; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA activity;
--
-- Name: data; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA data;
--
-- Name: public; Type: SCHEMA; Schema: -; Owner: -
--
-- *not* creating schema, since initdb creates it
--
-- Name: system; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA system;
--
-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
--
-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
--
-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public;
--
-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)';
--
-- Name: add_file_to_tag_recursive(uuid, uuid); Type: FUNCTION; Schema: data; Owner: -
--
CREATE FUNCTION data.add_file_to_tag_recursive(f_id uuid, t_id uuid) RETURNS SETOF uuid
LANGUAGE plpgsql
AS $$
DECLARE
tmp uuid;
tt_id uuid;
ttt_id uuid;
BEGIN
INSERT INTO data.file_tag VALUES (f_id, t_id) ON CONFLICT DO NOTHING RETURNING tag_id INTO tmp;
IF tmp IS NULL THEN
RETURN;
END IF;
RETURN NEXT t_id;
FOR tt_id IN
SELECT a.add_tag_id FROM data.autotags a WHERE a.trigger_tag_id=t_id AND a.is_active
LOOP
FOR ttt_id IN SELECT data.add_file_to_tag_recursive(f_id, tt_id)
LOOP
RETURN NEXT ttt_id;
END LOOP;
END LOOP;
END;
$$;
--
-- Name: uuid_extract_timestamp(uuid); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.uuid_extract_timestamp(uuid_val uuid) RETURNS timestamp with time zone
LANGUAGE sql IMMUTABLE
AS $$
SELECT to_timestamp(
('x' || LEFT(REPLACE(uuid_val::TEXT, '-', ''), 12))::BIT(48)::BIGINT
/ 1000.0
);
$$;
--
-- Name: uuid_v7(timestamp with time zone); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.uuid_v7(cts timestamp with time zone DEFAULT clock_timestamp()) RETURNS uuid
LANGUAGE plpgsql
AS $$
DECLARE
state text = current_setting('uuidv7.old_tp',true);
old_tp text = split_part(state, ':',1);
base int = coalesce(nullif(split_part(state,':',4),'')::int,(random()*16777215/2-1)::int);
tp text;
entropy text;
seq text=base;
seqn int=split_part(state,':',2);
ver text = coalesce(split_part(state,':',3),to_hex(8+(random()*3)::int));
BEGIN
base = (random()*16777215/2-1)::int;
tp = lpad(to_hex(floor(extract(epoch from cts)*1000)::int8),12,'0')||'7';
if tp is distinct from old_tp then
old_tp = tp;
ver = to_hex(8+(random()*3)::int);
base = (random()*16777215/2-1)::int;
seqn = base;
else
seqn = seqn+(random()*1000)::int;
end if;
perform set_config('uuidv7.old_tp',old_tp||':'||seqn||':'||ver||':'||base, false);
entropy = md5(gen_random_uuid()::text);
seq = lpad(to_hex(seqn),6,'0');
return (tp || substring(seq from 1 for 3) || ver || substring(seq from 4 for 3) ||
substring(entropy from 1 for 12))::uuid;
END
$$;
SET default_table_access_method = heap;
--
-- Name: categories; Type: TABLE; Schema: acl; Owner: -
--
CREATE TABLE acl.categories (
user_id smallint NOT NULL,
category_id uuid NOT NULL,
view boolean NOT NULL,
edit boolean NOT NULL
);
--
-- Name: files; Type: TABLE; Schema: acl; Owner: -
--
CREATE TABLE acl.files (
user_id smallint NOT NULL,
file_id uuid NOT NULL,
view boolean NOT NULL,
edit boolean NOT NULL
);
--
-- Name: pools; Type: TABLE; Schema: acl; Owner: -
--
CREATE TABLE acl.pools (
user_id smallint NOT NULL,
pool_id uuid NOT NULL,
view boolean NOT NULL,
edit boolean NOT NULL
);
--
-- Name: tags; Type: TABLE; Schema: acl; Owner: -
--
CREATE TABLE acl.tags (
user_id smallint NOT NULL,
tag_id uuid NOT NULL,
view boolean NOT NULL,
edit boolean NOT NULL
);
--
-- Name: file_views; Type: TABLE; Schema: activity; Owner: -
--
CREATE TABLE activity.file_views (
file_id uuid NOT NULL,
"timestamp" timestamp with time zone NOT NULL,
user_id smallint NOT NULL
);
--
-- Name: pool_views; Type: TABLE; Schema: activity; Owner: -
--
CREATE TABLE activity.pool_views (
pool_id uuid NOT NULL,
"timestamp" timestamp with time zone NOT NULL,
user_id smallint NOT NULL
);
--
-- Name: sessions; Type: TABLE; Schema: activity; Owner: -
--
CREATE TABLE activity.sessions (
id integer NOT NULL,
token text NOT NULL,
user_id smallint NOT NULL,
user_agent character varying(256) NOT NULL,
started_at timestamp with time zone DEFAULT statement_timestamp() NOT NULL,
expires_at timestamp with time zone,
last_activity timestamp with time zone DEFAULT statement_timestamp() NOT NULL
);
--
-- Name: sessions_id_seq; Type: SEQUENCE; Schema: activity; Owner: -
--
CREATE SEQUENCE activity.sessions_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: sessions_id_seq; Type: SEQUENCE OWNED BY; Schema: activity; Owner: -
--
ALTER SEQUENCE activity.sessions_id_seq OWNED BY activity.sessions.id;
--
-- Name: tag_uses; Type: TABLE; Schema: activity; Owner: -
--
CREATE TABLE activity.tag_uses (
tag_id uuid NOT NULL,
"timestamp" timestamp with time zone NOT NULL,
user_id smallint NOT NULL,
included boolean NOT NULL
);
--
-- Name: autotags; Type: TABLE; Schema: data; Owner: -
--
CREATE TABLE data.autotags (
trigger_tag_id uuid NOT NULL,
add_tag_id uuid NOT NULL,
is_active boolean DEFAULT true NOT NULL
);
--
-- Name: categories; Type: TABLE; Schema: data; Owner: -
--
CREATE TABLE data.categories (
id uuid DEFAULT public.uuid_v7() NOT NULL,
name character varying(256) NOT NULL,
notes text DEFAULT ''::text NOT NULL,
color character(6),
creator_id smallint NOT NULL
);
--
-- Name: file_pool; Type: TABLE; Schema: data; Owner: -
--
CREATE TABLE data.file_pool (
file_id uuid NOT NULL,
pool_id uuid NOT NULL,
number smallint NOT NULL
);
--
-- Name: file_tag; Type: TABLE; Schema: data; Owner: -
--
CREATE TABLE data.file_tag (
file_id uuid NOT NULL,
tag_id uuid NOT NULL
);
--
-- Name: files; Type: TABLE; Schema: data; Owner: -
--
CREATE TABLE data.files (
id uuid DEFAULT public.uuid_v7() NOT NULL,
name character varying(256),
mime_id smallint NOT NULL,
datetime timestamp with time zone DEFAULT clock_timestamp() NOT NULL,
notes text,
metadata jsonb NOT NULL,
creator_id smallint NOT NULL,
is_deleted boolean DEFAULT false NOT NULL
);
--
-- Name: pools; Type: TABLE; Schema: data; Owner: -
--
CREATE TABLE data.pools (
id uuid DEFAULT public.uuid_v7() NOT NULL,
name character varying(256) NOT NULL,
notes text,
creator_id smallint NOT NULL
);
--
-- Name: tags; Type: TABLE; Schema: data; Owner: -
--
CREATE TABLE data.tags (
id uuid DEFAULT public.uuid_v7() NOT NULL,
name character varying(256) NOT NULL,
notes text,
color character(6),
category_id uuid,
creator_id smallint NOT NULL
);
--
-- Name: mime; Type: TABLE; Schema: system; Owner: -
--
CREATE TABLE system.mime (
id smallint NOT NULL,
name character varying(127) NOT NULL,
extension character varying(16) NOT NULL
);
--
-- Name: mime_id_seq; Type: SEQUENCE; Schema: system; Owner: -
--
CREATE SEQUENCE system.mime_id_seq
AS smallint
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: mime_id_seq; Type: SEQUENCE OWNED BY; Schema: system; Owner: -
--
ALTER SEQUENCE system.mime_id_seq OWNED BY system.mime.id;
--
-- Name: users; Type: TABLE; Schema: system; Owner: -
--
CREATE TABLE system.users (
id smallint NOT NULL,
name character varying(32) NOT NULL,
password text NOT NULL,
is_admin boolean DEFAULT false NOT NULL,
can_create boolean DEFAULT false NOT NULL
);
--
-- Name: users_id_seq; Type: SEQUENCE; Schema: system; Owner: -
--
CREATE SEQUENCE system.users_id_seq
AS smallint
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: system; Owner: -
--
ALTER SEQUENCE system.users_id_seq OWNED BY system.users.id;
--
-- Name: sessions id; Type: DEFAULT; Schema: activity; Owner: -
--
ALTER TABLE ONLY activity.sessions ALTER COLUMN id SET DEFAULT nextval('activity.sessions_id_seq'::regclass);
--
-- Name: mime id; Type: DEFAULT; Schema: system; Owner: -
--
ALTER TABLE ONLY system.mime ALTER COLUMN id SET DEFAULT nextval('system.mime_id_seq'::regclass);
--
-- Name: users id; Type: DEFAULT; Schema: system; Owner: -
--
ALTER TABLE ONLY system.users ALTER COLUMN id SET DEFAULT nextval('system.users_id_seq'::regclass);
--
-- Name: categories categories_pkey; Type: CONSTRAINT; Schema: acl; Owner: -
--
ALTER TABLE ONLY acl.categories
ADD CONSTRAINT categories_pkey PRIMARY KEY (user_id, category_id);
--
-- Name: files files_pkey; Type: CONSTRAINT; Schema: acl; Owner: -
--
ALTER TABLE ONLY acl.files
ADD CONSTRAINT files_pkey PRIMARY KEY (user_id, file_id);
--
-- Name: pools pools_pkey; Type: CONSTRAINT; Schema: acl; Owner: -
--
ALTER TABLE ONLY acl.pools
ADD CONSTRAINT pools_pkey PRIMARY KEY (user_id, pool_id);
--
-- Name: tags tags_pkey; Type: CONSTRAINT; Schema: acl; Owner: -
--
ALTER TABLE ONLY acl.tags
ADD CONSTRAINT tags_pkey PRIMARY KEY (user_id, tag_id);
--
-- Name: file_views file_views_pkey; Type: CONSTRAINT; Schema: activity; Owner: -
--
ALTER TABLE ONLY activity.file_views
ADD CONSTRAINT file_views_pkey PRIMARY KEY (file_id, "timestamp", user_id);
--
-- Name: pool_views pool_views_pkey; Type: CONSTRAINT; Schema: activity; Owner: -
--
ALTER TABLE ONLY activity.pool_views
ADD CONSTRAINT pool_views_pkey PRIMARY KEY (pool_id, "timestamp", user_id);
--
-- Name: sessions sessions_pkey; Type: CONSTRAINT; Schema: activity; Owner: -
--
ALTER TABLE ONLY activity.sessions
ADD CONSTRAINT sessions_pkey PRIMARY KEY (id);
--
-- Name: tag_uses tag_uses_pkey; Type: CONSTRAINT; Schema: activity; Owner: -
--
ALTER TABLE ONLY activity.tag_uses
ADD CONSTRAINT tag_uses_pkey PRIMARY KEY (tag_id, "timestamp", user_id);
--
-- Name: autotags autotags_pkey; Type: CONSTRAINT; Schema: data; Owner: -
--
ALTER TABLE ONLY data.autotags
ADD CONSTRAINT autotags_pkey PRIMARY KEY (trigger_tag_id, add_tag_id);
--
-- Name: categories categories_pkey; Type: CONSTRAINT; Schema: data; Owner: -
--
ALTER TABLE ONLY data.categories
ADD CONSTRAINT categories_pkey PRIMARY KEY (id);
--
-- Name: file_pool file_pool_pkey; Type: CONSTRAINT; Schema: data; Owner: -
--
ALTER TABLE ONLY data.file_pool
ADD CONSTRAINT file_pool_pkey PRIMARY KEY (file_id, pool_id, number);
--
-- Name: file_tag file_tag_pkey; Type: CONSTRAINT; Schema: data; Owner: -
--
ALTER TABLE ONLY data.file_tag
ADD CONSTRAINT file_tag_pkey PRIMARY KEY (file_id, tag_id);
--
-- Name: files files_pkey; Type: CONSTRAINT; Schema: data; Owner: -
--
ALTER TABLE ONLY data.files
ADD CONSTRAINT files_pkey PRIMARY KEY (id);
--
-- Name: pools pools_pkey; Type: CONSTRAINT; Schema: data; Owner: -
--
ALTER TABLE ONLY data.pools
ADD CONSTRAINT pools_pkey PRIMARY KEY (id);
--
-- Name: tags tags_pkey; Type: CONSTRAINT; Schema: data; Owner: -
--
ALTER TABLE ONLY data.tags
ADD CONSTRAINT tags_pkey PRIMARY KEY (id);
--
-- Name: mime mime_pkey; Type: CONSTRAINT; Schema: system; Owner: -
--
ALTER TABLE ONLY system.mime
ADD CONSTRAINT mime_pkey PRIMARY KEY (id);
--
-- Name: users users_pkey; Type: CONSTRAINT; Schema: system; Owner: -
--
ALTER TABLE ONLY system.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
--
-- PostgreSQL database dump complete
--
-212
View File
@@ -1,212 +0,0 @@
@startuml Tanabata File Manager entity relationship diagram
' skinparam linetype ortho
' ========== SYSTEM ==========
entity "system.users" as usr {
* id : smallserial <<generated>>
--
* name : varchar(32)
* password : text
* is_admin : boolean
* can_create : boolean
}
entity "system.mime" as mime {
* id : smallserial <<generated>>
--
* name : varchar(127)
* extension : varchar(16)
}
' ========== DATA ==========
entity "data.categories" as cty {
* id : uuid <<generated>>
--
* name : varchar(256)
notes : text
color : char(6)
' * created_at : timestamptz <<generated>>
* creator_id : smallint
' * is_private : boolean
}
cty::creator_id }o--|| usr::id
entity "data.files" as fle {
* id : uuid <<generated>>
--
name : varchar(256)
* mime_id : smallint
* datetime : timestamptz
notes : text
* metadata : jsonb
' * created_at : timestamptz <<generated>>
* creator_id : smallint
' * is_private : boolean
* is_deleted : boolean
}
fle::mime_id }o--|| mime::id
fle::creator_id }o--|| usr::id
entity "data.tags" as tag {
* id : uuid <<generated>>
--
* name : varchar(256)
notes : text
color : char(6)
category_id : uuid
' * created_at : timestamptz <<generated>>
* creator_id : smallint
' * is_private : boolean
}
tag::category_id }o--o| cty::id
tag::creator_id }o--|| usr::id
entity "data.file_tag" as ft {
* file_id : uuid
* tag_id : uuid
}
ft::file_id }o--|| fle::id
ft::tag_id }o--|| tag::id
entity "data.autotags" as atg {
* trigger_tag_id : uuid
* add_tag_id : uuid
--
* is_active : boolean
}
atg::trigger_tag_id }o--|| tag::id
atg::add_tag_id }o--|| tag::id
entity "data.pools" as pool {
* id : uuid <<generated>>
--
* name : varchar(256)
notes : text
' parent_id : uuid
' * created_at : timestamptz
* creator_id : smallint
' * is_private : boolean
}
pool::creator_id }o--|| usr::id
' pool::parent_id }o--o| pool::id
entity "data.file_pool" as fp {
* file_id : uuid
* pool_id : uuid
* number : smallint
}
fp::file_id }o--|| fle::id
fp::pool_id }o--|| pool::id
' ========== ACL ==========
entity "acl.files" as acl_f {
* user_id : smallint
* file_id : uuid
--
* view : boolean
* edit : boolean
}
acl_f::user_id }o--|| usr::id
acl_f::file_id }o--|| fle::id
entity "acl.tags" as acl_t {
* user_id : smallint
* tag_id : uuid
--
* view : boolean
* edit : boolean
' * files_view : boolean
' * files_edit : boolean
}
acl_t::user_id }o--|| usr::id
acl_t::tag_id }o--|| tag::id
entity "acl.categories" as acl_c {
* user_id : smallint
* category_id : uuid
--
* view : boolean
* edit : boolean
' * tags_view : boolean
' * tags_edit : boolean
}
acl_c::user_id }o--|| usr::id
acl_c::category_id }o--|| cty::id
entity "acl.pools" as acl_p {
* user_id : smallint
* pool_id : uuid
--
* view : boolean
* edit : boolean
' * files_view : boolean
' * files_edit : boolean
}
acl_p::user_id }o--|| usr::id
acl_p::pool_id }o--|| pool::id
' ========== ACTIVITY ==========
entity "activity.sessions" as ssn {
* id : serial <<generated>>
--
* token : text
* user_id : smallint
* user_agent : varchar(512)
* started_at : timestamptz
expires_at : timestamptz
* last_activity : timestamptz
}
ssn::user_id }o--|| usr::id
entity "activity.file_views" as fv {
* file_id : uuid
* timestamp : timestamptz
* user_id : smallint
}
fv::file_id }o--|| fle::id
fv::user_id }o--|| usr::id
entity "activity.tag_uses" as tu {
* tag_id : uuid
* timestamp : timestamptz
* user_id : smallint
--
* included : boolean
}
tu::tag_id }o--|| tag::id
tu::user_id }o--|| usr::id
entity "activity.pool_views" as pv {
* pool_id : uuid
* timestamp : timestamptz
* user_id : smallint
}
pv::pool_id }o--|| pool::id
pv::user_id }o--|| usr::id
@enduml
+56
View File
@@ -0,0 +1,56 @@
# Tanabata File Manager
## Usage
### 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/tanabata/` directory and check its permissions. This is the directory where Tanabata programs store their configuration files. 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
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.
+103
View File
@@ -0,0 +1,103 @@
// 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
@@ -0,0 +1,86 @@
// 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
@@ -0,0 +1,27 @@
// 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
@@ -0,0 +1,58 @@
// 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
+111
View File
@@ -0,0 +1,111 @@
// 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
@@ -0,0 +1,222 @@
#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
@@ -0,0 +1,206 @@
#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
@@ -0,0 +1,208 @@
#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
@@ -0,0 +1,199 @@
#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
@@ -0,0 +1,68 @@
#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
@@ -0,0 +1,25 @@
#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
@@ -0,0 +1,25 @@
#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
@@ -0,0 +1,74 @@
#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
@@ -0,0 +1,90 @@
#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
@@ -0,0 +1,92 @@
#!/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
@@ -0,0 +1,14 @@
[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
+613
View File
@@ -0,0 +1,613 @@
#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/tanabata/"
// 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;
}
char *path = realpath(arg, NULL);
if (path == NULL) {
fprintf(stderr, ERROR("Invalid file path")"\n");
free(path);
return 1;
}
if (tanabata_sasa_add(&tanabata, path).id != HOLE_ID &&
tanabata_save(&tanabata) == 0) {
printf(SUCCESS("Successfully added file to database")"\n");
free(path);
return 0;
}
fprintf(stderr, ERROR("Failed to add file to database")"\n");
free(path);
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).id != HOLE_ID &&
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"tfm-cli.conf", "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"tfm-cli.conf", "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:w";
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"
);
if (tanabata_path != NULL) {
printf(HIGHLIGHT("Current database location: %s")"\n", tanabata_path);
} else {
printf(HIGHLIGHT("No database connected")"\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
@@ -0,0 +1,59 @@
#!/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
@@ -0,0 +1,51 @@
<!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
@@ -0,0 +1,2 @@
<?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
@@ -0,0 +1,3 @@
.btn-secondary {
display: none;
}
File diff suppressed because one or more lines are too long
+151
View File
@@ -0,0 +1,151 @@
@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: 3vmax;
}
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
@@ -0,0 +1,46 @@
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
@@ -0,0 +1,194 @@
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.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

+39
View File
@@ -0,0 +1,39 @@
<!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
@@ -0,0 +1,37 @@
$("#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;
}
});
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
/*!
* 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
@@ -0,0 +1,2 @@
/*! 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
@@ -0,0 +1,52 @@
$(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
@@ -0,0 +1,7 @@
var db_name = localStorage["db_name"];
$(window).on("load", function (e) {
if (db_name != null) {
$(".db_name").text(db_name);
}
});
+8
View File
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,20 @@
$(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
@@ -0,0 +1,9 @@
$(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
@@ -0,0 +1,9 @@
$(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
@@ -0,0 +1,71 @@
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
@@ -0,0 +1,9 @@
$(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
@@ -0,0 +1,16 @@
$(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
@@ -0,0 +1,204 @@
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
@@ -0,0 +1,36 @@
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!");
});
+37
View File
@@ -0,0 +1,37 @@
$(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>`);
});
lazy_menu = $("#menu-tag-view .thumb").lazy({
chainable: false,
scrollDirection: "vertical",
effect: "fadeIn",
visibleOnly: true,
appendScroll: $("#menu-tag-view .list")[0],
});
});
$(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);
});
+364
View File
@@ -0,0 +1,364 @@
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;
var lazy_menu;
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++;
$("#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");
}
lazy_menu.update();
}
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");
}
lazy_menu.update();
});
$(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
@@ -0,0 +1,99 @@
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!");
});
+46
View File
@@ -0,0 +1,46 @@
$(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>`);
});
lazy_menu = $("#menu-tag-view .thumb").lazy({
chainable: false,
scrollDirection: "vertical",
effect: "fadeIn",
visibleOnly: true,
appendScroll: $("#menu-tag-view .list")[0],
});
});
$(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);
});
+2
View File
@@ -0,0 +1,2 @@
User-agent: *
Disallow: /
+47
View File
@@ -0,0 +1,47 @@
{
"name": "Tanabata",
"lang": "en-US",
"description": "Tanabata Project PWA",
"start_url": "/",
"scope": "/",
"display": "standalone",
"theme_color": "#615880",
"icons": [
{
"src": "\/images\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/images\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/images\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/images\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/images\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/images\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}
+53
View File
@@ -0,0 +1,53 @@
<!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
@@ -0,0 +1,53 @@
<!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
@@ -0,0 +1,49 @@
<!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
@@ -0,0 +1,47 @@
<!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
@@ -0,0 +1,111 @@
<!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>

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