25 Commits
master ... test

Author SHA1 Message Date
6cff4247a8 typo
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is passing
2022-12-05 01:26:12 +08:00
90ff1382a8 add drone pipeline
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2022-12-05 01:24:56 +08:00
1450357b91 add drone pipeline
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-05 01:01:06 +08:00
7a31c36c10 Replace react with preact
reduce js file bundle to 20%
2022-12-04 20:08:08 +08:00
5271c12525 Replace webpack with only esbuild
reduce node_modules size to only 18M
2022-12-04 19:34:13 +08:00
d278e4009d fix avatar max 1 frame 2022-12-04 13:55:52 +08:00
e3d80ffc2a Fix: web height 100vh 2022-12-02 03:43:14 +08:00
30a8fbad4e Fix: web width 100% 2022-12-02 03:18:37 +08:00
e7bc625b6d Add: get alternative avatar from directory 2022-12-02 03:09:09 +08:00
2d71e7b0cb Update full avatar background 2022-12-01 23:47:07 +08:00
2297f87fa3 Update npm dependencies to react 18 2022-12-01 23:16:23 +08:00
271c7e5c13 Add: get avatar from file 2022-12-01 23:07:47 +08:00
9fdea6b169 Merge remote-tracking branch 'postgres' into postgres 2022-11-06 11:34:03 +08:00
38e20044e2 frontend treat 85% of duration as finished 2022-11-06 11:33:57 +08:00
830cbae17a log /v1/record_playback 2022-11-06 11:33:15 +08:00
51fee5bfe0 fix: break on browser don't support mediaSession
such as stupid WeChat Browser
2022-11-05 22:51:45 +08:00
e40fd2625f fix web playback log behavior 2022-11-05 01:24:40 +08:00
061ef9bdc9 improve web playback log behavior 2022-11-04 23:50:38 +08:00
d478923ce0 frontend record more types of playback 2022-11-04 22:12:15 +08:00
e4032069a5 fix postgres insert on conflict not returning id 2022-11-04 21:27:46 +08:00
73da4f8dc5 fix postgres unique constraint 2022-11-04 20:17:46 +08:00
89ff2bf452 fix postgres ILIKE 2022-11-04 20:06:00 +08:00
d0f6d19a7e frontend support record playback history 2022-11-04 19:06:06 +08:00
977b3e02e9 backend support record playback history 2022-11-04 18:39:24 +08:00
a6d82c1f47 migrate to postgres 2022-11-04 17:42:46 +08:00
16 changed files with 455 additions and 190 deletions

18
.drone.yml Normal file
View File

