Merge branch 'master' into dbms

This commit is contained in:
2021-12-11 00:46:38 +08:00
13 changed files with 173 additions and 75 deletions

View File

@@ -411,10 +411,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.

View File

@@ -188,6 +188,15 @@ func (database *Database) FindFolder(folder string) (int64, error) {
return id, nil 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) { func (database *Database) InsertFolder(folder string) (int64, error) {
result, err := database.stmt.insertFolder.Exec(folder, filepath.Base(folder)) result, err := database.stmt.insertFolder.Exec(folder, filepath.Base(folder))
if err != nil { if err != nil {
@@ -217,6 +226,13 @@ func (database *Database) Insert(path string, filesize int64) error {
return err return err
} }
} }
// if file exists, skip it
_, err = database.FindFile(folderId, filename)
if err == nil {
return nil
}
err = database.InsertFile(folderId, filename, filesize) err = database.InsertFile(folderId, filename, filesize)
if err != nil { if err != nil {
return err return err

View File

@@ -21,7 +21,8 @@ var initFoldersTableQuery = `CREATE TABLE IF NOT EXISTS folders (
var initFeedbacksTableQuery = `CREATE TABLE IF NOT EXISTS feedbacks ( var initFeedbacksTableQuery = `CREATE TABLE IF NOT EXISTS feedbacks (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
time INTEGER NOT NULL, time INTEGER NOT NULL,
feedback TEXT NOT NULL feedback TEXT NOT NULL,
header TEXT NOT NULL
);` );`
var initUsersTableQuery = `CREATE TABLE IF NOT EXISTS users ( var initUsersTableQuery = `CREATE TABLE IF NOT EXISTS users (
@@ -104,6 +105,8 @@ VALUES (?, ?);`
var findFolderQuery = `SELECT id FROM folders WHERE folder = ? LIMIT 1;` 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) var insertFileQuery = `INSERT INTO files (folder_id, filename, filesize)
VALUES (?, ?, ?);` VALUES (?, ?, ?);`
@@ -147,8 +150,8 @@ JOIN folders ON files.folder_id = folders.id
ORDER BY RANDOM() ORDER BY RANDOM()
LIMIT ?;` LIMIT ?;`
var insertFeedbackQuery = `INSERT INTO feedbacks (time, feedback) var insertFeedbackQuery = `INSERT INTO feedbacks (time, feedback, header)
VALUES (?, ?);` VALUES (?, ?, ?);`
type Stmt struct { type Stmt struct {
initFilesTable *sql.Stmt initFilesTable *sql.Stmt
@@ -166,6 +169,7 @@ type Stmt struct {
insertFolder *sql.Stmt insertFolder *sql.Stmt
insertFile *sql.Stmt insertFile *sql.Stmt
findFolder *sql.Stmt findFolder *sql.Stmt
findFile *sql.Stmt
searchFiles *sql.Stmt searchFiles *sql.Stmt
getFolder *sql.Stmt getFolder *sql.Stmt
dropFiles *sql.Stmt dropFiles *sql.Stmt
@@ -316,6 +320,12 @@ func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) {
return nil, err return nil, err
} }
// init findFile statement
stmt.findFile, err = sqlConn.Prepare(findFileQuery)
if err != nil {
return nil, err
}
// init insertFile stmt // init insertFile stmt
stmt.insertFile, err = sqlConn.Prepare(insertFileQuery) stmt.insertFile, err = sqlConn.Prepare(insertFileQuery)
if err != nil { if err != nil {

View File

@@ -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>

View File

@@ -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}

View File

@@ -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);
}} }}
> >

View File

@@ -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>

View 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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,9 +1,51 @@
import { useState } from "react";
function Manage() { function Manage() {
return ( const [token, setToken] = useState("");
<div> const [walkPath, setWalkPath] = useState("");
<h2>Manage</h2>
</div> 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 (
<div>
<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>
);
} }
export default Manage; export default Manage;

View File

@@ -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,35 +44,29 @@ 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) => { if (event.key === "Enter") {
if (event.key === "Enter") { searchFiles();
searchFiles(); }
} }}
}} 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}

View File

@@ -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>
); );
} }