Merge branch 'master' into dbms
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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