后端初步完成

This commit is contained in:
2021-05-21 01:00:58 +08:00
commit 84a7206c08
6 changed files with 722 additions and 0 deletions

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module msw-open-music
go 1.16
require github.com/mattn/go-sqlite3 v1.14.7 // indirect

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=

300
internal/pkg/api/api.go Normal file
View File

@@ -0,0 +1,300 @@
package api
import (
"encoding/json"
"errors"
"io"
"log"
"msw-open-music/internal/pkg/database"
"net/http"
"os"
)
type API struct {
Db *database.Database
Server http.Server
token string
}
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"`
Offest int64 `json:"offest"`
}
type SearchFoldersRequest struct {
Foldername string `json:"foldername"`
Limit int64 `json:"limit"`
Offest int64 `json:"offest"`
}
type SearchFilesRespond struct {
Files []database.File `json:"files"`
}
type SearchFoldersRespond struct {
Folders []database.Folder `json:"folders"`
}
func (api *API) CheckToken(token string) (error) {
if token != api.token {
return errors.New("token not matched")
}
return nil
}
func (api *API) HandleTokenNotMatch(w http.ResponseWriter, r *http.Request) {
api.HandleErrorStringCode(w, r, "token not match", 403)
}
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("Handle Error", 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(resetRequest.Token)
if err != nil {
api.HandleTokenNotMatch(w, r)
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(walkRequest.Token)
if err != nil {
api.HandleTokenNotMatch(w, r)
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 searchFilesRequest.Limit == 0 {
api.HandleErrorString(w, r, `"limit" can't be zero`)
return
}
searchFilesRespond := &SearchFilesRespond{}
searchFilesRespond.Files, err = api.Db.SearchFiles(searchFilesRequest.Filename, searchFilesRequest.Limit, searchFilesRequest.Limit)
if err != nil {
api.HandleError(w, r, err)
return
}
json.NewEncoder(w).Encode(searchFilesRespond)
}
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 searchFoldersRequest.Limit == 0 {
api.HandleErrorString(w, r, `"limit" can't be zero`)
return
}
searchFoldersRespond := &SearchFoldersRespond{}
searchFoldersRespond.Folders, err = api.Db.SearchFolders(searchFoldersRequest.Foldername, searchFoldersRequest.Limit, searchFoldersRequest.Limit)
if err != nil {
api.HandleError(w, r, err)
return
}
json.NewEncoder(w).Encode(searchFoldersRespond)
}
type GetFileRequest struct {
ID int64 `json:"id"`
}
func (api *API) HandleGetFile(w http.ResponseWriter, r *http.Request) {
getFilesRequest := &GetFileRequest{
ID: -1,
}
err := json.NewDecoder(r.Body).Decode(getFilesRequest)
if err != nil {
api.HandleError(w, r, err)
return
}
// check empty
if getFilesRequest.ID < 0 {
api.HandleErrorString(w, r, `"id" can't be none or negative`)
return
}
file, err := api.Db.GetFile(getFilesRequest.ID)
if err != nil {
api.HandleError(w, r, err)
return
}
path, err := file.Path()
if err != nil {
api.HandleError(w, r, err)
return
}
src, err := os.Open(path)
if err != nil {
api.HandleError(w, r, err)
return
}
defer src.Close()
io.Copy(w, src)
}
func NewAPI(dbName string, Addr string) (*API, error) {
var err error
db, err := database.NewDatabase(dbName)
if err != nil {
return nil, err
}
mux := http.NewServeMux()
apiMux := http.NewServeMux()
api := &API{
Db: db,
Server: http.Server{
Addr: Addr,
Handler: mux,
},
}
// mount api
apiMux.HandleFunc("/hello", api.HandleOK)
apiMux.HandleFunc("/get_file", api.HandleGetFile)
apiMux.HandleFunc("/search_files", api.HandleSearchFiles)
apiMux.HandleFunc("/search_folders", api.HandleSearchFolders)
// below needs token
apiMux.HandleFunc("/walk", api.HandleWalk)
apiMux.HandleFunc("/reset", api.HandleReset)
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", apiMux))
api.token = "pwd"
return api, nil
}

View File

@@ -0,0 +1,11 @@
package api
import "testing"
func TestAPI(t *testing.T) {
api, err := NewAPI("/tmp/test.sqlite3", ":8080")
if err != nil {
t.Fatal(err.Error())
}
t.Fatal(api.Server.ListenAndServe())
}

View File

@@ -0,0 +1,349 @@
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
);`
var initFoldersTableQuery = `CREATE TABLE IF NOT EXISTS folders (
id INTEGER PRIMARY KEY,
folder TEXT NOT NULL,
foldername 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) VALUES (?, ?);`
var searchFilesQuery = `SELECT files.id, files.folder_id, files.filename, folders.foldername 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 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 ?;`
type Database struct {
sqlConn *sql.DB
stmt *Stmt
}
type Stmt struct {
initFilesTable *sql.Stmt
initFoldersTable *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
}
type File struct {
Db *Database `json:"-"`
ID int64 `json:"id"`
Folder_id int64 `json:"folder_id"`
Foldername string `json:"foldername"`
Filename string `json:"filename"`
}
type Folder struct {
Db *Database `json:"-"`
ID int64 `json:"id"`
Folder string `json:"folder"`
Foldername string `json:"foldername"`
}
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)
if err != nil {
return nil, err
}
return file, nil
}
func (database *Database) ResetFiles() (error) {
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) {
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(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 {
log.Println("False", ext, info.Name())
return nil
}
log.Println("True", ext, info.Name())
// insert file, folder will aut created
err = database.Insert(path)
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)
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) (error) {
_, err := database.stmt.insertFile.Exec(folderId, filename)
if err != nil {
return err
}
return nil
}
func (database *Database) Insert(path string) (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)
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
}
// run init statement
_, err = stmt.initFilesTable.Exec()
if err != nil {
return nil, err
}
_, err = stmt.initFoldersTable.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
}
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
}

View File

@@ -0,0 +1,55 @@
package database
import (
"testing"
)
func TestDatabase(t *testing.T) {
db, err := NewDatabase("/tmp/test.sqlite3")
if err != nil {
t.Fatal("Error creating database" + err.Error())
}
t.Log("database open successfully")
_, err = db.InsertFolder("testfolder")
if err != nil {
t.Fatal(err.Error())
}
t.Log("insertFolders successfully")
id, err := db.FindFolder("testfolder")
if err != nil {
t.Fatal(err.Error())
}
t.Log("folder found", id)
err = db.Insert("/home/hmsy/go/bin/typora-image-ffmpeg")
if err != nil {
t.Fatal(err.Error())
}
files, err := db.SearchFiles("ffmpeg", 100, 0)
if err != nil {
t.Fatal(err.Error())
}
t.Log(files)
file := files[0]
t.Log(file.Path())
err = db.Walk("/home/hmsy/dsa/")
if err != nil {
t.Fatal(err.Error())
}
//err = db.ResetFiles()
//if err != nil {
// t.Fatal(err.Error())
//}
//err = db.ResetFolder()
//if err != nil {
// t.Fatal(err.Error())
//}
}