From d79fe6982fe9b58cb58dc0d87d2f55b1673b03e3 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 4 May 2019 17:26:24 -0700 Subject: [PATCH] Add support for file copy and deletion --- .../Daemon/FileRepositoryInterface.php | 16 +++++++++ .../Api/Client/Servers/FileController.php | 32 +++++++++++++++++ .../Client/Servers/Files/CopyFileRequest.php | 27 ++++++++++++++ .../Servers/Files/DeleteFileRequest.php | 27 ++++++++++++++ app/Repositories/Wings/FileRepository.php | 36 +++++++++++++++++++ .../scripts/api/server/files/copyElement.ts | 29 --------------- .../scripts/api/server/files/copyFile.ts | 13 +++++++ .../scripts/api/server/files/deleteElement.ts | 14 -------- .../scripts/api/server/files/deleteFile.ts | 13 +++++++ .../filemanager/modals/CopyFileModal.vue | 10 +++--- .../filemanager/modals/DeleteFileModal.vue | 7 ++-- routes/api-client.php | 2 ++ 12 files changed, 173 insertions(+), 53 deletions(-) create mode 100644 app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php create mode 100644 app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php delete mode 100644 resources/assets/scripts/api/server/files/copyElement.ts create mode 100644 resources/assets/scripts/api/server/files/copyFile.ts delete mode 100644 resources/assets/scripts/api/server/files/deleteElement.ts create mode 100644 resources/assets/scripts/api/server/files/deleteFile.ts diff --git a/app/Contracts/Repository/Daemon/FileRepositoryInterface.php b/app/Contracts/Repository/Daemon/FileRepositoryInterface.php index 10206d072..6ef25332e 100644 --- a/app/Contracts/Repository/Daemon/FileRepositoryInterface.php +++ b/app/Contracts/Repository/Daemon/FileRepositoryInterface.php @@ -65,4 +65,20 @@ interface FileRepositoryInterface extends BaseRepositoryInterface * @return \Psr\Http\Message\ResponseInterface */ public function renameFile(string $from, string $to): ResponseInterface; + + /** + * Copy a given file and give it a unique name. + * + * @param string $location + * @return \Psr\Http\Message\ResponseInterface + */ + public function copyFile(string $location): ResponseInterface; + + /** + * Delete a file or folder for the server. + * + * @param string $location + * @return \Psr\Http\Message\ResponseInterface + */ + public function deleteFile(string $location): ResponseInterface; } diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 08fbd8885..d4479c087 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -10,7 +10,9 @@ use Illuminate\Http\JsonResponse; use Illuminate\Contracts\Cache\Repository as CacheRepository; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\ListFilesRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DownloadFileRequest; @@ -86,6 +88,36 @@ class FileController extends ClientApiController return Response::create('', Response::HTTP_NO_CONTENT); } + /** + * Copies a file on the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest $request + * @return \Illuminate\Http\Response + */ + public function copyFile(CopyFileRequest $request): Response + { + $this->fileRepository + ->setServer($request->getModel(Server::class)) + ->copyFile($request->input('location')); + + return Response::create('', Response::HTTP_NO_CONTENT); + } + + /** + * Deletes a file or folder from the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest $request + * @return \Illuminate\Http\Response + */ + public function delete(DeleteFileRequest $request): Response + { + $this->fileRepository + ->setServer($request->getModel(Server::class)) + ->deleteFile($request->input('location')); + + return Response::create('', Response::HTTP_NO_CONTENT); + } + /** * Configure a reference to a file to download in the cache so that when the * user hits the Daemon and it verifies with the Panel they'll actually be able diff --git a/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php new file mode 100644 index 000000000..4382dd018 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php @@ -0,0 +1,27 @@ + 'required|string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php new file mode 100644 index 000000000..62820b428 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php @@ -0,0 +1,27 @@ + 'required|string', + ]; + } +} diff --git a/app/Repositories/Wings/FileRepository.php b/app/Repositories/Wings/FileRepository.php index b96d1bb50..2879ef95d 100644 --- a/app/Repositories/Wings/FileRepository.php +++ b/app/Repositories/Wings/FileRepository.php @@ -106,4 +106,40 @@ class FileRepository extends BaseWingsRepository implements FileRepositoryInterf ] ); } + + /** + * Copy a given file and give it a unique name. + * + * @param string $location + * @return \Psr\Http\Message\ResponseInterface + */ + public function copyFile(string $location): ResponseInterface + { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/files/copy', $this->getServer()->uuid), + [ + 'json' => [ + 'location' => $location, + ], + ] + ); + } + + /** + * Delete a file or folder for the server. + * + * @param string $location + * @return \Psr\Http\Message\ResponseInterface + */ + public function deleteFile(string $location): ResponseInterface + { + return $this->getHttpClient()->post( + sprintf('/api/servers/%s/files/delete', $this->getServer()->uuid), + [ + 'json' => [ + 'location' => $location, + ], + ] + ); + } } diff --git a/resources/assets/scripts/api/server/files/copyElement.ts b/resources/assets/scripts/api/server/files/copyElement.ts deleted file mode 100644 index a65c50c29..000000000 --- a/resources/assets/scripts/api/server/files/copyElement.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {withCredentials} from "@/api/http"; -import {ServerApplicationCredentials} from "@/store/types"; - -type PathChangeObject = { - currentPath: string, - newPath: string, -} -/** - * Creates a copy of the given file or directory on the Daemon. Expects a fully resolved path - * to be passed through for both data arguments. - */ -export function copyElement(server: string, credentials: ServerApplicationCredentials, data: PathChangeObject, isMove = false): Promise { - return new Promise((resolve, reject) => { - withCredentials(server, credentials).post(`/v1/server/file/${isMove ? 'move' : 'copy'}`, { - from: data.currentPath, - to: data.newPath, - }) - .then(() => resolve()) - .catch(reject); - }); -} - -/** - * Moves a file or folder to a new location on the server. Works almost exactly the same as the copy - * file logic, so it really just passes an extra argument to copy to indicate that it is a move. - */ -export function moveElement(server: string, credentials: ServerApplicationCredentials, data: PathChangeObject): Promise { - return copyElement(server, credentials, data, true); -} diff --git a/resources/assets/scripts/api/server/files/copyFile.ts b/resources/assets/scripts/api/server/files/copyFile.ts new file mode 100644 index 000000000..ab13d53b0 --- /dev/null +++ b/resources/assets/scripts/api/server/files/copyFile.ts @@ -0,0 +1,13 @@ +import http from "@/api/http"; + +/** + * Creates a copy of the given file or directory on the Daemon. Expects a fully resolved path + * to be passed through for both data arguments. + */ +export function copyFile(server: string, location: string): Promise { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${server}/files/copy`, {location}) + .then(() => resolve()) + .catch(reject); + }); +} diff --git a/resources/assets/scripts/api/server/files/deleteElement.ts b/resources/assets/scripts/api/server/files/deleteElement.ts deleted file mode 100644 index 6cb851396..000000000 --- a/resources/assets/scripts/api/server/files/deleteElement.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {withCredentials} from "@/api/http"; -import {ServerApplicationCredentials} from "@/store/types"; - -/** - * Deletes files and/or folders from the server. You should pass through an array of - * file or folder paths to be deleted. - */ -export function deleteElement(server: string, credentials: ServerApplicationCredentials, items: Array): Promise { - return new Promise((resolve, reject) => { - withCredentials(server, credentials).post('/v1/server/file/delete', { items }) - .then(() => resolve()) - .catch(reject); - }) -} diff --git a/resources/assets/scripts/api/server/files/deleteFile.ts b/resources/assets/scripts/api/server/files/deleteFile.ts new file mode 100644 index 000000000..f7e9ef927 --- /dev/null +++ b/resources/assets/scripts/api/server/files/deleteFile.ts @@ -0,0 +1,13 @@ +import http from "@/api/http"; + +/** + * Deletes files and/or folders from the server. You should pass through an array of + * file or folder paths to be deleted. + */ +export function deleteFile(server: string, location: string): Promise { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${server}/files/delete`, {location}) + .then(() => resolve()) + .catch(reject); + }) +} diff --git a/resources/assets/scripts/components/server/components/filemanager/modals/CopyFileModal.vue b/resources/assets/scripts/components/server/components/filemanager/modals/CopyFileModal.vue index b8465656b..c60690d75 100644 --- a/resources/assets/scripts/components/server/components/filemanager/modals/CopyFileModal.vue +++ b/resources/assets/scripts/components/server/components/filemanager/modals/CopyFileModal.vue @@ -10,12 +10,12 @@ import {DirectoryContentObject} from '@/api/server/types'; import {mapState} from "vuex"; import {ServerState} from '@/store/types'; - import { join } from 'path'; - import {copyElement} from '@/api/server/files/copyElement'; + import {join} from 'path'; + import {copyFile} from '@/api/server/files/copyFile'; import {AxiosError} from "axios"; export default Vue.extend({ - components: { SpinnerModal }, + components: {SpinnerModal}, computed: mapState('server', { server: (state: ServerState) => state.server, @@ -24,7 +24,7 @@ }), props: { - file: { type: Object as () => DirectoryContentObject, required: true }, + file: {type: Object as () => DirectoryContentObject, required: true}, }, /** @@ -46,7 +46,7 @@ } } - copyElement(this.server.uuid, this.credentials, {currentPath: join(this.fm.currentDirectory, this.file.name), newPath}) + copyFile(this.server.uuid, join(this.fm.currentDirectory, this.file.name)) .then(() => this.$emit('close')) .catch((error: AxiosError) => { alert(`There was an error creating a copy of this item: ${error.message}`); diff --git a/resources/assets/scripts/components/server/components/filemanager/modals/DeleteFileModal.vue b/resources/assets/scripts/components/server/components/filemanager/modals/DeleteFileModal.vue index ac161721f..6824c4677 100644 --- a/resources/assets/scripts/components/server/components/filemanager/modals/DeleteFileModal.vue +++ b/resources/assets/scripts/components/server/components/filemanager/modals/DeleteFileModal.vue @@ -26,7 +26,7 @@ import Vue from 'vue'; import Modal from '@/components/core/Modal.vue'; import {DirectoryContentObject} from "@/api/server/types"; - import {deleteElement} from '@/api/server/files/deleteElement'; + import {deleteFile} from '@/api/server/files/deleteFile'; import {mapState} from "vuex"; import {AxiosError} from "axios"; import { join } from 'path'; @@ -75,10 +75,7 @@ this.isLoading = true; // @ts-ignore - deleteElement(this.server.uuid, this.credentials, [ - // @ts-ignore - join(this.fm.currentDirectory, this.object.name) - ]) + deleteFile(this.server.uuid, join(this.fm.currentDirectory, this.object.name)) .then(() => this.$emit('deleted')) .catch((error: AxiosError) => { this.error = `There was an error deleting the requested ${(this.object.directory) ? 'folder' : 'file'}. Response was: ${error.message}`; diff --git a/routes/api-client.php b/routes/api-client.php index 59e3a6b75..039411565 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -44,6 +44,8 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ Route::group(['prefix' => '/files'], function () { Route::get('/list', 'Servers\FileController@listDirectory')->name('api.client.servers.files.list'); Route::put('/rename', 'Servers\FileController@renameFile')->name('api.client.servers.files.rename'); + Route::post('/copy', 'Servers\FileController@copyFile')->name('api.client.servers.files.copy'); + Route::post('/delete', 'Servers\FileController@delete')->name('api.client.servers.files.delete'); Route::post('/create-folder', 'Servers\FileController@createFolder')->name('api.client.servers.files.create-folder'); Route::post('/download/{file}', 'Servers\FileController@download')