Compare commits
7 Commits
submit-web
...
v1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
9b4c0b24ef
|
|||
|
c418634515
|
|||
|
87ac0cecb7
|
|||
|
1d49689171
|
|||
|
fdd41397bf
|
|||
|
7e5c92dd63
|
|||
|
83f2b76cbc
|
@@ -364,10 +364,10 @@ Anonymous API can be called by anonymous.
|
|||||||
|
|
||||||
Currently only few APIs in font-end.
|
Currently only few APIs in font-end.
|
||||||
|
|
||||||
- `/#/share/39`
|
- `/#/files/39/share`
|
||||||
|
|
||||||
Share a specific file.
|
Share a specific file.
|
||||||
|
|
||||||
- `/#/search-folders/2614`
|
- `/#/folders/2614`
|
||||||
|
|
||||||
Show files in a specific folder.
|
Show files in a specific folder.
|
||||||
|
|||||||
@@ -1,750 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"msw-open-music/internal/pkg/database"
|
|
||||||
"msw-open-music/internal/pkg/tmpfs"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type API struct {
|
|
||||||
Db *database.Database
|
|
||||||
Server http.Server
|
|
||||||
token string
|
|
||||||
APIConfig APIConfig
|
|
||||||
Tmpfs *tmpfs.Tmpfs
|
|
||||||
}
|
|
||||||
|
|
||||||
type FfmpegConfigList struct {
|
|
||||||
FfmpegConfigList []FfmpegConfig `json:"ffmpeg_config_list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddFfmpegConfigRequest struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
FfmpegConfig FfmpegConfig `json:"ffmpeg_config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FfmpegConfig struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Args string `json:"args"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Status struct {
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
}
|
|
||||||
var ok Status = Status{
|
|
||||||
Status: "OK",
|
|
||||||
}
|
|
||||||
|
|
||||||
type WalkRequest struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
Root string `json:"root"`
|
|
||||||
Pattern []string `json:"pattern"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResetRequest struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SearchFilesRequest struct {
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
Limit int64 `json:"limit"`
|
|
||||||
Offset int64 `json:"offset"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SearchFoldersRequest struct {
|
|
||||||
Foldername string `json:"foldername"`
|
|
||||||
Limit int64 `json:"limit"`
|
|
||||||
Offset int64 `json:"offset"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SearchFilesResponse struct {
|
|
||||||
Files []database.File `json:"files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SearchFoldersResponse struct {
|
|
||||||
Folders []database.Folder `json:"folders"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetFilesInFolderRequest struct {
|
|
||||||
Folder_id int64 `json:"folder_id"`
|
|
||||||
Limit int64 `json:"limit"`
|
|
||||||
Offset int64 `json:"offset"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetFilesInFolderResponse struct {
|
|
||||||
Files *[]database.File `json:"files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetRandomFilesResponse struct {
|
|
||||||
Files *[]database.File `json:"files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleGetRandomFiles(w http.ResponseWriter, r *http.Request) {
|
|
||||||
files, err := api.Db.GetRandomFiles(10);
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
getRandomFilesResponse := &GetRandomFilesResponse{
|
|
||||||
Files: &files,
|
|
||||||
}
|
|
||||||
log.Println("[api] Get random files")
|
|
||||||
json.NewEncoder(w).Encode(getRandomFilesResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleGetFilesInFolder(w http.ResponseWriter, r *http.Request) {
|
|
||||||
getFilesInFolderRequest := &GetFilesInFolderRequest{
|
|
||||||
Folder_id: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(getFilesInFolderRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check empyt
|
|
||||||
if getFilesInFolderRequest.Folder_id < 0 {
|
|
||||||
api.HandleErrorString(w, r, `"folder_id" can't be none or negative`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := api.Db.GetFilesInFolder(getFilesInFolderRequest.Folder_id, getFilesInFolderRequest.Limit, getFilesInFolderRequest.Offset)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
getFilesInFolderResponse := &GetFilesInFolderResponse{
|
|
||||||
Files: &files,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[api] Get files in folder", getFilesInFolderRequest.Folder_id)
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(getFilesInFolderResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) CheckToken(w http.ResponseWriter, r *http.Request, token string) (error) {
|
|
||||||
if token != api.token {
|
|
||||||
err := errors.New("token not matched")
|
|
||||||
log.Println("[api] [Warning] Token not matched", token)
|
|
||||||
api.HandleErrorCode(w, r, err, 403)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Println("[api] Token passed")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleError(w http.ResponseWriter, r *http.Request, err error) {
|
|
||||||
api.HandleErrorString(w, r, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleErrorCode(w http.ResponseWriter, r *http.Request, err error, code int) {
|
|
||||||
api.HandleErrorStringCode(w, r, err.Error(), code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleErrorString(w http.ResponseWriter, r *http.Request, errorString string) {
|
|
||||||
api.HandleErrorStringCode(w, r, errorString, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleErrorStringCode(w http.ResponseWriter, r *http.Request, errorString string, code int) {
|
|
||||||
log.Println("[api] [Error]", code, errorString)
|
|
||||||
errStatus := &Status{
|
|
||||||
Status: errorString,
|
|
||||||
}
|
|
||||||
w.WriteHeader(code)
|
|
||||||
json.NewEncoder(w).Encode(errStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleReset(w http.ResponseWriter, r *http.Request) {
|
|
||||||
resetRequest := &ResetRequest{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(resetRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check token
|
|
||||||
err = api.CheckToken(w, r, resetRequest.Token)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset
|
|
||||||
err = api.Db.ResetFiles()
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = api.Db.ResetFolder()
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
api.HandleStatus(w, r, "Database reseted")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleWalk(w http.ResponseWriter, r *http.Request) {
|
|
||||||
walkRequest := &WalkRequest{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(walkRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check token match
|
|
||||||
err = api.CheckToken(w, r, walkRequest.Token)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check root empty
|
|
||||||
if walkRequest.Root == "" {
|
|
||||||
api.HandleErrorString(w, r, `key "root" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check pattern empty
|
|
||||||
if len(walkRequest.Pattern) == 0 {
|
|
||||||
api.HandleErrorString(w, r, `"[]pattern" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// walk
|
|
||||||
err = api.Db.Walk(walkRequest.Root, walkRequest.Pattern)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
api.HandleStatus(w, r, "Database udpated")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleOK(w http.ResponseWriter, r *http.Request) {
|
|
||||||
json.NewEncoder(w).Encode(&ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleStatus(w http.ResponseWriter, r *http.Request, status string) {
|
|
||||||
s := &Status{
|
|
||||||
Status: status,
|
|
||||||
}
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleSearchFiles(w http.ResponseWriter, r *http.Request) {
|
|
||||||
searchFilesRequest := &SearchFilesRequest{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(searchFilesRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check empty
|
|
||||||
if searchFilesRequest.Filename == "" {
|
|
||||||
api.HandleErrorString(w, r, `"filename" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if api.CheckLimit(w, r, searchFilesRequest.Limit) != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
searchFilesResponse := &SearchFilesResponse{}
|
|
||||||
|
|
||||||
searchFilesResponse.Files, err = api.Db.SearchFiles(searchFilesRequest.Filename, searchFilesRequest.Limit, searchFilesRequest.Offset)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[api] Search files", searchFilesRequest.Filename, searchFilesRequest.Limit, searchFilesRequest.Offset)
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(searchFilesResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) CheckLimit(w http.ResponseWriter, r *http.Request, limit int64) (error) {
|
|
||||||
if limit <= 0 || limit > 10 {
|
|
||||||
log.Println("[api] [Warning] Limit error", limit)
|
|
||||||
err := errors.New(`"limit" can't be zero or more than 10`)
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleSearchFolders(w http.ResponseWriter, r *http.Request) {
|
|
||||||
searchFoldersRequest := &SearchFoldersRequest{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(searchFoldersRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check empty
|
|
||||||
if searchFoldersRequest.Foldername == "" {
|
|
||||||
api.HandleErrorString(w, r, `"foldername" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if api.CheckLimit(w, r, searchFoldersRequest.Limit) != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
searchFoldersResponse := &SearchFoldersResponse{}
|
|
||||||
|
|
||||||
searchFoldersResponse.Folders, err = api.Db.SearchFolders(searchFoldersRequest.Foldername, searchFoldersRequest.Limit, searchFoldersRequest.Offset)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[api] Search folders", searchFoldersRequest.Foldername, searchFoldersRequest.Limit, searchFoldersRequest.Offset)
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(searchFoldersResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetFileRequest struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleGetFileInfo(w http.ResponseWriter, r *http.Request) {
|
|
||||||
getFileRequest := &GetFileRequest{
|
|
||||||
ID: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(getFileRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check empty
|
|
||||||
if getFileRequest.ID < 0 {
|
|
||||||
api.HandleErrorString(w, r, `"id" can't be none or negative`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := api.Db.GetFile(getFileRequest.ID)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(file)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) CheckGetFileStream(w http.ResponseWriter, r *http.Request) (error) {
|
|
||||||
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 err
|
|
||||||
}
|
|
||||||
_, err = strconv.Atoi(ids[0])
|
|
||||||
if err != nil {
|
|
||||||
err = errors.New(`parameter "id" should be an integer`)
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
configs := q["config"]
|
|
||||||
if len(configs) == 0 {
|
|
||||||
err = errors.New(`parameter "config" can't be empty`)
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) GetFfmpegConfig(configName string) (FfmpegConfig, bool) {
|
|
||||||
ffmpegConfig := FfmpegConfig{}
|
|
||||||
for _, f := range api.APIConfig.FfmpegConfigList {
|
|
||||||
if f.Name == configName {
|
|
||||||
ffmpegConfig = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ffmpegConfig.Name == "" {
|
|
||||||
return ffmpegConfig, false
|
|
||||||
}
|
|
||||||
return ffmpegConfig, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleGetFileStream(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := api.CheckGetFileStream(w, r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
q := r.URL.Query()
|
|
||||||
ids := q["id"]
|
|
||||||
id, err := strconv.Atoi(ids[0])
|
|
||||||
configs := q["config"]
|
|
||||||
configName := configs[0]
|
|
||||||
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] Stream file", path, configName)
|
|
||||||
|
|
||||||
ffmpegConfig, ok := api.GetFfmpegConfig(configName)
|
|
||||||
if !ok {
|
|
||||||
api.HandleErrorStringCode(w, r, `ffmpeg config not found`, 404)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
args := strings.Split(ffmpegConfig.Args, " ")
|
|
||||||
startArgs := []string {"-threads", strconv.FormatInt(api.APIConfig.FfmpegThreads, 10), "-i", path}
|
|
||||||
endArgs := []string {"-vn", "-f", "ogg", "-"}
|
|
||||||
ffmpegArgs := append(startArgs, args...)
|
|
||||||
ffmpegArgs = append(ffmpegArgs, endArgs...)
|
|
||||||
cmd := exec.Command("ffmpeg", ffmpegArgs...)
|
|
||||||
cmd.Stdout = w
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrepareFileStreamDirectRequest struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
ConfigName string `json:"config_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrepareFileStreamDirectResponse struct {
|
|
||||||
Filesize int64 `json:"filesize"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandlePrepareFileStreamDirect(w http.ResponseWriter, r *http.Request) {
|
|
||||||
prepareFileStreamDirectRequst := &PrepareFileStreamDirectRequest{
|
|
||||||
ID: -1,
|
|
||||||
}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(prepareFileStreamDirectRequst)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check empty
|
|
||||||
if prepareFileStreamDirectRequst.ID < 0 {
|
|
||||||
api.HandleErrorString(w, r, `"id" can't be none or negative`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if prepareFileStreamDirectRequst.ConfigName == "" {
|
|
||||||
api.HandleErrorString(w, r, `"config_name" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := api.Db.GetFile(prepareFileStreamDirectRequst.ID)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
srcPath, err := file.Path()
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[api] Prepare stream direct file", srcPath, prepareFileStreamDirectRequst.ConfigName)
|
|
||||||
ffmpegConfig, ok := api.GetFfmpegConfig(prepareFileStreamDirectRequst.ConfigName)
|
|
||||||
if !ok {
|
|
||||||
api.HandleErrorStringCode(w, r, `ffmpeg config not found`, 404)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
objPath := api.Tmpfs.GetObjFilePath(prepareFileStreamDirectRequst.ID, prepareFileStreamDirectRequst.ConfigName)
|
|
||||||
|
|
||||||
// check obj file exists
|
|
||||||
exists := api.Tmpfs.Exits(objPath)
|
|
||||||
if exists {
|
|
||||||
fileInfo, err := os.Stat(objPath)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
prepareFileStreamDirectResponse := &PrepareFileStreamDirectResponse{
|
|
||||||
Filesize: fileInfo.Size(),
|
|
||||||
}
|
|
||||||
json.NewEncoder(w).Encode(prepareFileStreamDirectResponse)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
api.Tmpfs.Record(objPath)
|
|
||||||
args := strings.Split(ffmpegConfig.Args, " ")
|
|
||||||
startArgs := []string {"-threads", strconv.FormatInt(api.APIConfig.FfmpegThreads, 10), "-i", srcPath}
|
|
||||||
endArgs := []string {"-vn", "-y", objPath}
|
|
||||||
ffmpegArgs := append(startArgs, args...)
|
|
||||||
ffmpegArgs = append(ffmpegArgs, endArgs...)
|
|
||||||
cmd := exec.Command("ffmpeg", ffmpegArgs...)
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfo, err := os.Stat(objPath)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
prepareFileStreamDirectResponse := &PrepareFileStreamDirectResponse{
|
|
||||||
Filesize: fileInfo.Size(),
|
|
||||||
}
|
|
||||||
json.NewEncoder(w).Encode(prepareFileStreamDirectResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleGetFileStreamDirect(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := api.CheckGetFileStream(w, r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
q := r.URL.Query()
|
|
||||||
ids := q["id"]
|
|
||||||
id, err := strconv.Atoi(ids[0])
|
|
||||||
configs := q["config"]
|
|
||||||
configName := configs[0]
|
|
||||||
|
|
||||||
path := api.Tmpfs.GetObjFilePath(int64(id), configName)
|
|
||||||
if api.Tmpfs.Exits(path) {
|
|
||||||
api.Tmpfs.Record(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[api] Get direct cached file", path)
|
|
||||||
|
|
||||||
http.ServeFile(w, r, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleGetFileDirect(w http.ResponseWriter, r *http.Request) {
|
|
||||||
q := r.URL.Query()
|
|
||||||
ids := q["id"]
|
|
||||||
if len(ids) == 0 {
|
|
||||||
api.HandleErrorString(w, r, `parameter "id" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id, err := strconv.Atoi(ids[0])
|
|
||||||
if err != nil {
|
|
||||||
api.HandleErrorString(w, r, `parameter "id" should be an integer`)
|
|
||||||
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 direct raw file", path)
|
|
||||||
|
|
||||||
http.ServeFile(w, r, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
|
||||||
getFileRequest := &GetFileRequest{
|
|
||||||
ID: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(getFileRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check empty
|
|
||||||
if getFileRequest.ID < 0 {
|
|
||||||
api.HandleErrorString(w, r, `"id" can't be none or negative`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := api.Db.GetFile(getFileRequest.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 pipe raw file", path)
|
|
||||||
|
|
||||||
src, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
io.Copy(w, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleGetFfmpegConfigs(w http.ResponseWriter, r *http.Request) {
|
|
||||||
log.Println("[api] Get ffmpeg config list")
|
|
||||||
ffmpegConfigList:= &FfmpegConfigList{
|
|
||||||
FfmpegConfigList: api.APIConfig.FfmpegConfigList,
|
|
||||||
}
|
|
||||||
json.NewEncoder(w).Encode(&ffmpegConfigList)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleAddFfmpegConfig(w http.ResponseWriter, r *http.Request) {
|
|
||||||
addFfmpegConfigRequest := AddFfmpegConfigRequest{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&addFfmpegConfigRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check token
|
|
||||||
err = api.CheckToken(w, r, addFfmpegConfigRequest.Token)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check name and args not null
|
|
||||||
if addFfmpegConfigRequest.Name == "" {
|
|
||||||
api.HandleErrorString(w, r, `"ffmpeg_config.name" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if addFfmpegConfigRequest.FfmpegConfig.Args == "" {
|
|
||||||
api.HandleErrorString(w, r, `"ffmpeg_config.args" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[api] Add ffmpeg config")
|
|
||||||
|
|
||||||
api.APIConfig.FfmpegConfigList = append(api.APIConfig.FfmpegConfigList, addFfmpegConfigRequest.FfmpegConfig)
|
|
||||||
|
|
||||||
api.HandleOK(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeedbackRequest struct {
|
|
||||||
Feedback string `json:"feedback"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleFeedback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
feedbackRequest := &FeedbackRequest{}
|
|
||||||
err :=json.NewDecoder(r.Body).Decode(feedbackRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check empty feedback
|
|
||||||
if feedbackRequest.Feedback == "" {
|
|
||||||
api.HandleErrorString(w, r, `"feedback" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[api] Feedback", feedbackRequest.Feedback)
|
|
||||||
|
|
||||||
headerBuff := &bytes.Buffer{}
|
|
||||||
err = r.Header.Write(headerBuff)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
header := headerBuff.String()
|
|
||||||
|
|
||||||
err = api.Db.InsertFeedback(time.Now().Unix(), feedbackRequest.Feedback, header)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
api.HandleOK(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAPIConfig() (APIConfig) {
|
|
||||||
apiConfig := APIConfig{}
|
|
||||||
return apiConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type APIConfig struct {
|
|
||||||
DatabaseName string `json:"database_name"`
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
FfmpegThreads int64 `json:"ffmpeg_threads"`
|
|
||||||
FfmpegConfigList []FfmpegConfig `json:"ffmpeg_config_list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
APIConfig APIConfig `json:"api"`
|
|
||||||
TmpfsConfig tmpfs.TmpfsConfig `json:"tmpfs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAPI(config Config) (*API, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
apiConfig := config.APIConfig
|
|
||||||
tmpfsConfig := config.TmpfsConfig
|
|
||||||
|
|
||||||
db, err := database.NewDatabase(apiConfig.DatabaseName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
apiMux := http.NewServeMux()
|
|
||||||
|
|
||||||
api := &API{
|
|
||||||
Db: db,
|
|
||||||
Server: http.Server{
|
|
||||||
Addr: apiConfig.Addr,
|
|
||||||
Handler: mux,
|
|
||||||
},
|
|
||||||
APIConfig: apiConfig,
|
|
||||||
}
|
|
||||||
api.Tmpfs = tmpfs.NewTmpfs(tmpfsConfig)
|
|
||||||
|
|
||||||
// mount api
|
|
||||||
apiMux.HandleFunc("/hello", api.HandleOK)
|
|
||||||
apiMux.HandleFunc("/get_file", api.HandleGetFile)
|
|
||||||
apiMux.HandleFunc("/get_file_direct", api.HandleGetFileDirect)
|
|
||||||
apiMux.HandleFunc("/search_files", api.HandleSearchFiles)
|
|
||||||
apiMux.HandleFunc("/search_folders", api.HandleSearchFolders)
|
|
||||||
apiMux.HandleFunc("/get_files_in_folder", api.HandleGetFilesInFolder)
|
|
||||||
apiMux.HandleFunc("/get_random_files", api.HandleGetRandomFiles)
|
|
||||||
apiMux.HandleFunc("/get_file_stream", api.HandleGetFileStream)
|
|
||||||
apiMux.HandleFunc("/get_ffmpeg_config_list", api.HandleGetFfmpegConfigs)
|
|
||||||
apiMux.HandleFunc("/feedback", api.HandleFeedback)
|
|
||||||
apiMux.HandleFunc("/get_file_info", api.HandleGetFileInfo)
|
|
||||||
apiMux.HandleFunc("/get_file_stream_direct", api.HandleGetFileStreamDirect)
|
|
||||||
apiMux.HandleFunc("/prepare_file_stream_direct", api.HandlePrepareFileStreamDirect)
|
|
||||||
// below needs token
|
|
||||||
apiMux.HandleFunc("/walk", api.HandleWalk)
|
|
||||||
apiMux.HandleFunc("/reset", api.HandleReset)
|
|
||||||
apiMux.HandleFunc("/add_ffmpeg_config", api.HandleAddFfmpegConfig)
|
|
||||||
|
|
||||||
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", apiMux))
|
|
||||||
mux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web/build"))))
|
|
||||||
|
|
||||||
api.token = apiConfig.Token
|
|
||||||
|
|
||||||
return api, nil
|
|
||||||
}
|
|
||||||
@@ -1,441 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var initFilesTableQuery = `CREATE TABLE IF NOT EXISTS files (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
folder_id INTEGER NOT NULL,
|
|
||||||
filename TEXT NOT NULL,
|
|
||||||
filesize INTEGER NOT NULL
|
|
||||||
);`
|
|
||||||
var initFoldersTableQuery = `CREATE TABLE IF NOT EXISTS folders (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
folder TEXT NOT NULL,
|
|
||||||
foldername TEXT NOT NULL
|
|
||||||
);`
|
|
||||||
var initFeedbacksTableQuery = `CREATE TABLE IF NOT EXISTS feedbacks (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
time INTEGER NOT NULL,
|
|
||||||
feedback TEXT NOT NULL,
|
|
||||||
header TEXT NOT NULL
|
|
||||||
);`
|
|
||||||
var insertFolderQuery = `INSERT INTO folders (folder, foldername) VALUES (?, ?);`
|
|
||||||
var findFolderQuery = `SELECT id FROM folders WHERE folder = ? LIMIT 1;`
|
|
||||||
var insertFileQuery = `INSERT INTO files (folder_id, filename, filesize) VALUES (?, ?, ?);`
|
|
||||||
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 ? LIMIT ? OFFSET ?;`
|
|
||||||
var getFolderQuery = `SELECT folder FROM folders WHERE id = ? LIMIT 1;`
|
|
||||||
var dropFilesQuery = `DROP TABLE files;`
|
|
||||||
var dropFolderQuery = `DROP TABLE folders;`
|
|
||||||
var getFileQuery = `SELECT files.id, files.folder_id, files.filename, folders.foldername, files.filesize FROM files JOIN folders ON files.folder_id = folders.id WHERE files.id = ? LIMIT 1;`
|
|
||||||
var searchFoldersQuery = `SELECT id, folder, foldername FROM folders WHERE foldername LIKE ? LIMIT ? OFFSET ?;`
|
|
||||||
var getFilesInFolderQuery = `SELECT files.id, files.filename, files.filesize, folders.foldername FROM files JOIN folders ON files.folder_id = folders.id WHERE folder_id = ? LIMIT ? OFFSET ?;`
|
|
||||||
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 ?;`
|
|
||||||
var insertFeedbackQuery = `INSERT INTO feedbacks (time, feedback, header) VALUES (?, ?, ?);`
|
|
||||||
|
|
||||||
type Database struct {
|
|
||||||
sqlConn *sql.DB
|
|
||||||
stmt *Stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
type Stmt struct {
|
|
||||||
initFilesTable *sql.Stmt
|
|
||||||
initFoldersTable *sql.Stmt
|
|
||||||
initFeedbacksTable *sql.Stmt
|
|
||||||
insertFolder *sql.Stmt
|
|
||||||
insertFile *sql.Stmt
|
|
||||||
findFolder *sql.Stmt
|
|
||||||
searchFiles *sql.Stmt
|
|
||||||
getFolder *sql.Stmt
|
|
||||||
dropFiles *sql.Stmt
|
|
||||||
dropFolder *sql.Stmt
|
|
||||||
getFile *sql.Stmt
|
|
||||||
searchFolders *sql.Stmt
|
|
||||||
getFilesInFolder *sql.Stmt
|
|
||||||
getRandomFiles *sql.Stmt
|
|
||||||
insertFeedback *sql.Stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Db *Database `json:"-"`
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Folder_id int64 `json:"folder_id"`
|
|
||||||
Foldername string `json:"foldername"`
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
Filesize int64 `json:"filesize"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Folder struct {
|
|
||||||
Db *Database `json:"-"`
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Folder string `json:"-"`
|
|
||||||
Foldername string `json:"foldername"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) InsertFeedback(time int64, feedback string, header string) (error) {
|
|
||||||
_, err := database.stmt.insertFeedback.Exec(time, feedback, header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) GetRandomFiles(limit int64) ([]File, error) {
|
|
||||||
rows, err := database.stmt.getRandomFiles.Query(limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
files := make([]File, 0)
|
|
||||||
for rows.Next() {
|
|
||||||
file := File{
|
|
||||||
Db: database,
|
|
||||||
}
|
|
||||||
err = rows.Scan(&file.ID, &file.Folder_id, &file.Filename, &file.Foldername, &file.Filesize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) GetFilesInFolder(folder_id int64, limit int64, offset int64) ([]File, error) {
|
|
||||||
rows, err := database.stmt.getFilesInFolder.Query(folder_id, limit, offset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
files := make([]File, 0)
|
|
||||||
for rows.Next() {
|
|
||||||
file := File{
|
|
||||||
Db: database,
|
|
||||||
Folder_id: folder_id,
|
|
||||||
}
|
|
||||||
err = rows.Scan(&file.ID, &file.Filename, &file.Filesize, &file.Foldername)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) SearchFolders(foldername string, limit int64, offset int64) ([]Folder, error) {
|
|
||||||
rows, err := database.stmt.searchFolders.Query("%"+foldername+"%", limit, offset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Error searching folders at query " + err.Error())
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
folders := make([]Folder, 0)
|
|
||||||
for rows.Next() {
|
|
||||||
folder := Folder{
|
|
||||||
Db: database,
|
|
||||||
}
|
|
||||||
err = rows.Scan(&folder.ID, &folder.Folder, &folder.Foldername)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Error scanning SearchFolders" + err.Error())
|
|
||||||
}
|
|
||||||
folders = append(folders, folder)
|
|
||||||
}
|
|
||||||
return folders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) GetFile(id int64) (*File, error) {
|
|
||||||
file := &File{
|
|
||||||
Db: database,
|
|
||||||
}
|
|
||||||
err := database.stmt.getFile.QueryRow(id).Scan(&file.ID, &file.Folder_id, &file.Filename, &file.Foldername, &file.Filesize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) ResetFiles() (error) {
|
|
||||||
log.Println("[db] Reset files")
|
|
||||||
var err error
|
|
||||||
_, err = database.stmt.dropFiles.Exec()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = database.stmt.initFilesTable.Exec()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) ResetFolder() (error) {
|
|
||||||
log.Println("[db] Reset folders")
|
|
||||||
var err error
|
|
||||||
_, err = database.stmt.dropFolder.Exec()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = database.stmt.initFoldersTable.Exec()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) Walk(root string, pattern []string) (error) {
|
|
||||||
patternDict := make(map[string]bool)
|
|
||||||
for _, v := range pattern {
|
|
||||||
patternDict[v] = true
|
|
||||||
}
|
|
||||||
log.Println("[db] Walk", root, patternDict)
|
|
||||||
return filepath.Walk(root, func (path string, info os.FileInfo, err error) (error) {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check pattern
|
|
||||||
ext := filepath.Ext(info.Name())
|
|
||||||
if _, ok := patternDict[ext]; !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert file, folder will aut created
|
|
||||||
err = database.Insert(path, info.Size())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) Path() (string, error) {
|
|
||||||
folder, err := f.Db.GetFolder(f.Folder_id)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return filepath.Join(folder.Folder, f.Filename), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) GetFolder(folderId int64) (*Folder, error) {
|
|
||||||
folder := &Folder{
|
|
||||||
Db: database,
|
|
||||||
}
|
|
||||||
err := database.stmt.getFolder.QueryRow(folderId).Scan(&folder.Folder)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return folder, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) SearchFiles(filename string, limit int64, offset int64) ([]File, error) {
|
|
||||||
rows, err := database.stmt.searchFiles.Query("%"+filename+"%", limit, offset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Error searching files at query " + err.Error())
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
files := make([]File, 0)
|
|
||||||
for rows.Next() {
|
|
||||||
var file File = File{
|
|
||||||
Db: database,
|
|
||||||
}
|
|
||||||
err = rows.Scan(&file.ID, &file.Folder_id, &file.Filename, &file.Foldername, &file.Filesize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Error scanning SearchFiles " + err.Error())
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
if err = rows.Err(); err != nil {
|
|
||||||
return nil, errors.New("Error scanning SearchFiles exit without full result" + err.Error())
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) FindFolder(folder string) (int64, error) {
|
|
||||||
var id int64
|
|
||||||
err := database.stmt.findFolder.QueryRow(folder).Scan(&id)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) InsertFolder(folder string) (int64, error) {
|
|
||||||
result, err := database.stmt.insertFolder.Exec(folder, filepath.Base(folder))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
lastInsertId, err := result.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return lastInsertId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) InsertFile(folderId int64, filename string, filesize int64) (error) {
|
|
||||||
_, err := database.stmt.insertFile.Exec(folderId, filename, filesize)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) Insert(path string, filesize int64) (error) {
|
|
||||||
folder, filename := filepath.Split(path)
|
|
||||||
folderId, err := database.FindFolder(folder)
|
|
||||||
if err != nil {
|
|
||||||
folderId, err = database.InsertFolder(folder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = database.InsertFile(folderId, filename, filesize)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) {
|
|
||||||
var err 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)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// init insert folder statement
|
|
||||||
stmt.insertFolder, err = sqlConn.Prepare(insertFolderQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init findFolder statement
|
|
||||||
stmt.findFolder, err = sqlConn.Prepare(findFolderQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init insertFile stmt
|
|
||||||
stmt.insertFile, err = sqlConn.Prepare(insertFileQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init searchFile stmt
|
|
||||||
stmt.searchFiles, err = sqlConn.Prepare(searchFilesQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init getFolder stmt
|
|
||||||
stmt.getFolder, err = sqlConn.Prepare(getFolderQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init dropFolder stmt
|
|
||||||
stmt.dropFolder, err = sqlConn.Prepare(dropFolderQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init dropFiles stmt
|
|
||||||
stmt.dropFiles, err = sqlConn.Prepare(dropFilesQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init getFile stmt
|
|
||||||
stmt.getFile, err = sqlConn.Prepare(getFileQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init searchFolder stmt
|
|
||||||
stmt.searchFolders, err = sqlConn.Prepare(searchFoldersQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init getFilesInFolder stmt
|
|
||||||
stmt.getFilesInFolder, err = sqlConn.Prepare(getFilesInFolderQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init getRandomFiles
|
|
||||||
stmt.getRandomFiles, err = sqlConn.Prepare(getRandomFilesQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// init insertFeedback
|
|
||||||
stmt.insertFeedback, err = sqlConn.Prepare(insertFeedbackQuery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stmt, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDatabase(dbName string) (*Database, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// open database
|
|
||||||
sqlConn, err := sql.Open("sqlite3", dbName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare statement
|
|
||||||
stmt, err := NewPreparedStatement(sqlConn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// new database
|
|
||||||
database := &Database{
|
|
||||||
sqlConn: sqlConn,
|
|
||||||
stmt: stmt,
|
|
||||||
}
|
|
||||||
|
|
||||||
return database, nil
|
|
||||||
}
|
|
||||||
2
main.go
2
main.go
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"msw-open-music/internal/pkg/api"
|
"msw-open-music/pkg/api"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
84
pkg/api/api.go
Normal file
84
pkg/api/api.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"msw-open-music/pkg/database"
|
||||||
|
"msw-open-music/pkg/tmpfs"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API struct {
|
||||||
|
Db *database.Database
|
||||||
|
Server http.Server
|
||||||
|
token string
|
||||||
|
APIConfig APIConfig
|
||||||
|
Tmpfs *tmpfs.Tmpfs
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIConfig() APIConfig {
|
||||||
|
apiConfig := APIConfig{}
|
||||||
|
return apiConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIConfig struct {
|
||||||
|
DatabaseName string `json:"database_name"`
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
FfmpegThreads int64 `json:"ffmpeg_threads"`
|
||||||
|
FfmpegConfigList []FfmpegConfig `json:"ffmpeg_config_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
APIConfig APIConfig `json:"api"`
|
||||||
|
TmpfsConfig tmpfs.TmpfsConfig `json:"tmpfs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPI(config Config) (*API, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
apiConfig := config.APIConfig
|
||||||
|
tmpfsConfig := config.TmpfsConfig
|
||||||
|
|
||||||
|
db, err := database.NewDatabase(apiConfig.DatabaseName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
apiMux := http.NewServeMux()
|
||||||
|
|
||||||
|
api := &API{
|
||||||
|
Db: db,
|
||||||
|
Server: http.Server{
|
||||||
|
Addr: apiConfig.Addr,
|
||||||
|
Handler: mux,
|
||||||
|
},
|
||||||
|
APIConfig: apiConfig,
|
||||||
|
}
|
||||||
|
api.Tmpfs = tmpfs.NewTmpfs(tmpfsConfig)
|
||||||
|
|
||||||
|
// mount api
|
||||||
|
apiMux.HandleFunc("/hello", api.HandleOK)
|
||||||
|
apiMux.HandleFunc("/get_file", api.HandleGetFile)
|
||||||
|
apiMux.HandleFunc("/get_file_direct", api.HandleGetFileDirect)
|
||||||
|
apiMux.HandleFunc("/search_files", api.HandleSearchFiles)
|
||||||
|
apiMux.HandleFunc("/search_folders", api.HandleSearchFolders)
|
||||||
|
apiMux.HandleFunc("/get_files_in_folder", api.HandleGetFilesInFolder)
|
||||||
|
apiMux.HandleFunc("/get_random_files", api.HandleGetRandomFiles)
|
||||||
|
apiMux.HandleFunc("/get_file_stream", api.HandleGetFileStream)
|
||||||
|
apiMux.HandleFunc("/get_ffmpeg_config_list", api.HandleGetFfmpegConfigs)
|
||||||
|
apiMux.HandleFunc("/feedback", api.HandleFeedback)
|
||||||
|
apiMux.HandleFunc("/get_file_info", api.HandleGetFileInfo)
|
||||||
|
apiMux.HandleFunc("/get_file_stream_direct", api.HandleGetFileStreamDirect)
|
||||||
|
apiMux.HandleFunc("/prepare_file_stream_direct", api.HandlePrepareFileStreamDirect)
|
||||||
|
// below needs token
|
||||||
|
apiMux.HandleFunc("/walk", api.HandleWalk)
|
||||||
|
apiMux.HandleFunc("/reset", api.HandleReset)
|
||||||
|
apiMux.HandleFunc("/add_ffmpeg_config", api.HandleAddFfmpegConfig)
|
||||||
|
|
||||||
|
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", apiMux))
|
||||||
|
mux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web/build"))))
|
||||||
|
|
||||||
|
api.token = apiConfig.Token
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
||||||
17
pkg/api/check.go
Normal file
17
pkg/api/check.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *API) CheckLimit(w http.ResponseWriter, r *http.Request, limit int64) error {
|
||||||
|
if limit <= 0 || limit > 10 {
|
||||||
|
log.Println("[api] [Warning] Limit error", limit)
|
||||||
|
err := errors.New(`"limit" can't be zero or more than 10`)
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
26
pkg/api/handle_common.go
Normal file
26
pkg/api/handle_common.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleStatus(w http.ResponseWriter, r *http.Request, status string) {
|
||||||
|
s := &Status{
|
||||||
|
Status: status,
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok Status = Status{
|
||||||
|
Status: "OK",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleOK(w http.ResponseWriter, r *http.Request) {
|
||||||
|
json.NewEncoder(w).Encode(&ok)
|
||||||
|
}
|
||||||
81
pkg/api/handle_database_manage.go
Normal file
81
pkg/api/handle_database_manage.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WalkRequest struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
Root string `json:"root"`
|
||||||
|
Pattern []string `json:"pattern"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResetRequest struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleReset(w http.ResponseWriter, r *http.Request) {
|
||||||
|
resetRequest := &ResetRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(resetRequest)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check token
|
||||||
|
err = api.CheckToken(w, r, resetRequest.Token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
err = api.Db.ResetFiles()
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = api.Db.ResetFolder()
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.HandleStatus(w, r, "Database reseted")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleWalk(w http.ResponseWriter, r *http.Request) {
|
||||||
|
walkRequest := &WalkRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(walkRequest)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check token match
|
||||||
|
err = api.CheckToken(w, r, walkRequest.Token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check root empty
|
||||||
|
if walkRequest.Root == "" {
|
||||||
|
api.HandleErrorString(w, r, `key "root" can't be empty`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check pattern empty
|
||||||
|
if len(walkRequest.Pattern) == 0 {
|
||||||
|
api.HandleErrorString(w, r, `"[]pattern" can't be empty`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk
|
||||||
|
err = api.Db.Walk(walkRequest.Root, walkRequest.Pattern)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.HandleStatus(w, r, "Database udpated")
|
||||||
|
}
|
||||||
28
pkg/api/handle_error.go
Normal file
28
pkg/api/handle_error.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *API) HandleError(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
api.HandleErrorString(w, r, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleErrorCode(w http.ResponseWriter, r *http.Request, err error, code int) {
|
||||||
|
api.HandleErrorStringCode(w, r, err.Error(), code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleErrorString(w http.ResponseWriter, r *http.Request, errorString string) {
|
||||||
|
api.HandleErrorStringCode(w, r, errorString, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleErrorStringCode(w http.ResponseWriter, r *http.Request, errorString string, code int) {
|
||||||
|
log.Println("[api] [Error]", code, errorString)
|
||||||
|
errStatus := &Status{
|
||||||
|
Status: errorString,
|
||||||
|
}
|
||||||
|
w.WriteHeader(code)
|
||||||
|
json.NewEncoder(w).Encode(errStatus)
|
||||||
|
}
|
||||||
45
pkg/api/handle_feedback.go
Normal file
45
pkg/api/handle_feedback.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FeedbackRequest struct {
|
||||||
|
Feedback string `json:"feedback"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleFeedback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
feedbackRequest := &FeedbackRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(feedbackRequest)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check empty feedback
|
||||||
|
if feedbackRequest.Feedback == "" {
|
||||||
|
api.HandleErrorString(w, r, `"feedback" can't be empty`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[api] Feedback", feedbackRequest.Feedback)
|
||||||
|
|
||||||
|
headerBuff := &bytes.Buffer{}
|
||||||
|
err = r.Header.Write(headerBuff)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
header := headerBuff.String()
|
||||||
|
|
||||||
|
err = api.Db.InsertFeedback(time.Now().Unix(), feedbackRequest.Feedback, header)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.HandleOK(w, r)
|
||||||
|
}
|
||||||
74
pkg/api/handle_ffmpeg_config.go
Normal file
74
pkg/api/handle_ffmpeg_config.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FfmpegConfig struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Args string `json:"args"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FfmpegConfigList struct {
|
||||||
|
FfmpegConfigList []FfmpegConfig `json:"ffmpeg_config_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) GetFfmpegConfig(configName string) (FfmpegConfig, bool) {
|
||||||
|
ffmpegConfig := FfmpegConfig{}
|
||||||
|
for _, f := range api.APIConfig.FfmpegConfigList {
|
||||||
|
if f.Name == configName {
|
||||||
|
ffmpegConfig = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ffmpegConfig.Name == "" {
|
||||||
|
return ffmpegConfig, false
|
||||||
|
}
|
||||||
|
return ffmpegConfig, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleGetFfmpegConfigs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Println("[api] Get ffmpeg config list")
|
||||||
|
ffmpegConfigList := &FfmpegConfigList{
|
||||||
|
FfmpegConfigList: api.APIConfig.FfmpegConfigList,
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(&ffmpegConfigList)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddFfmpegConfigRequest struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
FfmpegConfig FfmpegConfig `json:"ffmpeg_config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleAddFfmpegConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
addFfmpegConfigRequest := AddFfmpegConfigRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&addFfmpegConfigRequest)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check token
|
||||||
|
err = api.CheckToken(w, r, addFfmpegConfigRequest.Token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check name and args not null
|
||||||
|
if addFfmpegConfigRequest.Name == "" {
|
||||||
|
api.HandleErrorString(w, r, `"ffmpeg_config.name" can't be empty`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if addFfmpegConfigRequest.FfmpegConfig.Args == "" {
|
||||||
|
api.HandleErrorString(w, r, `"ffmpeg_config.args" can't be empty`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[api] Add ffmpeg config")
|
||||||
|
|
||||||
|
api.APIConfig.FfmpegConfigList = append(api.APIConfig.FfmpegConfigList, addFfmpegConfigRequest.FfmpegConfig)
|
||||||
|
|
||||||
|
api.HandleOK(w, r)
|
||||||
|
}
|
||||||
117
pkg/api/handle_get_file_info.go
Normal file
117
pkg/api/handle_get_file_info.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetFileRequest struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleGetFileInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
getFileRequest := &GetFileRequest{
|
||||||
|
ID: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(getFileRequest)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check empty
|
||||||
|
if getFileRequest.ID < 0 {
|
||||||
|
api.HandleErrorString(w, r, `"id" can't be none or negative`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := api.Db.GetFile(getFileRequest.ID)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewEncoder(w).Encode(file)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /get_file
|
||||||
|
// get raw file with io.Copy method
|
||||||
|
func (api *API) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
getFileRequest := &GetFileRequest{
|
||||||
|
ID: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(getFileRequest)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check empty
|
||||||
|
if getFileRequest.ID < 0 {
|
||||||
|
api.HandleErrorString(w, r, `"id" can't be none or negative`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := api.Db.GetFile(getFileRequest.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 pipe raw file", path)
|
||||||
|
|
||||||
|
src, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
io.Copy(w, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// /get_file_direct?id=1
|
||||||
|
// get raw file with http.ServeFile method
|
||||||
|
func (api *API) HandleGetFileDirect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
q := r.URL.Query()
|
||||||
|
ids := q["id"]
|
||||||
|
if len(ids) == 0 {
|
||||||
|
api.HandleErrorString(w, r, `parameter "id" can't be empty`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id, err := strconv.Atoi(ids[0])
|
||||||
|
if err != nil {
|
||||||
|
api.HandleErrorString(w, r, `parameter "id" should be an integer`)
|
||||||
|
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 direct raw file", path)
|
||||||
|
|
||||||
|
http.ServeFile(w, r, path)
|
||||||
|
}
|
||||||
50
pkg/api/handle_get_files_in_folder.go
Normal file
50
pkg/api/handle_get_files_in_folder.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"msw-open-music/pkg/database"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetFilesInFolderRequest struct {
|
||||||
|
Folder_id int64 `json:"folder_id"`
|
||||||
|
Limit int64 `json:"limit"`
|
||||||
|
Offset int64 `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetFilesInFolderResponse struct {
|
||||||
|
Files *[]database.File `json:"files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleGetFilesInFolder(w http.ResponseWriter, r *http.Request) {
|
||||||
|
getFilesInFolderRequest := &GetFilesInFolderRequest{
|
||||||
|
Folder_id: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(getFilesInFolderRequest)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check empyt
|
||||||
|
if getFilesInFolderRequest.Folder_id < 0 {
|
||||||
|
api.HandleErrorString(w, r, `"folder_id" can't be none or negative`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := api.Db.GetFilesInFolder(getFilesInFolderRequest.Folder_id, getFilesInFolderRequest.Limit, getFilesInFolderRequest.Offset)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilesInFolderResponse := &GetFilesInFolderResponse{
|
||||||
|
Files: &files,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[api] Get files in folder", getFilesInFolderRequest.Folder_id)
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(getFilesInFolderResponse)
|
||||||
|
}
|
||||||
25
pkg/api/handle_get_random_files.go
Normal file
25
pkg/api/handle_get_random_files.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"msw-open-music/pkg/database"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetRandomFilesResponse struct {
|
||||||
|
Files *[]database.File `json:"files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleGetRandomFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
|
files, err := api.Db.GetRandomFiles(10)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
getRandomFilesResponse := &GetRandomFilesResponse{
|
||||||
|
Files: &files,
|
||||||
|
}
|
||||||
|
log.Println("[api] Get random files")
|
||||||
|
json.NewEncoder(w).Encode(getRandomFilesResponse)
|
||||||
|
}
|
||||||
48
pkg/api/handle_search_files.go
Normal file
48
pkg/api/handle_search_files.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"msw-open-music/pkg/database"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchFilesRequest struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Limit int64 `json:"limit"`
|
||||||
|
Offset int64 `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchFilesResponse struct {
|
||||||
|
Files []database.File `json:"files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleSearchFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
|
searchFilesRequest := &SearchFilesRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(searchFilesRequest)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check empty
|
||||||
|
if searchFilesRequest.Filename == "" {
|
||||||
|
api.HandleErrorString(w, r, `"filename" can't be empty`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if api.CheckLimit(w, r, searchFilesRequest.Limit) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
searchFilesResponse := &SearchFilesResponse{}
|
||||||
|
|
||||||
|
searchFilesResponse.Files, err = api.Db.SearchFiles(searchFilesRequest.Filename, searchFilesRequest.Limit, searchFilesRequest.Offset)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[api] Search files", searchFilesRequest.Filename, searchFilesRequest.Limit, searchFilesRequest.Offset)
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(searchFilesResponse)
|
||||||
|
}
|
||||||
48
pkg/api/handle_search_folders.go
Normal file
48
pkg/api/handle_search_folders.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"msw-open-music/pkg/database"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchFoldersRequest struct {
|
||||||
|
Foldername string `json:"foldername"`
|
||||||
|
Limit int64 `json:"limit"`
|
||||||
|
Offset int64 `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchFoldersResponse struct {
|
||||||
|
Folders []database.Folder `json:"folders"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) HandleSearchFolders(w http.ResponseWriter, r *http.Request) {
|
||||||
|
searchFoldersRequest := &SearchFoldersRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(searchFoldersRequest)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check empty
|
||||||
|
if searchFoldersRequest.Foldername == "" {
|
||||||
|
api.HandleErrorString(w, r, `"foldername" can't be empty`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if api.CheckLimit(w, r, searchFoldersRequest.Limit) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
searchFoldersResponse := &SearchFoldersResponse{}
|
||||||
|
|
||||||
|
searchFoldersResponse.Folders, err = api.Db.SearchFolders(searchFoldersRequest.Foldername, searchFoldersRequest.Limit, searchFoldersRequest.Offset)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[api] Search folders", searchFoldersRequest.Foldername, searchFoldersRequest.Limit, searchFoldersRequest.Offset)
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(searchFoldersResponse)
|
||||||
|
}
|
||||||
191
pkg/api/handle_stream.go
Normal file
191
pkg/api/handle_stream.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *API) CheckGetFileStream(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
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 err
|
||||||
|
}
|
||||||
|
_, err = strconv.Atoi(ids[0])
|
||||||
|
if err != nil {
|
||||||
|
err = errors.New(`parameter "id" should be an integer`)
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configs := q["config"]
|
||||||
|
if len(configs) == 0 {
|
||||||
|
err = errors.New(`parameter "config" can't be empty`)
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// /get_file_stream?id=1&config=ffmpeg_config_name
|
||||||
|
func (api *API) HandleGetFileStream(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := api.CheckGetFileStream(w, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q := r.URL.Query()
|
||||||
|
ids := q["id"]
|
||||||
|
id, err := strconv.Atoi(ids[0])
|
||||||
|
configs := q["config"]
|
||||||
|
configName := configs[0]
|
||||||
|
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] Stream file", path, configName)
|
||||||
|
|
||||||
|
ffmpegConfig, ok := api.GetFfmpegConfig(configName)
|
||||||
|
if !ok {
|
||||||
|
api.HandleErrorStringCode(w, r, `ffmpeg config not found`, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
args := strings.Split(ffmpegConfig.Args, " ")
|
||||||
|
startArgs := []string{"-threads", strconv.FormatInt(api.APIConfig.FfmpegThreads, 10), "-i", path}
|
||||||
|
endArgs := []string{"-vn", "-f", "ogg", "-"}
|
||||||
|
ffmpegArgs := append(startArgs, args...)
|
||||||
|
ffmpegArgs = append(ffmpegArgs, endArgs...)
|
||||||
|
cmd := exec.Command("ffmpeg", ffmpegArgs...)
|
||||||
|
cmd.Stdout = w
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrepareFileStreamDirectRequest struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ConfigName string `json:"config_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrepareFileStreamDirectResponse struct {
|
||||||
|
Filesize int64 `json:"filesize"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// /prepare_file_stream_direct?id=1&config=ffmpeg_config_name
|
||||||
|
func (api *API) HandlePrepareFileStreamDirect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
prepareFileStreamDirectRequst := &PrepareFileStreamDirectRequest{
|
||||||
|
ID: -1,
|
||||||
|
}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(prepareFileStreamDirectRequst)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check empty
|
||||||
|
if prepareFileStreamDirectRequst.ID < 0 {
|
||||||
|
api.HandleErrorString(w, r, `"id" can't be none or negative`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if prepareFileStreamDirectRequst.ConfigName == "" {
|
||||||
|
api.HandleErrorString(w, r, `"config_name" can't be empty`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := api.Db.GetFile(prepareFileStreamDirectRequst.ID)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srcPath, err := file.Path()
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[api] Prepare stream direct file", srcPath, prepareFileStreamDirectRequst.ConfigName)
|
||||||
|
ffmpegConfig, ok := api.GetFfmpegConfig(prepareFileStreamDirectRequst.ConfigName)
|
||||||
|
if !ok {
|
||||||
|
api.HandleErrorStringCode(w, r, `ffmpeg config not found`, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
objPath := api.Tmpfs.GetObjFilePath(prepareFileStreamDirectRequst.ID, prepareFileStreamDirectRequst.ConfigName)
|
||||||
|
|
||||||
|
// check obj file exists
|
||||||
|
exists := api.Tmpfs.Exits(objPath)
|
||||||
|
if exists {
|
||||||
|
fileInfo, err := os.Stat(objPath)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prepareFileStreamDirectResponse := &PrepareFileStreamDirectResponse{
|
||||||
|
Filesize: fileInfo.Size(),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(prepareFileStreamDirectResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.Tmpfs.Record(objPath)
|
||||||
|
args := strings.Split(ffmpegConfig.Args, " ")
|
||||||
|
startArgs := []string{"-threads", strconv.FormatInt(api.APIConfig.FfmpegThreads, 10), "-i", srcPath}
|
||||||
|
endArgs := []string{"-vn", "-y", objPath}
|
||||||
|
ffmpegArgs := append(startArgs, args...)
|
||||||
|
ffmpegArgs = append(ffmpegArgs, endArgs...)
|
||||||
|
cmd := exec.Command("ffmpeg", ffmpegArgs...)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo, err := os.Stat(objPath)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prepareFileStreamDirectResponse := &PrepareFileStreamDirectResponse{
|
||||||
|
Filesize: fileInfo.Size(),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(prepareFileStreamDirectResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// /get_file_stream_direct?id=1&config=ffmpeg_config_name
|
||||||
|
// return converted file with http.ServeFile method
|
||||||
|
func (api *API) HandleGetFileStreamDirect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := api.CheckGetFileStream(w, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q := r.URL.Query()
|
||||||
|
ids := q["id"]
|
||||||
|
id, err := strconv.Atoi(ids[0])
|
||||||
|
configs := q["config"]
|
||||||
|
configName := configs[0]
|
||||||
|
|
||||||
|
path := api.Tmpfs.GetObjFilePath(int64(id), configName)
|
||||||
|
if api.Tmpfs.Exits(path) {
|
||||||
|
api.Tmpfs.Record(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[api] Get direct cached file", path)
|
||||||
|
|
||||||
|
http.ServeFile(w, r, path)
|
||||||
|
}
|
||||||
18
pkg/api/handle_token.go
Normal file
18
pkg/api/handle_token.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *API) CheckToken(w http.ResponseWriter, r *http.Request, token string) error {
|
||||||
|
if token != api.token {
|
||||||
|
err := errors.New("token not matched")
|
||||||
|
log.Println("[api] [Warning] Token not matched", token)
|
||||||
|
api.HandleErrorCode(w, r, err, 403)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("[api] Token passed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
36
pkg/database/database.go
Normal file
36
pkg/database/database.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
sqlConn *sql.DB
|
||||||
|
stmt *Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDatabase(dbName string) (*Database, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// open database
|
||||||
|
sqlConn, err := sql.Open("sqlite3", dbName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare statement
|
||||||
|
stmt, err := NewPreparedStatement(sqlConn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// new database
|
||||||
|
database := &Database{
|
||||||
|
sqlConn: sqlConn,
|
||||||
|
stmt: stmt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return database, nil
|
||||||
|
}
|
||||||
241
pkg/database/method.go
Normal file
241
pkg/database/method.go
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (database *Database) InsertFeedback(time int64, feedback string, header string) error {
|
||||||
|
_, err := database.stmt.insertFeedback.Exec(time, feedback, header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) GetRandomFiles(limit int64) ([]File, error) {
|
||||||
|
rows, err := database.stmt.getRandomFiles.Query(limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
files := make([]File, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
file := File{
|
||||||
|
Db: database,
|
||||||
|
}
|
||||||
|
err = rows.Scan(&file.ID, &file.Folder_id, &file.Filename, &file.Foldername, &file.Filesize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
files = append(files, file)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) GetFilesInFolder(folder_id int64, limit int64, offset int64) ([]File, error) {
|
||||||
|
rows, err := database.stmt.getFilesInFolder.Query(folder_id, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
files := make([]File, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
file := File{
|
||||||
|
Db: database,
|
||||||
|
Folder_id: folder_id,
|
||||||
|
}
|
||||||
|
err = rows.Scan(&file.ID, &file.Filename, &file.Filesize, &file.Foldername)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
files = append(files, file)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) SearchFolders(foldername string, limit int64, offset int64) ([]Folder, error) {
|
||||||
|
rows, err := database.stmt.searchFolders.Query("%"+foldername+"%", limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Error searching folders at query " + err.Error())
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
folders := make([]Folder, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
folder := Folder{
|
||||||
|
Db: database,
|
||||||
|
}
|
||||||
|
err = rows.Scan(&folder.ID, &folder.Folder, &folder.Foldername)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Error scanning SearchFolders" + err.Error())
|
||||||
|
}
|
||||||
|
folders = append(folders, folder)
|
||||||
|
}
|
||||||
|
return folders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) GetFile(id int64) (*File, error) {
|
||||||
|
file := &File{
|
||||||
|
Db: database,
|
||||||
|
}
|
||||||
|
err := database.stmt.getFile.QueryRow(id).Scan(&file.ID, &file.Folder_id, &file.Filename, &file.Foldername, &file.Filesize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) ResetFiles() error {
|
||||||
|
log.Println("[db] Reset files")
|
||||||
|
var err error
|
||||||
|
_, err = database.stmt.dropFiles.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = database.stmt.initFilesTable.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) ResetFolder() error {
|
||||||
|
log.Println("[db] Reset folders")
|
||||||
|
var err error
|
||||||
|
_, err = database.stmt.dropFolder.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = database.stmt.initFoldersTable.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) Walk(root string, pattern []string) error {
|
||||||
|
patternDict := make(map[string]bool)
|
||||||
|
for _, v := range pattern {
|
||||||
|
patternDict[v] = true
|
||||||
|
}
|
||||||
|
log.Println("[db] Walk", root, patternDict)
|
||||||
|
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check pattern
|
||||||
|
ext := filepath.Ext(info.Name())
|
||||||
|
if _, ok := patternDict[ext]; !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert file, folder will aut created
|
||||||
|
err = database.Insert(path, info.Size())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) GetFolder(folderId int64) (*Folder, error) {
|
||||||
|
folder := &Folder{
|
||||||
|
Db: database,
|
||||||
|
}
|
||||||
|
err := database.stmt.getFolder.QueryRow(folderId).Scan(&folder.Folder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return folder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) SearchFiles(filename string, limit int64, offset int64) ([]File, error) {
|
||||||
|
rows, err := database.stmt.searchFiles.Query("%"+filename+"%", limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Error searching files at query " + err.Error())
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
files := make([]File, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var file File = File{
|
||||||
|
Db: database,
|
||||||
|
}
|
||||||
|
err = rows.Scan(&file.ID, &file.Folder_id, &file.Filename, &file.Foldername, &file.Filesize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Error scanning SearchFiles " + err.Error())
|
||||||
|
}
|
||||||
|
files = append(files, file)
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return nil, errors.New("Error scanning SearchFiles exit without full result" + err.Error())
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) FindFolder(folder string) (int64, error) {
|
||||||
|
var id int64
|
||||||
|
err := database.stmt.findFolder.QueryRow(folder).Scan(&id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) FindFile(folderId int64, filename string) (int64, error) {
|
||||||
|
var id int64
|
||||||
|
err := database.stmt.findFile.QueryRow(folderId, filename).Scan(&id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) InsertFolder(folder string) (int64, error) {
|
||||||
|
result, err := database.stmt.insertFolder.Exec(folder, filepath.Base(folder))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
lastInsertId, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return lastInsertId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) InsertFile(folderId int64, filename string, filesize int64) error {
|
||||||
|
_, err := database.stmt.insertFile.Exec(folderId, filename, filesize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) Insert(path string, filesize int64) error {
|
||||||
|
folder, filename := filepath.Split(path)
|
||||||
|
folderId, err := database.FindFolder(folder)
|
||||||
|
if err != nil {
|
||||||
|
folderId, err = database.InsertFolder(folder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if file exists, skip it
|
||||||
|
_, err = database.FindFile(folderId, filename)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = database.InsertFile(folderId, filename, filesize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
215
pkg/database/sql_stmt.go
Normal file
215
pkg/database/sql_stmt.go
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initFilesTableQuery = `CREATE TABLE IF NOT EXISTS files (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
folder_id INTEGER NOT NULL,
|
||||||
|
filename TEXT NOT NULL,
|
||||||
|
filesize INTEGER NOT NULL
|
||||||
|
);`
|
||||||
|
|
||||||
|
var initFoldersTableQuery = `CREATE TABLE IF NOT EXISTS folders (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
folder TEXT NOT NULL,
|
||||||
|
foldername TEXT NOT NULL
|
||||||
|
);`
|
||||||
|
|
||||||
|
var initFeedbacksTableQuery = `CREATE TABLE IF NOT EXISTS feedbacks (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
time INTEGER NOT NULL,
|
||||||
|
feedback TEXT NOT NULL,
|
||||||
|
header TEXT NOT NULL
|
||||||
|
);`
|
||||||
|
|
||||||
|
var insertFolderQuery = `INSERT INTO folders (folder, foldername)
|
||||||
|
VALUES (?, ?);`
|
||||||
|
|
||||||
|
var findFolderQuery = `SELECT id FROM folders WHERE folder = ? LIMIT 1;`
|
||||||
|
|
||||||
|
var findFileQuery = `SELECT id FROM files WHERE folder_id = ? AND filename = ? LIMIT 1;`
|
||||||
|
|
||||||
|
var insertFileQuery = `INSERT INTO files (folder_id, filename, filesize)
|
||||||
|
VALUES (?, ?, ?);`
|
||||||
|
|
||||||
|
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 ?
|
||||||
|
LIMIT ? OFFSET ?;`
|
||||||
|
|
||||||
|
var getFolderQuery = `SELECT folder FROM folders WHERE id = ? LIMIT 1;`
|
||||||
|
|
||||||
|
var dropFilesQuery = `DROP TABLE files;`
|
||||||
|
|
||||||
|
var dropFolderQuery = `DROP TABLE folders;`
|
||||||
|
|
||||||
|
var getFileQuery = `SELECT
|
||||||
|
files.id, files.folder_id, files.filename, folders.foldername, files.filesize
|
||||||
|
FROM files
|
||||||
|
JOIN folders ON files.folder_id = folders.id
|
||||||
|
WHERE files.id = ?
|
||||||
|
LIMIT 1;`
|
||||||
|
|
||||||
|
var searchFoldersQuery = `SELECT
|
||||||
|
id, folder, foldername
|
||||||
|
FROM folders
|
||||||
|
WHERE foldername LIKE ?
|
||||||
|
LIMIT ? OFFSET ?;`
|
||||||
|
|
||||||
|
var getFilesInFolderQuery = `SELECT
|
||||||
|
files.id, files.filename, files.filesize, folders.foldername
|
||||||
|
FROM files
|
||||||
|
JOIN folders ON files.folder_id = folders.id
|
||||||
|
WHERE folder_id = ?
|
||||||
|
LIMIT ? OFFSET ?;`
|
||||||
|
|
||||||
|
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 ?;`
|
||||||
|
|
||||||
|
var insertFeedbackQuery = `INSERT INTO feedbacks (time, feedback, header)
|
||||||
|
VALUES (?, ?, ?);`
|
||||||
|
|
||||||
|
type Stmt struct {
|
||||||
|
initFilesTable *sql.Stmt
|
||||||
|
initFoldersTable *sql.Stmt
|
||||||
|
initFeedbacksTable *sql.Stmt
|
||||||
|
insertFolder *sql.Stmt
|
||||||
|
insertFile *sql.Stmt
|
||||||
|
findFolder *sql.Stmt
|
||||||
|
findFile *sql.Stmt
|
||||||
|
searchFiles *sql.Stmt
|
||||||
|
getFolder *sql.Stmt
|
||||||
|
dropFiles *sql.Stmt
|
||||||
|
dropFolder *sql.Stmt
|
||||||
|
getFile *sql.Stmt
|
||||||
|
searchFolders *sql.Stmt
|
||||||
|
getFilesInFolder *sql.Stmt
|
||||||
|
getRandomFiles *sql.Stmt
|
||||||
|
insertFeedback *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) {
|
||||||
|
var err 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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// init insert folder statement
|
||||||
|
stmt.insertFolder, err = sqlConn.Prepare(insertFolderQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init findFolder statement
|
||||||
|
stmt.findFolder, err = sqlConn.Prepare(findFolderQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init findFile statement
|
||||||
|
stmt.findFile, err = sqlConn.Prepare(findFileQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init insertFile stmt
|
||||||
|
stmt.insertFile, err = sqlConn.Prepare(insertFileQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init searchFile stmt
|
||||||
|
stmt.searchFiles, err = sqlConn.Prepare(searchFilesQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init getFolder stmt
|
||||||
|
stmt.getFolder, err = sqlConn.Prepare(getFolderQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init dropFolder stmt
|
||||||
|
stmt.dropFolder, err = sqlConn.Prepare(dropFolderQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init dropFiles stmt
|
||||||
|
stmt.dropFiles, err = sqlConn.Prepare(dropFilesQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init getFile stmt
|
||||||
|
stmt.getFile, err = sqlConn.Prepare(getFileQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init searchFolder stmt
|
||||||
|
stmt.searchFolders, err = sqlConn.Prepare(searchFoldersQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init getFilesInFolder stmt
|
||||||
|
stmt.getFilesInFolder, err = sqlConn.Prepare(getFilesInFolderQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init getRandomFiles
|
||||||
|
stmt.getRandomFiles, err = sqlConn.Prepare(getRandomFilesQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// init insertFeedback
|
||||||
|
stmt.insertFeedback, err = sqlConn.Prepare(insertFeedbackQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stmt, err
|
||||||
|
}
|
||||||
30
pkg/database/struct.go
Normal file
30
pkg/database/struct.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Db *Database `json:"-"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Folder_id int64 `json:"folder_id"`
|
||||||
|
Foldername string `json:"foldername"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Filesize int64 `json:"filesize"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Folder struct {
|
||||||
|
Db *Database `json:"-"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Folder string `json:"-"`
|
||||||
|
Foldername string `json:"foldername"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Path() (string, error) {
|
||||||
|
folder, err := f.Db.GetFolder(f.Folder_id)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(folder.Folder, f.Filename), nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ import "./App.css";
|
|||||||
import GetRandomFiles from "./component/GetRandomFiles";
|
import GetRandomFiles from "./component/GetRandomFiles";
|
||||||
import SearchFiles from "./component/SearchFiles";
|
import SearchFiles from "./component/SearchFiles";
|
||||||
import SearchFolders from "./component/SearchFolders";
|
import SearchFolders from "./component/SearchFolders";
|
||||||
|
import FilesInFolder from "./component/FilesInFolder";
|
||||||
import Manage from "./component/Manage";
|
import Manage from "./component/Manage";
|
||||||
import Share from "./component/Share";
|
import Share from "./component/Share";
|
||||||
import AudioPlayer from "./component/AudioPlayer";
|
import AudioPlayer from "./component/AudioPlayer";
|
||||||
@@ -28,10 +29,10 @@ function App() {
|
|||||||
<NavLink to="/" className="nav-link">
|
<NavLink to="/" className="nav-link">
|
||||||
Feeling luckly
|
Feeling luckly
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink to="/search-files" className="nav-link">
|
<NavLink to="/files" className="nav-link">
|
||||||
Files
|
Files
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink to="/search-folders" className="nav-link">
|
<NavLink to="/folders" className="nav-link">
|
||||||
Folders
|
Folders
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink to="/manage" className="nav-link">
|
<NavLink to="/manage" className="nav-link">
|
||||||
@@ -47,20 +48,20 @@ function App() {
|
|||||||
element={<GetRandomFiles setPlayingFile={setPlayingFile} />}
|
element={<GetRandomFiles setPlayingFile={setPlayingFile} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/search-files"
|
path="/files"
|
||||||
element={<SearchFiles setPlayingFile={setPlayingFile} />}
|
element={<SearchFiles setPlayingFile={setPlayingFile} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/search-folders"
|
path="/folders"
|
||||||
element={<SearchFolders setPlayingFile={setPlayingFile} />}
|
element={<SearchFolders setPlayingFile={setPlayingFile} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/search-folders/:id"
|
path="/folders/:id"
|
||||||
element={<SearchFolders setPlayingFile={setPlayingFile} />}
|
element={<FilesInFolder setPlayingFile={setPlayingFile} />}
|
||||||
/>
|
/>
|
||||||
<Route path="/manage" element={<Manage />} />
|
<Route path="/manage" element={<Manage />} />
|
||||||
<Route
|
<Route
|
||||||
path="/share/:id"
|
path="/files/:id/share"
|
||||||
element={<Share setPlayingFile={setPlayingFile} />}
|
element={<Share setPlayingFile={setPlayingFile} />}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ function AudioPlayer(props) {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(`search-folders/${props.playingFile.folder_id}`)
|
navigate(`/folders/${props.playingFile.folder_id}`)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{props.playingFile.foldername}
|
{props.playingFile.foldername}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function FileDialog(props) {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/share/${props.file.id}`);
|
navigate(`/files/${props.file.id}/share`);
|
||||||
props.setShowStatus(false);
|
props.setShowStatus(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function FileEntry(props) {
|
|||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className="clickable"
|
className="clickable"
|
||||||
onClick={() => navigate(`/search-folders/${props.file.folder_id}`)}
|
onClick={() => navigate(`/folders/${props.file.folder_id}`)}
|
||||||
>
|
>
|
||||||
{props.file.foldername}
|
{props.file.foldername}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
60
web/src/component/FilesInFolder.js
Normal file
60
web/src/component/FilesInFolder.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { useParams } from "react-router";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import FilesTable from "./FilesTable";
|
||||||
|
|
||||||
|
function FilesInFolder(props) {
|
||||||
|
let params = useParams();
|
||||||
|
const [files, setFiles] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [offset, setOffset] = useState(0);
|
||||||
|
const limit = 10;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
fetch("/api/v1/get_files_in_folder", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
folder_id: parseInt(params.id),
|
||||||
|
offset: offset,
|
||||||
|
limit: limit,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
setFiles(data.files ? data.files : []);
|
||||||
|
})
|
||||||
|
.catch((error) => alert(error))
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}, [params.id, offset]);
|
||||||
|
|
||||||
|
function nextPage() {
|
||||||
|
setOffset(offset + limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lastPage() {
|
||||||
|
const offsetValue = offset - limit;
|
||||||
|
if (offsetValue < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setOffset(offsetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page">
|
||||||
|
<h3>Files in Folder</h3>
|
||||||
|
<div className="search_toolbar">
|
||||||
|
<button onClick={lastPage}>Last page</button>
|
||||||
|
<button disabled>
|
||||||
|
{isLoading ? "Loading..." : `${offset} - ${offset + files.length}`}
|
||||||
|
</button>
|
||||||
|
<button onClick={nextPage}>Next page</button>
|
||||||
|
</div>
|
||||||
|
<FilesTable setPlayingFile={props.setPlayingFile} files={files} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilesInFolder;
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import FileEntry from "./FileEntry";
|
import FileEntry from "./FileEntry";
|
||||||
|
|
||||||
function FilesTable(props) {
|
function FilesTable(props) {
|
||||||
|
if (props.files.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import { useNavigate } from "react-router";
|
|||||||
|
|
||||||
function FoldersTable(props) {
|
function FoldersTable(props) {
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
if (props.folders.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -14,12 +17,12 @@ function FoldersTable(props) {
|
|||||||
{props.folders.map((folder) => (
|
{props.folders.map((folder) => (
|
||||||
<tr key={folder.id}>
|
<tr key={folder.id}>
|
||||||
<td
|
<td
|
||||||
onClick={() => navigate(`/search-folders/${folder.id}`)}
|
onClick={() => navigate(`/folders/${folder.id}`)}
|
||||||
className="clickable"
|
className="clickable"
|
||||||
>
|
>
|
||||||
{folder.foldername}
|
{folder.foldername}
|
||||||
</td>
|
</td>
|
||||||
<td onClick={() => navigate(`/search-folders/${folder.id}`)}>
|
<td onClick={() => navigate(`/folders/${folder.id}`)}>
|
||||||
<button>View</button>
|
<button>View</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,9 +1,51 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
function Manage() {
|
function Manage() {
|
||||||
|
const [token, setToken] = useState("");
|
||||||
|
const [walkPath, setWalkPath] = useState("");
|
||||||
|
|
||||||
|
function updateDatabase() {
|
||||||
|
fetch("/api/v1/walk", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
token: token,
|
||||||
|
root: walkPath,
|
||||||
|
pattern: [".wav", ".mp3"],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>Manage</h2>
|
<h2>Manage</h2>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={token}
|
||||||
|
placeholder="token"
|
||||||
|
onChange={(e) => setToken(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={walkPath}
|
||||||
|
placeholder="walk path"
|
||||||
|
onChange={(e) => setWalkPath(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
updateDatabase();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Update Database
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Manage;
|
export default Manage;
|
||||||
|
|||||||
@@ -9,25 +9,14 @@ function SearchFiles(props) {
|
|||||||
const limit = 10;
|
const limit = 10;
|
||||||
|
|
||||||
function searchFiles() {
|
function searchFiles() {
|
||||||
if (
|
|
||||||
filename === "" &&
|
|
||||||
(props.folder === undefined || props.folder.id === undefined)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const folder = props.folder ? props.folder : {};
|
|
||||||
const url = folder.id
|
|
||||||
? "/api/v1/get_files_in_folder"
|
|
||||||
: "/api/v1/search_files";
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
fetch(url, {
|
fetch("/api/v1/search_files", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
filename: filename,
|
filename: filename,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
folder_id: folder.id,
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
@@ -36,7 +25,7 @@ function SearchFiles(props) {
|
|||||||
setFiles(files);
|
setFiles(files);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
alert("get_files_in_folder error: " + error);
|
alert("search_files error: " + error);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -55,13 +44,12 @@ function SearchFiles(props) {
|
|||||||
setOffset(offsetValue);
|
setOffset(offsetValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => searchFiles(), [offset, props.folder]); // eslint-disable-line react-hooks/exhaustive-deps
|
useEffect(() => searchFiles(), [offset]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<h3>Search Files</h3>
|
<h3>Search Files</h3>
|
||||||
<div className="search_toolbar">
|
<div className="search_toolbar">
|
||||||
{!props.folder && (
|
|
||||||
<input
|
<input
|
||||||
onChange={(event) => setFilename(event.target.value)}
|
onChange={(event) => setFilename(event.target.value)}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
@@ -72,18 +60,13 @@ function SearchFiles(props) {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter filename"
|
placeholder="Enter filename"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
disabled={!!props.folder}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
searchFiles();
|
searchFiles();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isLoading ? "Loading..." : "Search"}
|
{isLoading ? "Loading..." : "Search"}
|
||||||
</button>
|
</button>
|
||||||
{props.folder && props.folder.foldername && (
|
|
||||||
<button onClick={searchFiles}>{props.folder.foldername}</button>
|
|
||||||
)}
|
|
||||||
<button onClick={lastPage}>Last page</button>
|
<button onClick={lastPage}>Last page</button>
|
||||||
<button disabled>
|
<button disabled>
|
||||||
{offset} - {offset + files.length}
|
{offset} - {offset + files.length}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router";
|
|
||||||
import FoldersTable from "./FoldersTable";
|
import FoldersTable from "./FoldersTable";
|
||||||
import SearchFiles from "./SearchFiles";
|
|
||||||
|
|
||||||
function SearchFolders(props) {
|
function SearchFolders() {
|
||||||
const [foldername, setFoldername] = useState("");
|
const [foldername, setFoldername] = useState("");
|
||||||
const [folders, setFolders] = useState([]);
|
const [folders, setFolders] = useState([]);
|
||||||
const [folder, setFolder] = useState({});
|
|
||||||
const [offset, setOffset] = useState(0);
|
const [offset, setOffset] = useState(0);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const limit = 10;
|
const limit = 10;
|
||||||
@@ -27,13 +24,7 @@ function SearchFolders(props) {
|
|||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
let folders;
|
setFolders(data.folders ? data.folders : []);
|
||||||
if (data.folders) {
|
|
||||||
folders = data.folders;
|
|
||||||
} else {
|
|
||||||
folders = [];
|
|
||||||
}
|
|
||||||
setFolders(folders);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
alert("search_folders error: " + error);
|
alert("search_folders error: " + error);
|
||||||
@@ -55,17 +46,7 @@ function SearchFolders(props) {
|
|||||||
setOffset(offsetValue);
|
setOffset(offsetValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewFolder(folder) {
|
|
||||||
setFolder(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
let params = useParams();
|
|
||||||
useEffect(() => searchFolder(), [offset]); // eslint-disable-line react-hooks/exhaustive-deps
|
useEffect(() => searchFolder(), [offset]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
useEffect(() => {
|
|
||||||
if (params.id !== undefined) {
|
|
||||||
setFolder({ id: parseInt(params.id) });
|
|
||||||
}
|
|
||||||
}, [params.id]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
@@ -90,8 +71,7 @@ function SearchFolders(props) {
|
|||||||
</button>
|
</button>
|
||||||
<button onClick={nextPage}>Next page</button>
|
<button onClick={nextPage}>Next page</button>
|
||||||
</div>
|
</div>
|
||||||
<FoldersTable viewFolder={viewFolder} folders={folders} />
|
<FoldersTable folders={folders} />
|
||||||
<SearchFiles setPlayingFile={props.setPlayingFile} folder={folder} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user