Rename + list files bugfix

This commit is contained in:
Roman 2021-03-31 16:02:29 +02:00
parent 0df60ddd96
commit 42d2a3d919
5 changed files with 325 additions and 55 deletions

@ -111,26 +111,65 @@ namespace Api {
}
}
private function insert(&$files, $row) {
$entry = array("uid" => $row["uid"], "name" => $row["name"], "isDirectory" => $row["directory"]);
if ($row["directory"]) {
$entry["items"] = array();
} else {
$entry["size"] = @filesize($row["path"]);
$entry["mimeType"] = @mime_content_type($row["path"]);
}
$dir =& $this->findDirectory($files, $row["parentId"]);
if ($dir !== $files || $row["parentId"] === null) {
$dir[$row["uid"]] = $entry;
return true;
}
return false;
}
protected function createFileList($res) {
$files = array();
// Create temporary files
$tempFiles = [];
foreach ($res as $row) {
if ($row["uid"] === null) continue;
$fileId = $row["uid"];
$parentId = $row["parentId"];
$fileName = $row["name"];
$isDirectory = $row["directory"];
$fileElement = array("uid" => $fileId, "name" => $fileName, "isDirectory" => $isDirectory);
if ($isDirectory) {
$fileElement["items"] = array();
} else {
$fileElement["size"] = @filesize($row["path"]);
$fileElement["mimeType"] = @mime_content_type($row["path"]);
if ($fileId === null) {
continue;
}
$dir =& $this->findDirectory($files, $parentId);
$dir[$fileId] = $fileElement;
unset($dir);
// insert all files/dirs in the root directory
if ($parentId === null) {
$this->insert($files, $row);
} else {
// save other files temporary
$tempFiles[$fileId] = $row;
}
}
// repeatedly try to find the directory
// a bit ugly code over here..
$filesToProcess = count($tempFiles);
while (count($tempFiles) > 0) {
foreacH($tempFiles as $fileId => $row) {
if ($this->insert($files, $row)) {
unset($tempFiles[$fileId]);
}
}
// some files could not be inserted for some reason
if (count($tempFiles) === $filesToProcess) {
break;
} else {
$filesToProcess = count($tempFiles);
}
}
return $files;
}
@ -434,9 +473,175 @@ namespace Api\File {
}
}
class Rename extends FileAPI { }
class Rename extends FileAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
"id" => new Parameter("id", Parameter::TYPE_INT),
"name" => new StringType("name", 64, false),
"token" => new StringType("token", 36, true, null)
));
}
class Move extends FileAPI { }
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$fileId = $this->getParam("id");
$newName = $this->getParam("name");
$token = $this->getParam("token");
if (!$this->user->isLoggedIn() && is_null($token)) {
return $this->createError("Permission denied (expected token)");
}
$sql = $this->user->getSQL();
$selectColumns = ($token !== null ? array("parent_id", "user_id") : array("parent_id"));
$query = $sql->select(...$selectColumns)->from("UserFile");
$this->filterFiles($sql, $query, $fileId, $token);
$res = $query->execute();
$this->success = $res !== false;
$this->lastError = $sql->getLastError();
if (!$this->success) {
return false;
} else if (empty($res)) {
return $this->createError("File not found");
}
// check if file already exists
$parentId = $res[0]["parent_id"];
$userId = ($token === null) ? $this->user->getId() : $res[0]["user_id"];
$res = $sql->select($sql->count())
->from("UserFile")
->where(new Compare("name", $newName))
->where(new Compare("parent_id", $parentId))
->where(new Compare("user_id", $userId))
->execute();
$this->success = $res !== false;
$this->lastError = $sql->getLastError();
if (!$this->success) {
return false;
} else if ($res[0]["count"] > 0) {
return $this->createError("A file or directory with this name does already exist");
}
$res = $sql->update("UserFile")
->set("name", $newName)
->where(new Compare("parent_id", $parentId))
->where(new Compare("user_id", $userId))
->where(new Compare("uid", $fileId))
->execute();
$this->success = $res !== false;
$this->lastError = $sql->getLastError();
return $this->success;
}
}
class Move extends FileAPI {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
'parentId' => new Parameter('parentId', Parameter::TYPE_INT, true, null),
"id" => new ArrayType("id", Parameter::TYPE_INT, true),
));
$this->loginRequired = true;
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$fileIds = array_unique($this->getParam("id"));
$destinationId = $this->getParam("parentId");
$userId = $this->user->getId();
// check, if we are trying to do some illegal move
if ($destinationId !== null && in_array($destinationId, $fileIds)) {
return $this->createError("Cannot move a file to the given directory");
}
$query = $sql->select("UserFile.uid", "directory", "name", "parent_id")->from("UserFile");
$this->filterFiles($sql, $query, array_merge($fileIds, [$destinationId]));
$query->orderBy("parent_id")->ascending();
$res = $query->execute();
$this->success = ($res !== false);
$this->lastError = $sql->getLastError();
if (!$this->success) {
return false;
}
$foundFiles = array();
$moveFiles = array();
$skipDirectories = array();
foreach($res as $row) {
$fileId = $row["uid"];
$isDirectory = $row["directory"];
$fileName = $row["name"];
$filesDirectory = $row["parent_id"];
if ($fileId === $destinationId) {
if (!$isDirectory) {
return $this->createError("Cannot move file: Destination is not a directory");
}
} else {
$foundFiles[] = $fileId;
if ($filesDirectory === null || !in_array($filesDirectory, $skipDirectories)) {
$moveFiles[$fileId] = $fileName;
}
if ($isDirectory) {
$skipDirectories[] = $fileId;
}
}
}
if (count($foundFiles) !== count($fileIds)) {
foreach ($fileIds as $fileId) {
if (!array_key_exists($fileId, $foundFiles)) {
return $this->createError("File not found: $fileId");
}
}
}
// check for duplicates
$res = $sql->select($sql->count())
->from("UserFile")
->where(new Compare("user_id", $userId))
->where(new Compare("parent_id", $destinationId))
->where(new CondIn("name", array_values($moveFiles)))
->execute();
$this->success = $res !== false;
$this->lastError = $sql->getLastError();
if (!$this->success) {
return false;
} else if($res[0]["count"] > 0) {
return $this->createError("Cannot move files: the destination directory already contains one or " .
"more files or directories with the same name");
}
// update parent ids
$res = $sql->update("UserFile")
->set("parent_id", $destinationId)
->where(new Compare("user_id", $userId))
->where(new CondIn("uid", array_keys($moveFiles)))
->execute();
$this->success = $res !== false;
$this->lastError = $sql->getLastError();
return $this->success;
}
}
class Upload extends FileAPI {

@ -64,6 +64,14 @@ export default class API {
return this.apiCall("file/createDirectory", { name: name, parentId: parentId });
}
moveFiles(files, parentId = null) {
return this.apiCall("file/move", { id: files, parentId: parentId });
}
rename(id, name, token = null) {
return this.apiCall("file/rename", { id: id, name: name, token: token });
}
getRestrictions() {
return this.apiCall("file/getRestrictions");
}

@ -17,10 +17,14 @@ export function FileBrowser(props) {
let directories = props.directories || {};
let restrictions = props.restrictions || {maxFiles: 0, maxSize: 0, extensions: ""};
let [popup, setPopup] = useState({visible: false, directoryName: "", directory: 0, type: "upload"});
let [popup, setPopup] = useState({ visible: false, name: "", directory: 0, target: null, type: "createDirectory" });
let [alerts, setAlerts] = useState([]);
let [filesToUpload, setFilesToUpload] = useState([]);
function canUpload() {
return api.loggedIn || (tokenObj.valid && tokenObj.type === "upload");
}
function svgMiddle(key, scale = 1.0) {
let width = 48 * scale;
let height = 64 * scale;
@ -122,10 +126,6 @@ export function FileBrowser(props) {
}
}, [filesToUpload]);
function canUpload() {
return api.loggedIn || (tokenObj.valid && tokenObj.type === "upload");
}
function onAddUploadFiles(acceptedFiles, rejectedFiles) {
if (rejectedFiles && rejectedFiles.length > 0) {
@ -179,6 +179,13 @@ export function FileBrowser(props) {
}
}
let selectedIds = getSelectedIds();
let selectedCount = selectedIds.length;
let uploadZone = <></>;
let writePermissions = canUpload();
let uploadedFiles = [];
let alertElements = [];
function createFileList(elements, indentation = 0) {
let rows = [];
let rowIndex = 0;
@ -224,6 +231,10 @@ export function FileBrowser(props) {
<input type={"checkbox"} checked={!!fileElement.selected}
onChange={(e) => onSelectFile(e, uid)}
/>
{ writePermissions ?
<Icon icon={"pencil-alt"} title={"Rename"} className={"ml-2 clickable text-secondary"}
style={{marginTop: "-17px"}} onClick={() => onPopupOpen("rename", uid)} /> :
<></> }
</td>
</tr>
);
@ -236,14 +247,6 @@ export function FileBrowser(props) {
return rows;
}
let rows = createFileList(files);
let selectedIds = getSelectedIds();
let selectedCount = selectedIds.length;
let uploadZone = <></>;
let writePermissions = canUpload();
let uploadedFiles = [];
let alertElements = [];
for (let i = 0; i < alerts.length; i++) {
const alert = alerts[i];
alertElements.push(
@ -287,6 +290,7 @@ export function FileBrowser(props) {
setFilesToUpload(files);
}
let rows = createFileList(files);
if (writePermissions) {
for (let i = 0; i < filesToUpload.length; i++) {
@ -340,6 +344,54 @@ export function FileBrowser(props) {
</>;
}
let singleButton = {
gridColumnStart: 1,
gridColumnEnd: 3,
width: "40%",
margin: "0 auto"
};
function createPopup() {
let title = "";
let inputs = [];
if (popup.type === "createDirectory" || popup.type === "moveFiles") {
inputs.push(
<div className={"form-group"} key={"select-directory"}>
<label>Destination Directory:</label>
<select value={popup.directory} className={"form-control"}
onChange={(e) => onPopupChange(e, "directory")}>
{options}
</select>
</div>
);
}
if (popup.type === "createDirectory" || popup.type === "rename") {
inputs.push(
<div className={"form-group"} key={"input-name"}>
<label>{ popup.type === "createDirectory" ? "Create Directory" : "New Name" }</label>
<input type={"text"} className={"form-control"} value={popup.name} maxLength={32}
placeholder={"Enter name…"}
onChange={(e) => onPopupChange(e, "name")}/>
</div>
);
}
if (popup.type === "createDirectory") {
title = "Create Directory";
} else if (popup.type === "moveFiles") {
title = "Move Files";
} else if (popup.type === "rename") {
title = "Rename File or Directory";
}
return <Popup title={title} visible={popup.visible} buttons={["Ok", "Cancel"]} onClose={onPopupClose}
onClick={onPopupButton}>
{ inputs }
</Popup>
}
return <>
<h4>
<Icon icon={"sync"} className={"mx-3 clickable small"} onClick={fetchFiles}/>
@ -366,12 +418,11 @@ export function FileBrowser(props) {
</tbody>
</table>
<div className={"file-control-buttons"}>
<button type={"button"} className={"btn btn-success"} disabled={selectedCount === 0}
<button type={"button"} className={"btn btn-success"} disabled={selectedCount === 0} style={!writePermissions ? singleButton : {}}
onClick={() => onDownload(selectedIds)}>
<Icon icon={"download"} className={"mr-1"}/>
Download Selected Files ({selectedCount})
</button>
<span/>
{
writePermissions ?
<>
@ -381,11 +432,18 @@ export function FileBrowser(props) {
Delete Selected Files ({selectedCount})
</button>
{api.loggedIn ?
<button type={"button"} className={"btn btn-info"}
<>
<button type={"button"} className={"btn btn-info"}
onClick={(e) => onPopupOpen("createDirectory")}>
<Icon icon={"plus"} className={"mr-1"}/>
Create Directory
</button> :
</button>
<button type={"button"} className={"btn btn-primary"} disabled={selectedCount === 0}
onClick={(e) => onPopupOpen("moveFiles")}>
<Icon icon={"plus"} className={"mr-1"}/>
Move Selected Files ({selectedCount})
</button>
</>:
<></>
}
</>
@ -402,28 +460,11 @@ export function FileBrowser(props) {
<div>
{alertElements}
</div>
<Popup title={"Create Directory"} visible={popup.visible} buttons={["Ok", "Cancel"]} onClose={onPopupClose}
onClick={onPopupButton}>
<div className={"form-group"}>
<label>Destination Directory:</label>
<select value={popup.directory} className={"form-control"}
onChange={(e) => onPopupChange(e, "directory")}>
{options}
</select>
</div>
{popup.type !== "upload" ?
<div className={"form-group"}>
<label>Directory Name</label>
<input type={"text"} className={"form-control"} value={popup.directoryName} maxLength={32}
placeholder={"Enter name…"}
onChange={(e) => onPopupChange(e, "directoryName")}/>
</div> : <></>
}
</Popup>
{ createPopup() }
</>;
function onPopupOpen(type) {
setPopup({...popup, visible: true, type: type});
function onPopupOpen(type, target = null) {
setPopup({...popup, visible: true, type: type, target: target});
}
function onPopupClose() {
@ -439,13 +480,29 @@ export function FileBrowser(props) {
if (btn === "Ok") {
let parentId = popup.directory === 0 ? null : popup.directory;
if (popup.type === "createDirectory") {
api.createDirectory(popup.directoryName, parentId).then((res) => {
api.createDirectory(popup.name, parentId).then((res) => {
if (!res.success) {
pushAlert(res, "Error creating directory");
} else {
fetchFiles();
}
});
} else if (popup.type === "moveFiles") {
api.moveFiles(selectedIds, parentId).then((res) => {
if (!res.success) {
pushAlert(res, "Error moving files");
} else {
fetchFiles();
}
});
} else if (popup.type === "rename") {
api.rename(popup.target, popup.name, tokenObj.valid ? tokenObj.value : null).then((res) => {
if (!res.success) {
pushAlert(res, "Error renaming file or directory");
} else {
fetchFiles();
}
});
}
}

@ -84,9 +84,9 @@ class FileControlPanel extends React.Component {
if (this.onSelectFile(e, uid, fileElement.items)) {
if (!e.target.checked) {
fileElement.selected = false;
} else if (this.getSelectedIds(fileElement.items, false).length === Object.values(fileElement.items).length) {
}/* else if (this.getSelectedIds(fileElement.items, false).length === Object.values(fileElement.items).length) {
fileElement.selected = true;
}
}*/
found = true;
break;
}

2
js/files.min.js vendored

File diff suppressed because one or more lines are too long