commit 84a7206c08ce2f963d2ddb9652cc2f6eb55ce676 Author: heimoshuiyu Date: Fri May 21 01:00:58 2021 +0800 后端初步完成 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c87cde8 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module msw-open-music + +go 1.16 + +require github.com/mattn/go-sqlite3 v1.14.7 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..96ff824 --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go new file mode 100644 index 0000000..33a613f --- /dev/null +++ b/internal/pkg/api/api.go @@ -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 +} diff --git a/internal/pkg/api/api_test.go b/internal/pkg/api/api_test.go new file mode 100644 index 0000000..c1a11ba --- /dev/null +++ b/internal/pkg/api/api_test.go @@ -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()) +} diff --git a/internal/pkg/database/database.go b/internal/pkg/database/database.go new file mode 100644 index 0000000..1c7bd68 --- /dev/null +++ b/internal/pkg/database/database.go @@ -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 +} diff --git a/internal/pkg/database/database_test.go b/internal/pkg/database/database_test.go new file mode 100644 index 0000000..fe1778c --- /dev/null +++ b/internal/pkg/database/database_test.go @@ -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()) + //} + +}