diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 6039f71..d23f45c 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -8,6 +8,7 @@ import ( "msw-open-music/internal/pkg/database" "net/http" "os" + "strconv" ) type API struct { @@ -36,23 +37,63 @@ type ResetRequest struct { type SearchFilesRequest struct { Filename string `json:"filename"` Limit int64 `json:"limit"` - Offset int64 `json:"offest"` + Offset int64 `json:"offset"` } type SearchFoldersRequest struct { Foldername string `json:"foldername"` Limit int64 `json:"limit"` - Offset int64 `json:"offest"` + Offset int64 `json:"offset"` } -type SearchFilesRespond struct { +type SearchFilesResponse struct { Files []database.File `json:"files"` } -type SearchFoldersRespond struct { +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"` +} + +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, + } + + json.NewEncoder(w).Encode(getFilesInFolderResponse) +} + func (api *API) CheckToken(token string) (error) { if token != api.token { return errors.New("token not matched") @@ -182,15 +223,15 @@ func (api *API) HandleSearchFiles(w http.ResponseWriter, r *http.Request) { return } - searchFilesRespond := &SearchFilesRespond{} + searchFilesResponse := &SearchFilesResponse{} - searchFilesRespond.Files, err = api.Db.SearchFiles(searchFilesRequest.Filename, searchFilesRequest.Limit, searchFilesRequest.Offset) + searchFilesResponse.Files, err = api.Db.SearchFiles(searchFilesRequest.Filename, searchFilesRequest.Limit, searchFilesRequest.Offset) if err != nil { api.HandleError(w, r, err) return } - json.NewEncoder(w).Encode(searchFilesRespond) + json.NewEncoder(w).Encode(searchFilesResponse) } func (api *API) HandleSearchFolders(w http.ResponseWriter, r *http.Request) { @@ -211,21 +252,48 @@ func (api *API) HandleSearchFolders(w http.ResponseWriter, r *http.Request) { return } - searchFoldersRespond := &SearchFoldersRespond{} + searchFoldersResponse := &SearchFoldersResponse{} - searchFoldersRespond.Folders, err = api.Db.SearchFolders(searchFoldersRequest.Foldername, searchFoldersRequest.Limit, searchFoldersRequest.Offset) + searchFoldersResponse.Folders, err = api.Db.SearchFolders(searchFoldersRequest.Foldername, searchFoldersRequest.Limit, searchFoldersRequest.Offset) if err != nil { api.HandleError(w, r, err) return } - json.NewEncoder(w).Encode(searchFoldersRespond) + json.NewEncoder(w).Encode(searchFoldersResponse) } type GetFileRequest struct { ID int64 `json:"id"` } +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 + } + + http.ServeFile(w, r, path) +} + func (api *API) HandleGetFile(w http.ResponseWriter, r *http.Request) { getFilesRequest := &GetFileRequest{ ID: -1, @@ -286,8 +354,10 @@ func NewAPI(dbName string, Addr string) (*API, error) { // 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) // below needs token apiMux.HandleFunc("/walk", api.HandleWalk) apiMux.HandleFunc("/reset", api.HandleReset) diff --git a/internal/pkg/database/database.go b/internal/pkg/database/database.go index 3b3387b..bee82dd 100644 --- a/internal/pkg/database/database.go +++ b/internal/pkg/database/database.go @@ -30,6 +30,7 @@ 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 id, filename, filesize FROM files WHERE folder_id = ? LIMIT ? OFFSET ?;` type Database struct { sqlConn *sql.DB @@ -48,6 +49,7 @@ type Stmt struct { dropFolder *sql.Stmt getFile *sql.Stmt searchFolders *sql.Stmt + getFilesInFolder *sql.Stmt } type File struct { @@ -66,6 +68,27 @@ type Folder struct { Foldername string `json:"foldername"` } +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) + 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 { @@ -323,6 +346,12 @@ func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) { return nil, err } + // init getFilesInFolder stmt + stmt.getFilesInFolder, err = sqlConn.Prepare(getFilesInFolderQuery) + if err != nil { + return nil, err + } + return stmt, err } diff --git a/web/index.html b/web/index.html index 69dc96d..72366f8 100644 --- a/web/index.html +++ b/web/index.html @@ -6,7 +6,7 @@
- +
diff --git a/web/index.js b/web/index.js index 4168cc0..ba0d594 100644 --- a/web/index.js +++ b/web/index.js @@ -1,30 +1,220 @@ const app = Vue.createApp({ data() { return { - search_filenames: '', - download_total: 0, - download_loaded: 0, } }, +}) + +app.component('root-component', { + data() { + return { + playing_audio_file: {}, + } + }, + template: ` + + + + +`, methods: { + play_audio(file) { + console.log(file) + this.playing_audio_file = file + }, + }, +}) + +app.component('component-search-folders', { + emits: ['play_audio'], + data() { + return { + search_foldernames: "", + folders: [], + offset: 0, + limit: 10, + folder_offset: 0, + folder_limit: 10, + files_in_folder: [], + playing_audio_file: {}, + } + }, + template: ` + + + + +{{ offset }}~{{ offset + folders.length }} + + + + + + + + + + + + + + + + + +
IDFolder NameAction
{{ folder.id }}{{ folder.foldername }}
+ + + + + + + + + + + + + + + + +
IDFilenameFolder NameSizeAction
+`, + methods: { + get_files_in_folder(folder) { + axios.post('/api/v1/get_files_in_folder', { + folder_id: folder.id, + limit: this.folder_limit, + offset: this.folder_offset, + }).then((response) => { + this.files_in_folder = response.data.files + }) + }, + last_page() { + this.offset = this.offset - this.limit + if (this.offset < 0) { + this.offset = 0 + return + } + this.search_folders() + }, + next_page() { + this.offset = this.offset + this.limit + this.search_folders() + }, + first_search_folders() { + this.offset = 0 + this.search_folders() + }, + search_folders() { + axios.post('/api/v1/search_folders', { + foldername: this.search_foldernames, + limit: this.limit, + offset: this.offset, + }).then((response) => { + this.folders = response.data.folders + }) + }, + }, +}) + +app.component('component-update-database', { + data() { + return { + token: "", + root: "", + pattern: [".flac", ".mp3"], + pattern_tmp: "", + s: "", + } + }, + template: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Token
Root
Pattern List
{{ p }}
Status{{ s }}
+`, + methods: { + add_pattern() { + this.pattern.push(this.pattern_tmp) + this.pattern_tmp = "" + }, + reset_database() { + axios.post('/api/v1/reset', { + token: this.token, + }).then((response) => { + this.s = response.data.status + }).catch((err) => { + this.s = err.response.data.status + }) + }, + update_database() { + this.s = "Updating..." + axios.post('/api/v1/walk', { + token: this.token, + root: this.root, + pattern: this.pattern, + }).then((response) => { + this.s = response.data.status + }).catch((err) => { + this.s = err.response.data.status + }) + } }, }) app.component('component-file', { props: ['file'], + emits: ['play_audio'], template: ` {{ file.id }} {{ file.filename }} +{{ file.foldername }} {{ computed_readable_size }} - + + + + `, data() { return { download_loaded: 0, + disabled: false, } }, methods: { + emit_play_audio() { + this.$emit("play_audio", this.file) + }, download_file(file) { + this.disabled = true axios({ url: '/api/v1/get_file', method: 'POST', @@ -33,7 +223,6 @@ app.component('component-file', { id: file.id, }, onDownloadProgress: ProgressEvent => { - console.log(ProgressEvent.loaded) this.download_loaded = ProgressEvent.loaded } }).then((response) => { @@ -43,6 +232,8 @@ app.component('component-file', { link.setAttribute('download', file.filename); document.body.appendChild(link); link.click(); + this.download_loaded = 0 + this.disabled = false }) }, }, @@ -72,41 +263,86 @@ app.component('component-file', { }, }) +app.component('component-audio-player', { + data() { + return { + } + }, + props: ["file"], + template: ` + +`, + computed: { + computed_playing_audio_file_url() { + return '/api/v1/get_file_direct?id=' + this.file.id + }, + computed_show() { + return this.file.id ? true : false + }, + }, +}) + app.component('component-search-files', { + emits: ['play_audio'], template: ` - + + +{{ offset }}~{{ offset + files.length }} + + - +
ID FilenameFolder Name Size Action
`, data() { return { + search_filenames: '', files: [], + offset: 0, + limit: 10, + playing_audio_file: {}, } }, methods: { - search_files(app) { - axios.post('http://localhost:8080/api/v1/search_files', { - filename: app.search_filenames, - limit: 10, - offset: 0, - }).then(function(response) { - app.files = response.data.files + first_search_files() { + this.offset = 0 + this.search_files() + }, + search_files() { + axios.post('/api/v1/search_files', { + filename: this.search_filenames, + limit: this.limit, + offset: this.offset, + }).then((response) => { + this.files = response.data.files }) }, + last_page() { + this.offset = this.offset - this.limit + if (this.offset < 0) { + this.offset = 0 + return + } + this.search_files() + }, + next_page() { + this.offset = this.offset + this.limit + this.search_files() + }, }, }) app.mount('#app')