@@ -0,0 +1,18 @@
kind: pipeline
type: docker
name: default
steps:
- name: frontend-web
image: node:19
commands:
- cd web
- npm install
- npm run build
- name: release
image: plugins/gitea-release
settings:
api_key: da966507c259aa32ccc2d434e930af4a580de785
base_url: https://yongyuancv.cn/git/
files: dist/*

View File

@@ -57,7 +57,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 16
- name: Build web front end
run: |
make web

View File

@@ -1,7 +1,7 @@
{
"api": {
"secret": "CHANGE_YOUR_SECRET_HERE",
"database_name": "music.sqlite3",
"database_name": "postgres://postgres:woshimima@localhost/postgres?sslmode=disable",
"single_thread": true,
"addr": ":8080",
"ffmpeg_threads": 1,

2
go.mod
View File

@@ -4,7 +4,7 @@ go 1.18
require (
github.com/gorilla/sessions v1.2.1
github.com/mattn/go-sqlite3 v1.14.14
github.com/lib/pq v1.10.7
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
)

4
go.sum
View File

@@ -2,7 +2,7 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=

View File

@@ -59,6 +59,7 @@ func NewAPI(config commonconfig.Config) (*API, error) {
apiMux.HandleFunc("/get_file_info", api.HandleGetFileInfo)
apiMux.HandleFunc("/get_file_ffprobe_info", api.HandleGetFileFfprobeInfo)
apiMux.HandleFunc("/get_file_stream_direct", api.HandleGetFileStreamDirect)
apiMux.HandleFunc("/get_file_avatar", api.HandelGetFileAvatar)
apiMux.HandleFunc("/prepare_file_stream_direct", api.HandlePrepareFileStreamDirect)
apiMux.HandleFunc("/delete_file", api.HandleDeleteFile)
apiMux.HandleFunc("/update_filename", api.HandleUpdateFilename)

108
pkg/api/handle_avatar.go Normal file
View File

@@ -0,0 +1,108 @@
package api
import (
"bytes"
"errors"
"io"
"log"
"msw-open-music/pkg/database"
"net/http"
"os"
"os/exec"
"path"
"strconv"
"strings"
)
func (api *API) HandelGetFileAvatar(w http.ResponseWriter, r *http.Request) {
var err error
q := r.URL.Query()
ids := q["id"]
if len(ids) == 0 {
err = errors.New(`parameter "id" can't be empty`)
api.HandleError(w, r, err)
return
}
id, err := strconv.Atoi(ids[0])
if err != nil {
api.HandleError(w, r, err)
return
}
file, err := api.Db.GetFile(int64(id))
if err != nil {
api.HandleError(w, r, err)
return
}
path, err := file.Path()
if err != nil {
api.HandleError(w, r, err)
return
}
log.Println("[api] Get avatar of file", path)
buff := make([]byte, 0)
cache := bytes.NewBuffer(buff)
cmd := exec.Command("ffmpeg", "-i", path, "-c:v", "libwebp_anim", "-update", "1", "-frames:v", "1", "-f", "image2pipe", "-")
cmd.Stdout = cache
err = cmd.Run()
if err != nil {
api.HandleGetAlternativeFileAvatar(w, r, file)
return
}
w.Header().Set("Content-Type", "image/webp")
io.Copy(w, cache)
}
func (api *API) HandleGetAlternativeFileAvatar(w http.ResponseWriter, r *http.Request, f *database.File) {
var err error
dir, err := f.Dir()
if err != nil {
api.HandleError(w, r, err)
return
}
log.Println("[api] Get alternative avatar in dir", dir)
files, err := os.ReadDir(dir)
if err != nil {
api.HandleError(w, r, err)
return
}
avatar, err := findAvatarFile(files)
avatarPath := path.Join(dir, avatar)
if err != nil {
api.HandleError(w, r, err)
return
}
cmd := exec.Command("ffmpeg", "-i", avatarPath, "-c:v", "libwebp_anim", "-f", "image2pipe", "-")
cmd.Stdout = w
w.Header().Set("Content-Type", "image/webp")
err = cmd.Run()
if err != nil {
api.HandleError(w, r, err)
return
}
}
func findAvatarFile(files []os.DirEntry) (string, error) {
for _, file := range files {
if isAvatarType(file.Name()) {
return file.Name(), nil
}
}
return "", errors.New("Cannot find avatar file")
}
var avatarFileTypes = []string{
".jpg",
".png",
}
func isAvatarType(filename string) bool {
for _, t := range avatarFileTypes {
if strings.HasSuffix(strings.ToLower(filename), t) {
return true
}
}
return false
}

View File

@@ -2,6 +2,7 @@ package api
import (
"encoding/json"
"log"
"msw-open-music/pkg/database"
"net/http"
"time"
@@ -35,6 +36,12 @@ func (api *API) HandleRecordPlayback(w http.ResponseWriter, r *http.Request) {
}
}
log.Println("[api] Record playback history",
recordPlaybackRequest.Playback.UserID,
recordPlaybackRequest.Playback.FileID,
recordPlaybackRequest.Playback.Duration,
recordPlaybackRequest.Playback.Method)
err = api.Db.RecordPlayback(recordPlaybackRequest.Playback)
if err != nil {
api.HandleError(w, r, err)

View File

@@ -4,7 +4,7 @@ import (
"database/sql"
"sync"
_ "github.com/mattn/go-sqlite3"
_ "github.com/lib/pq"
)
type Database struct {
@@ -41,7 +41,7 @@ func NewDatabase(dbName string, singleThread bool) (*Database, error) {
var err error
// open database
sqlConn, err := sql.Open("sqlite3", dbName)
sqlConn, err := sql.Open("postgres", dbName)
if err != nil {
return nil, err
}

View File

@@ -168,6 +168,7 @@ func (database *Database) Walk(root string, pattern []string, tagIDs []int64, us
insertFileStmt := tx.Stmt(database.stmt.insertFile)
putTagOnFileStmt := tx.Stmt(database.stmt.putTagOnFile)
findFolderStmt := tx.Stmt(database.stmt.findFolder)
findFileStmt := tx.Stmt(database.stmt.findFile)
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
@@ -194,23 +195,42 @@ func (database *Database) Walk(root string, pattern []string, tagIDs []int64, us
folder, filename := filepath.Split(path)
err = findFolderStmt.QueryRow(folder).Scan(&folderID)
if err != nil {
result, err := insertFolderStmt.Exec(folder, filepath.Base(folder))
result, err := insertFolderStmt.Query(folder, filepath.Base(folder))
if err != nil {
return err
}
folderID, err = result.LastInsertId()
for result.Next() {
err = result.Scan(&folderID)
if err != nil {
return err
}
}
result, err := insertFileStmt.Exec(folderID, filename, filename, info.Size())
}
// try find file id
var fileID int64
result, err := findFileStmt.Query(folderID, filename)
if err != nil {
return err
}
fileID, err := result.LastInsertId()
for result.Next() {
err = result.Scan(&fileID)
if err != nil {
return err
}
}
// insert new file
result, err = insertFileStmt.Query(folderID, filename, filename, info.Size())
if err != nil {
return err
}
for result.Next() {
err = result.Scan(&fileID)
if err != nil {
return err
}
}
for _, tag := range tags {
_, err := putTagOnFileStmt.Exec(tag.ID, fileID, userID)

View File

@@ -6,11 +6,17 @@ func (database *Database) InsertTag(tag *Tag) (int64, error) {
database.singleThreadLock.Lock()
defer database.singleThreadLock.Unlock()
result, err := database.stmt.insertTag.Exec(tag.Name, tag.Description, tag.CreatedByUserId)
result, err := database.stmt.insertTag.Query(tag.Name, tag.Description, tag.CreatedByUserId)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
var id int64
for result.Next() {
err = result.Scan(&id)
if err != nil {
return 0, err
}
}
if err != nil {
return 0, err
}

View File

@@ -6,22 +6,22 @@ import (
)
var initFilesTableQuery = `CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY,
folder_id INTEGER NOT NULL,
id SERIAL PRIMARY KEY,
folder_id INTEGER NOT NULL REFERENCES folders(id),
realname TEXT NOT NULL,
filename TEXT NOT NULL,
filesize INTEGER NOT NULL,
FOREIGN KEY(folder_id) REFERENCES folders(id)
UNIQUE (folder_id, realname)
);`
var initFoldersTableQuery = `CREATE TABLE IF NOT EXISTS folders (
id INTEGER PRIMARY KEY,
folder TEXT NOT NULL,
id SERIAL PRIMARY KEY,
folder TEXT NOT NULL UNIQUE,
foldername TEXT NOT NULL
);`
var initFeedbacksTableQuery = `CREATE TABLE IF NOT EXISTS feedbacks (
id INTEGER PRIMARY KEY,
id SERIAL PRIMARY KEY,
time INTEGER NOT NULL,
content TEXT NOT NULL,
user_id INTEGER NOT NULL,
@@ -30,108 +30,101 @@ var initFeedbacksTableQuery = `CREATE TABLE IF NOT EXISTS feedbacks (
// User table schema definition
// role: 0 - Anonymous User, 1 - Admin, 2 - User
// postgres avatar references problem
var initUsersTableQuery = `CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id SERIAL PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
role INTEGER NOT NULL,
active BOOLEAN NOT NULL,
avatar_id INTEGER NOT NULL,
FOREIGN KEY(avatar_id) REFERENCES avatars(id)
avatar_id INTEGER NOT NULL DEFAULT 0
);`
var initAvatarsTableQuery = `CREATE TABLE IF NOT EXISTS avatars (
id INTEGER PRIMARY KEY,
id SERIAL PRIMARY KEY,
avatarname TEXT NOT NULL,
avatar BLOB NOT NULL
avatar BYTEA NOT NULL
);`
var initTagsTableQuery = `CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT NOT NULL,
created_by_user_id INTEGER NOT NULL,
FOREIGN KEY(created_by_user_id) REFERENCES users(id)
created_by_user_id INTEGER NOT NULL REFERENCES users(id)
);`
var initFileHasTagTableQuery = `CREATE TABLE IF NOT EXISTS file_has_tag (
file_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
PRIMARY KEY (file_id, tag_id),
FOREIGN KEY(user_id) REFERENCES users(id)
FOREIGN KEY (file_id) REFERENCES files(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
file_id INTEGER NOT NULL REFERENCES files(id),
tag_id INTEGER NOT NULL REFERENCES tags(id),
user_id INTEGER NOT NULL REFERENCES users(id),
PRIMARY KEY (file_id, tag_id)
);`
var initLikesTableQuery = `CREATE TABLE IF NOT EXISTS likes (
user_id INTEGER NOT NULL,
file_id INTEGER NOT NULL,
PRIMARY KEY (user_id, file_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (file_id) REFERENCES files(id)
user_id INTEGER NOT NULL REFERENCES users(id),
file_id INTEGER NOT NULL REFERENCES files(id),
PRIMARY KEY (user_id, file_id)
);`
var initReviewsTableQuery = `CREATE TABLE IF NOT EXISTS reviews (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
file_id INTEGER NOT NULL,
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
file_id INTEGER NOT NULL REFERENCES files(id),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL DEFAULT 0,
content TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (file_id) REFERENCES files(id)
content TEXT NOT NULL
);`
var initPlaybacksTableQuery = `CREATE TABLE IF NOT EXISTS playbacks (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
file_id INTEGER NOT NULL,
time INTEGER NOT NULL,
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
file_id INTEGER NOT NULL REFERENCES files(id),
time TIMESTAMP NOT NULL,
method INTEGER NOT NULL,
duration INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (file_id) REFERENCES files(id)
duration INTERVAL NOT NULL
);`
var initLogsTableQuery = `CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY,
id SERIAL PRIMARY KEY,
time INTEGER NOT NULL,
message TEXT NOT NULL,
user_id INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
user_id INTEGER NOT NULL REFERENCES users(id)
);`
var initTmpfsTableQuery = `CREATE TABLE IF NOT EXISTS tmpfs (
id INTEGER PRIMARY KEY,
id SERIAL PRIMARY KEY,
path TEXT NOT NULL,
size INTEGER NOT NULL,
file_id INTEGER NOT NULL,
file_id INTEGER NOT NULL REFERENCES files(id),
ffmpeg_config TEXT NOT NULL,
created_time INTEGER NOT NULL,
accessed_time INTEGER NOT NULL,
FOREIGN KEY (file_id) REFERENCES files(id)
accessed_time INTEGER NOT NULL
);`
var insertFolderQuery = `INSERT INTO folders (folder, foldername)
VALUES (?, ?);`
VALUES ($1, $2)
ON CONFLICT DO NOTHING
RETURNING id;
;`
var findFolderQuery = `SELECT id FROM folders WHERE folder = ? LIMIT 1;`
var findFolderQuery = `SELECT id FROM folders WHERE folder = $1 LIMIT 1;`
var findFileQuery = `SELECT id FROM files WHERE folder_id = ? AND realname = ? LIMIT 1;`
var findFileQuery = `SELECT id FROM files WHERE folder_id = $1 AND realname = $2 LIMIT 1;`
var insertFileQuery = `INSERT INTO files (folder_id, realname, filename, filesize)
VALUES (?, ?, ?, ?);`
VALUES ($1, $2, $3, $4)
ON CONFLICT DO NOTHING
RETURNING id;`
var searchFilesQuery = `SELECT
files.id, files.folder_id, files.filename, folders.foldername, files.filesize
FROM files
JOIN folders ON files.folder_id = folders.id
WHERE filename LIKE ?
WHERE filename ILIKE $1
ORDER BY folders.foldername, files.filename
LIMIT ? OFFSET ?;`
LIMIT $2 OFFSET $3;`
var getFolderQuery = `SELECT folder FROM folders WHERE id = ? LIMIT 1;`
var getFolderQuery = `SELECT folder FROM folders WHERE id = $1 LIMIT 1;`
var dropFilesQuery = `DROP TABLE files;`
@@ -141,42 +134,42 @@ var getFileQuery = `SELECT
files.id, files.folder_id, files.realname, files.filename, folders.foldername, files.filesize
FROM files
JOIN folders ON files.folder_id = folders.id
WHERE files.id = ?
WHERE files.id = $1
LIMIT 1;`
var searchFoldersQuery = `SELECT
id, folder, foldername
FROM folders
WHERE foldername LIKE ?
WHERE foldername ILIKE $1
ORDER BY foldername
LIMIT ? OFFSET ?;`
LIMIT $2 OFFSET $3;`
var getFilesInFolderQuery = `SELECT
files.id, files.filename, files.filesize, folders.foldername, folders.folder
FROM files
JOIN folders ON files.folder_id = folders.id
WHERE folder_id = ?
WHERE folder_id = $1
ORDER BY files.filename
LIMIT ? OFFSET ?;`
LIMIT $2 OFFSET $3;`
var getRandomFilesQuery = `SELECT
files.id, files.folder_id, files.filename, folders.foldername, files.filesize
FROM files
JOIN folders ON files.folder_id = folders.id
ORDER BY RANDOM()
LIMIT ?;`
LIMIT $1;`
var getRandomFilesWithTagQuery = `SELECT
files.id, files.folder_id, files.filename, folders.foldername, files.filesize
FROM file_has_tag
JOIN files ON file_has_tag.file_id = files.id
JOIN folders ON files.folder_id = folders.id
WHERE file_has_tag.tag_id = ?
WHERE file_has_tag.tag_id = $1
ORDER BY RANDOM()
LIMIT ?;`
LIMIT $2;`
var insertFeedbackQuery = `INSERT INTO feedbacks (time, content, user_id, header)
VALUES (?, ?, ?, ?);`
VALUES ($1, $2, $3, $4);`
var getFeedbacksQuery = `SELECT
feedbacks.id, feedbacks.time, feedbacks.content, feedbacks.header,
@@ -186,39 +179,39 @@ JOIN users ON feedbacks.user_id = users.id
ORDER BY feedbacks.time
;`
var deleteFeedbackQuery = `DELETE FROM feedbacks WHERE id = ?;`
var deleteFeedbackQuery = `DELETE FROM feedbacks WHERE id = $1;`
var insertUserQuery = `INSERT INTO users (username, password, role, active, avatar_id)
VALUES (?, ?, ?, ?, ?);`
VALUES ($1, $2, $3, $4, $5);`
var countUserQuery = `SELECT count(*) FROM users;`
var countAdminQuery = `SELECT count(*) FROM users WHERE role= 1;`
var getUserQuery = `SELECT id, username, password, role, active, avatar_id FROM users WHERE username = ? LIMIT 1;`
var getUserQuery = `SELECT id, username, password, role, active, avatar_id FROM users WHERE username = $1 LIMIT 1;`
var getUsersQuery = `SELECT id, username, role, active, avatar_id FROM users;`
var getUserByIdQuery = `SELECT id, username, role, active, avatar_id FROM users WHERE id = ? LIMIT 1;`
var getUserByIdQuery = `SELECT id, username, role, active, avatar_id FROM users WHERE id = $1 LIMIT 1;`
var updateUserActiveQuery = `UPDATE users SET active = ? WHERE id = ?;`
var updateUserActiveQuery = `UPDATE users SET active = $1 WHERE id = $2;`
var updateUsernameQuery = `UPDATE users SET username = ? WHERE id = ?;`
var updateUsernameQuery = `UPDATE users SET username = $1 WHERE id = $2;`
var updateUserPasswordQuery = `UPDATE users SET password = ? WHERE id = ?;`
var updateUserPasswordQuery = `UPDATE users SET password = $1 WHERE id = $2;`
var getAnonymousUserQuery = `SELECT id, username, role, avatar_id FROM users WHERE role = 0 LIMIT 1;`
var insertTagQuery = `INSERT INTO tags (name, description, created_by_user_id) VALUES (?, ?, ?);`
var insertTagQuery = `INSERT INTO tags (name, description, created_by_user_id) VALUES ($1, $2, $3) RETURNING id;`
var deleteTagQuery = `DELETE FROM tags WHERE id = ?;`
var deleteTagQuery = `DELETE FROM tags WHERE id = $1;`
var getTagQuery = `SELECT
tags.id, tags.name, tags.description,
users.id, users.username, users.role, users.avatar_id
FROM tags
JOIN users ON tags.created_by_user_id = users.id
WHERE tags.id = ? LIMIT 1;`
WHERE tags.id = $1 LIMIT 1;`
var getTagsQuery = `SELECT
tags.id, tags.name, tags.description,
@@ -228,26 +221,27 @@ JOIN users ON tags.created_by_user_id = users.id
ORDER BY tags.name
;`
var updateTagQuery = `UPDATE tags SET name = ?, description = ? WHERE id = ?;`
var updateTagQuery = `UPDATE tags SET name = $1, description = $2 WHERE id = $3;`
var putTagOnFileQuery = `INSERT OR IGNORE INTO file_has_tag (tag_id, file_id, user_id) VALUES (?, ?, ?);`
// postgres INSERT IGNORE
var putTagOnFileQuery = `INSERT INTO file_has_tag (tag_id, file_id, user_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;`
var getTagsOnFileQuery = `SELECT
tags.id, tags.name, tags.description, tags.created_by_user_id
FROM file_has_tag
JOIN tags ON file_has_tag.tag_id = tags.id
WHERE file_has_tag.file_id = ?
WHERE file_has_tag.file_id = $1
ORDER BY tags.name
;`
var deleteTagOnFileQuery = `DELETE FROM file_has_tag WHERE tag_id = ? AND file_id = ?;`
var deleteTagOnFileQuery = `DELETE FROM file_has_tag WHERE tag_id = $1 AND file_id = $2;`
var deleteTagReferenceInFileHasTagQuery = `DELETE FROM file_has_tag WHERE tag_id = ?;`
var deleteTagReferenceInFileHasTagQuery = `DELETE FROM file_has_tag WHERE tag_id = $1;`
var updateFoldernameQuery = `UPDATE folders SET foldername = ? WHERE id = ?;`
var updateFoldernameQuery = `UPDATE folders SET foldername = $1 WHERE id = $2;`
var insertReviewQuery = `INSERT INTO reviews (user_id, file_id, created_at, content)
VALUES (?, ?, ?, ?);`
VALUES ($1, $2, $3, $4);`
var getReviewsOnFileQuery = `SELECT
reviews.id, reviews.created_at, reviews.updated_at, reviews.content,
@@ -256,15 +250,15 @@ files.id, files.filename
FROM reviews
JOIN users ON reviews.user_id = users.id
JOIN files ON reviews.file_id = files.id
WHERE reviews.file_id = ?
WHERE reviews.file_id = $1
ORDER BY reviews.created_at
;`
var getReviewQuery = `SELECT id, file_id, user_id, created_at, updated_at, content FROM reviews WHERE id = ? LIMIT 1;`
var getReviewQuery = `SELECT id, file_id, user_id, created_at, updated_at, content FROM reviews WHERE id = $1 LIMIT 1;`
var updateReviewQuery = `UPDATE reviews SET content = ?, updated_at = ? WHERE id = ?;`
var updateReviewQuery = `UPDATE reviews SET content = $1, updated_at = $2 WHERE id = $3;`
var deleteReviewQuery = `DELETE FROM reviews WHERE id = ?;`
var deleteReviewQuery = `DELETE FROM reviews WHERE id = $1;`
var getReviewsByUserQuery = `SELECT
reviews.id, reviews.created_at, reviews.updated_at, reviews.content,
@@ -273,19 +267,19 @@ files.id, files.filename
FROM reviews
JOIN users ON reviews.user_id = users.id
JOIN files ON reviews.file_id = files.id
WHERE reviews.user_id = ?
WHERE reviews.user_id = $1
ORDER BY reviews.created_at
;`
var deleteFileQuery = `DELETE FROM files WHERE id = ?;`
var deleteFileQuery = `DELETE FROM files WHERE id = $1;`
var deleteFileReferenceInFileHasTagQuery = `DELETE FROM file_has_tag WHERE file_id = ?;`
var deleteFileReferenceInFileHasTagQuery = `DELETE FROM file_has_tag WHERE file_id = $1;`
var deleteFileReferenceInReviewsQuery = `DELETE FROM reviews WHERE file_id = ?;`
var deleteFileReferenceInReviewsQuery = `DELETE FROM reviews WHERE file_id = $1;`
var updateFilenameQuery = `UPDATE files SET filename = ? WHERE id = ?;`
var updateFilenameQuery = `UPDATE files SET filename = $1 WHERE id = $2;`
var resetFilenameQuery = `UPDATE files SET filename = realname WHERE id = ?;`
var resetFilenameQuery = `UPDATE files SET filename = realname WHERE id = $1;`
var recordPlaybackQuery = `INSERT INTO playbacks (user_id, file_id, time, method, duration) VALUES ($1, $2, $3, $4, $5);`
@@ -357,26 +351,22 @@ func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) {
stmt := &Stmt{}
// init files table
stmt.initFilesTable, err = sqlConn.Prepare(initFilesTableQuery)
if err != nil {
return nil, err
}
// init folders table
stmt.initFoldersTable, err = sqlConn.Prepare(initFoldersTableQuery)
if err != nil {
return nil, err
}
// init feedbacks tables
stmt.initFeedbacksTable, err = sqlConn.Prepare(initFeedbacksTableQuery)
_, err = stmt.initFoldersTable.Exec()
if err != nil {
return nil, err
}
// init users table
stmt.initUsersTable, err = sqlConn.Prepare(initUsersTableQuery)
// init files table
stmt.initFilesTable, err = sqlConn.Prepare(initFilesTableQuery)
if err != nil {
return nil, err
}
_, err = stmt.initFilesTable.Exec()
if err != nil {
return nil, err
}
@@ -386,99 +376,103 @@ func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) {
if err != nil {
return nil, err
}
_, err = stmt.initAvatarsTable.Exec()
if err != nil {
return nil, err
}
// init users table
stmt.initUsersTable, err = sqlConn.Prepare(initUsersTableQuery)
if err != nil {
return nil, err
}
_, err = stmt.initUsersTable.Exec()
if err != nil {
return nil, err
}
// init feedbacks tables
stmt.initFeedbacksTable, err = sqlConn.Prepare(initFeedbacksTableQuery)
if err != nil {
return nil, err
}
_, err = stmt.initFeedbacksTable.Exec()
if err != nil {
return nil, err
}
// init tags table
stmt.initTagsTable, err = sqlConn.Prepare(initTagsTableQuery)
if err != nil {
return nil, err
}
_, err = stmt.initTagsTable.Exec()
if err != nil {
return nil, err
}
// init file_has_tag table
stmt.initFileHasTag, err = sqlConn.Prepare(initFileHasTagTableQuery)
if err != nil {
return nil, err
}
_, err = stmt.initFileHasTag.Exec()
if err != nil {
return nil, err
}
// init likes table
stmt.initLikesTable, err = sqlConn.Prepare(initLikesTableQuery)
if err != nil {
return nil, err
}
_, err = stmt.initLikesTable.Exec()
if err != nil {
return nil, err
}
// init reviews table
stmt.initReviewsTable, err = sqlConn.Prepare(initReviewsTableQuery)
if err != nil {
return nil, err
}
_, err = stmt.initReviewsTable.Exec()
if err != nil {
return nil, err
}
// init playbacks table
stmt.initPlaybacksTable, err = sqlConn.Prepare(initPlaybacksTableQuery)
if err != nil {
return nil, err
}
_, err = stmt.initPlaybacksTable.Exec()
if err != nil {
return nil, err
}
// init logs table
stmt.initLogsTable, err = sqlConn.Prepare(initLogsTableQuery)
if err != nil {
return nil, err
}
_, err = stmt.initLogsTable.Exec()
if err != nil {
return nil, err
}
// init tmpfs table
stmt.initTmpfsTable, err = sqlConn.Prepare(initTmpfsTableQuery)
if err != nil {
return nil, err
}
// run init statement
_, err = stmt.initFilesTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initFoldersTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initFeedbacksTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initUsersTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initAvatarsTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initTagsTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initFileHasTag.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initLikesTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initReviewsTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initPlaybacksTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initLogsTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initTmpfsTable.Exec()
if err != nil {
return nil, err
}
log.Println("Init tables finished")
// init insert folder statement
stmt.insertFolder, err = sqlConn.Prepare(insertFolderQuery)
if err != nil {

View File

@@ -13,6 +13,7 @@ type File struct {
Realname string `json:"-"`
Filename string `json:"filename"`
Filesize int64 `json:"filesize"`
folderCache *Folder
}
type Folder struct {
@@ -75,9 +76,23 @@ var (
)
func (f *File) Path() (string, error) {
folder, err := f.Db.GetFolder(f.Folder_id)
var err error
if f.folderCache == nil {
f.folderCache, err = f.Db.GetFolder(f.Folder_id)
}
if err != nil {
return "", err
}
return filepath.Join(folder.Folder, f.Realname), nil
return filepath.Join(f.folderCache.Folder, f.Realname), nil
}
func (f *File) Dir() (string, error) {
var err error
if f.folderCache == nil {
f.folderCache, err = f.Db.GetFolder(f.Folder_id)
}
if err != nil {
return "", err
}
return f.folderCache.Folder, nil
}

View File

@@ -3,11 +3,19 @@ html {
}
body {
margin: auto;
margin-top: 1rem;
padding-top: 1rem;
max-width: unset;
min-height: 100vh;
}
#root {
display: flex;
justify-content: center;
}
.base {
display: grid;
grid-row-gap: 1em;
max-width: 800px;
width: 100%;
}
.header {
color: white;

View File

@@ -28,6 +28,27 @@ function App() {
const [user, setUser] = useState({});
const [langCode, setLangCode] = useState("en_US");
useEffect(() => {
if (playingFile.id === undefined) {
return;
}
const html = document.getElementsByTagName("html")[0];
const retStyle = html.style;
const bodyRetStyle = document.body.style
html.style = `
backdrop-filter: blur(10px);
background-size: cover;
background-attachment: fixed;
background-position: center;
background-image: url("/api/v1/get_file_avatar?id=${playingFile.id}");
`;
document.body.style.opacity = 0.88;
return () => {
html.style = retStyle;
document.body.style = bodyRetStyle;
};
}, [playingFile.id]);
// select language
useEffect(() => {
const browserCode = window.navigator.language;
@@ -41,7 +62,7 @@ function App() {
}
}
// fallback to english
setLangCode('en-US');
setLangCode("en-US");
}, []);
return (

View File

@@ -4,7 +4,7 @@ import {useNavigate} from "react-router";
import {CalcReadableFilesizeDetail} from "./Common";
import FfmpegConfig from "./FfmpegConfig";
import FileDialog from "./FileDialog";
import { Tr } from "../translate";
import {Tr} from "../translate";
function AudioPlayer(props) {
// props.playingFile
@@ -22,25 +22,50 @@ function AudioPlayer(props) {
const [isPreparing, setIsPreparing] = useState(false);
const [timerCount, setTimerCount] = useState(0);
const [timerID, setTimerID] = useState(null);
const [beginPlayTime, setBeginPlayTime] = useState(null);
const [lastID, setLastID] = useState(null);
const recordPlaybackHistory = async (file_id, method) => {
if (file_id === null) {
return
}
const player = document.getElementById('dom-player')
const endPlayTime = new Date()
let duration = parseInt((endPlayTime - beginPlayTime) / 1000)
const maxDuration = parseInt(player.duration)
// treat 85% of duration as finished
if (duration / maxDuration >= 0.85) {
method = 1
}
duration = duration < maxDuration ? duration : maxDuration
setBeginPlayTime(endPlayTime)
await fetch('/api/v1/record_playback', {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
playback: {
file_id,
method,
duration,
},
})
})
}
// init mediaSession API
useEffect(() => {
if (navigator.mediaSession) {
navigator.mediaSession.setActionHandler("stop", () => {
props.setPlayingFile({});
});
}
}, []);
useEffect(() => {
// media session related staff
navigator.mediaSession.metadata = new window.MediaMetadata({
title: props.playingFile.filename,
album: props.playingFile.foldername,
artwork: [{ src: "/favicon.png", type: "image/png" }],
});
// no playing file
const updatePlayMode = () => {
if (props.playingFile.id === undefined) {
setPlayingURL("");
return;
return
}
if (raw) {
console.log("Play raw file");
@@ -51,7 +76,7 @@ function AudioPlayer(props) {
setIsPreparing(true);
fetch("/api/v1/prepare_file_stream_direct", {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
id: props.playingFile.id,
config_name: selectedFfmpegConfig.name,
@@ -76,7 +101,39 @@ function AudioPlayer(props) {
);
}
}
}, [props.playingFile.id, raw, prepare, selectedFfmpegConfig]);
}
useEffect(() => {
// media session related staff
if (navigator.mediaSession) {
navigator.mediaSession.metadata = new window.MediaMetadata({
title: props.playingFile.filename,
album: props.playingFile.foldername,
artwork: [{src: "/favicon.png", type: "image/png"}],
});
}
// no playing file
if (props.playingFile.id === undefined) {
// 3 music stopped
recordPlaybackHistory(lastID, 3)
setPlayingURL("");
return;
}
// crrently playing file, record interupt
if (playingURL) {
// 2 music changed
recordPlaybackHistory(lastID, 2)
}
setLastID(props.playingFile.id)
// have playingFile, record begin time
setBeginPlayTime(new Date())
updatePlayMode()
}, [props.playingFile.id]);
useEffect(() => {
updatePlayMode()
}, [raw, prepare, selectedFfmpegConfig])
let navigate = useNavigate();
@@ -192,16 +249,26 @@ function AudioPlayer(props) {
id="dom-player"
controls
autoPlay
loop={loop}
className="audio-player"
src={playingURL}
onEnded={async () => {
const player = document.getElementById('dom-player')
if (loop) {
player.play()
}
// 1 music finished
recordPlaybackHistory(props.playingFile.id, 1)
}}
onPlay={async () => {
setBeginPlayTime(new Date());
}}
></audio>
<FfmpegConfig
selectedFfmpegConfig={selectedFfmpegConfig}
setSelectedFfmpegConfig={setSelectedFfmpegConfig}
/>
</footer>
</footer >
);
}