diff --git a/src/API/API_functions.tsx b/src/API/API_functions.tsx index 13d5ae3..fe6dc8e 100644 --- a/src/API/API_functions.tsx +++ b/src/API/API_functions.tsx @@ -101,16 +101,53 @@ export async function getServerRootDir() { export async function fetchSandboxStatus() { try { - let response = await fetch('zenodo-jupyterlab/env?env_var=ZENODO_SANDBOX'); - if (response.ok) { - let data = await response.json(); - return data.ZENODO_SANDBOX; - } else { - console.error('Failed to fetch sandbox status'); - return null; - } + const data = await requestAPI(`zenodo-jupyterlab/env?env_var=${encodeURIComponent('ZENODO_SANDBOX')}`, { + method: 'GET' + }); + return(data.json().ZENODO_SANDBOX); } catch (error) { console.error('Error fetching sandbox status:', error); return null; } +} + +export async function downloadFile(recordID: string, filePath: string) { + try { + /* // Fetch the _xsrf token + const xsrfTokenResponse = await fetch('zenodo-jupyterlab/xsrf_token', { + method: 'GET', + }); + const xsrfTokenData = await xsrfTokenResponse.json(); + const xsrfToken = xsrfTokenData.xsrfToken; */ + const start = filePath.indexOf('files/') + 'files/'.length; // Find the start index + const end = filePath.indexOf('/content'); // Find the end index + const fileName = filePath.substring(start, end); + //console.log(fileName, recordID); + const response = await requestAPI('zenodo-jupyterlab/download-file', { + method: 'POST', + body: JSON.stringify({ + file_name: fileName, + record_id: recordID + }), + }); + + console.log(response['status']); + + /* if (!response.ok) { + throw new Error(`Failed to download file: ${response.status}`); + } */ + + /* // Convert the response into a blob and create a download link + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + a.remove(); // Remove the link after download + window.URL.revokeObjectURL(url); // Clean up URL object */ + } catch { + console.error('Error downloading file:'); + } } \ No newline at end of file diff --git a/src/API/handler.tsx b/src/API/handler.tsx index e9146bb..e7036b1 100644 --- a/src/API/handler.tsx +++ b/src/API/handler.tsx @@ -34,18 +34,18 @@ export async function requestAPI(endPoint: string, init: RequestInit): Promise { +/* async function getCsrfToken(): Promise { try { const response = await fetch('/zenodo-jupyterlab/xsrf_token'); // Replace with your actual endpoint if (!response.ok) { @@ -69,4 +69,4 @@ async function getCsrfToken(): Promise { console.error('Failed to fetch CSRF token:', error); return undefined; } -} \ No newline at end of file +} */ \ No newline at end of file diff --git a/src/components/FileBrowser.tsx b/src/components/FileBrowser.tsx index 8351900..60efe6f 100644 --- a/src/components/FileBrowser.tsx +++ b/src/components/FileBrowser.tsx @@ -4,6 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faFolder, faFile } from '@fortawesome/free-solid-svg-icons'; import { getServerRootDir } from '../API/API_functions'; import { FileEntry, OnSelectFile } from './type'; +import { requestAPI } from '../API/handler'; const useStyles = createUseStyles({ container: { @@ -133,17 +134,19 @@ const FileBrowser: React.FC = ({ onSelectFile }) => { setError(''); try { if (!currentPath) return; - - const response = await fetch(`/zenodo-jupyterlab/files?path=${encodeURIComponent(currentPath)}`); - if (response.ok) { - const data = await response.json(); - setEntries(data.entries || []); + console.log(currentPath, rootPath); + const response = await requestAPI(`/zenodo-jupyterlab/files?path=${encodeURIComponent(currentPath)}`, { + method: 'GET' + }); + setEntries(response.entries || []); + /* if (response.ok) { + setEntries(response.entries || []); } else { setError('Failed to fetch file entries.'); - } - } catch (error) { + } */ + } catch { setError('Error fetching file entries.'); - console.error('Error fetching file entries:', error); + console.error('Error fetching file entries:'); } finally { setLoading(false); } diff --git a/src/components/SearchPanel.tsx b/src/components/SearchPanel.tsx index f1be2f2..687392f 100644 --- a/src/components/SearchPanel.tsx +++ b/src/components/SearchPanel.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { searchRecords, searchCommunities, recordInformation } from '../API/API_functions'; +import { searchRecords, searchCommunities, recordInformation, downloadFile } from '../API/API_functions'; import { createUseStyles } from 'react-jss'; import clsx from 'clsx'; @@ -313,12 +313,12 @@ const SearchWidget: React.FC = () => { return fileName; } - function cleanUrl(url: string): string { +/* function cleanUrl(url: string): string { // Remove "/api" and "/content" from the URL return url .replace('/api', '') // Remove "/api" .replace('/content', ''); // Remove "/content" - } + } */ return (
@@ -407,7 +407,12 @@ const SearchWidget: React.FC = () => {

Files:

diff --git a/src/components/upload.tsx b/src/components/upload.tsx index 1deab36..2a2cee3 100644 --- a/src/components/upload.tsx +++ b/src/components/upload.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { createUseStyles } from 'react-jss'; import FileBrowser from './FileBrowser'; import Confirmation from './confirmation'; -import { depositUpload } from '../API/API_functions'; +import { depositUpload, getEnvVariable } from '../API/API_functions'; import { UploadPayload } from './type'; const useStyles = createUseStyles({ @@ -236,6 +236,11 @@ const Upload: React.FC = () => { const [isSandbox, setIsSandbox] = useState(false); const [description, setDescription] = useState(''); const [expandedFile, setExpandedFile] = useState(null); + const [submissionSuccess, setSubmissionSuccess] = useState(false); + const [submissionFailure, setSubmissionFailure] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [recordLink, setRecordLink] = useState(''); + const [recordSandbox, setRecordSandbox] = useState(false); useEffect(() => { async function fetchSandboxStatus() { @@ -302,6 +307,7 @@ const Upload: React.FC = () => { }; const handleConfirm = async () => { + setIsLoading(true); const formData = new FormData(); selectedFilePaths.forEach(filePath => formData.append('filePaths', filePath)); formData.append('title', title); @@ -326,18 +332,45 @@ const Upload: React.FC = () => { console.log(`${key}: ${value}`); } console.log(JSON.stringify(payload)); - const response = await depositUpload(payload); - console.log(response['status']); + try { + const response = await depositUpload(payload); + console.log(response['status']); + + if (response['status'] == "200") { + setSubmissionSuccess(true); // Mark submission as successful + setIsConfirmationVisible(false); // Hide the confirmation section + setSubmissionFailure(false); + const sandbox = await getEnvVariable('ZENODO_SANDBOX'); + setRecordSandbox(sandbox['ZENODO_SANDBOX']==='true' ? true : false); + setRecordLink(response['recordID']); + } else { + setSubmissionFailure(true); + setIsConfirmationVisible(false); + setSubmissionSuccess(false); + } + } catch (error) { + console.error('Error during submission:', error); + } finally { + setIsLoading(false); + } }; const fileName = (filePath: string) => { const segments = filePath.split('/'); return segments.pop(); } + const removeCreator = (index: number) => { + setCreators(creators.filter((_, i) => i !== index)); + }; return (
- {isConfirmationVisible ? ( + {isLoading ? ( +
+

Submitting...

+

Please wait while your submission is being processed.

+
+ ) : isConfirmationVisible ? ( { onEdit={handleEdit} onConfirm={handleConfirm} /> + ) : submissionSuccess ? ( +
+

Submission Successful!

+

Your information has been successfully submitted.

+ {recordSandbox ? ( +
+

Your record has not been published yet. Click here to view it: Record.

+
+ ) + : + ( +
+

Your record has not been published yet. Click here to view it: Record.

+
+ )} +
+ ) : submissionFailure ? ( +
+

Submission Failure!

+

Your information has NOT been successfully submitted. Please try again.

+
) : ( <>

Upload

@@ -459,6 +513,9 @@ const Upload: React.FC = () => { placeholder="Affiliation" className={classes.creatorInput} /> + {index > 0 && ( + + )}
))} diff --git a/zenodo_jupyterlab/server/handlers.py b/zenodo_jupyterlab/server/handlers.py index 9ba4501..d7ca0ab 100644 --- a/zenodo_jupyterlab/server/handlers.py +++ b/zenodo_jupyterlab/server/handlers.py @@ -4,10 +4,12 @@ from jupyter_server.base.handlers import APIHandler, JupyterHandler from jupyter_server.utils import url_path_join import os +import requests from .upload import upload from .testConnection import checkZenodoConnection from .search import searchRecords, searchCommunities, recordInformation +from notebook.services.contents.manager import ContentsManager #from eossr.api.zenodo import ZenodoAPI @@ -36,10 +38,10 @@ async def get(self): response = await checkZenodoConnection() self.finish({'status': response}) """ -class XSRFTokenHandler(JupyterHandler): +""" class XSRFTokenHandler(JupyterHandler): async def get(self): xsrf_token = self.xsrf_token - self.finish({'xsrfToken': xsrf_token.decode('utf-8') if isinstance(xsrf_token, bytes) else xsrf_token}) + self.finish({'xsrfToken': xsrf_token.decode('utf-8') if isinstance(xsrf_token, bytes) else xsrf_token}) """ class SearchRecordHandler(APIHandler): async def get(self): @@ -65,16 +67,22 @@ async def get(self): class FileBrowserHandler(APIHandler): async def get(self): # Use the home directory as the root directory - root_dir = os.getenv("HOME") + #root_dir = os.getenv("HOME") relative_path = self.get_query_argument('path', '') - full_path = os.path.join(root_dir, relative_path) + full_path = os.path.join(os.getcwd(), relative_path) + print(relative_path, full_path) - # Check if the directory exists - if not os.path.isdir(full_path): + if '..' in full_path or not os.path.isdir(full_path): self.set_status(404) self.finish({"error": "Directory not found"}) return + """ # Check if the directory exists + if not os.path.isdir(full_path): + self.set_status(404) + self.finish({"error": "Directory not found"}) + return """ + entries = [] for entry in os.listdir(full_path): if entry.startswith('.'): @@ -84,7 +92,8 @@ async def get(self): entries.append({ "name": entry, "type": "directory" if os.path.isdir(entry_path) else "file", - "path": os.path.relpath(entry_path, root_dir).replace('\\', '/'), # Use relative path from home directory + #"path": os.path.relpath(entry_path, os.getcwd()).replace('\\', '/'), # Use relative path from home directory, + "path": os.path.join(full_path, entry), "modified": datetime.fromtimestamp(entry_stat.st_mtime, tz=timezone.utc).isoformat(), "size": entry_stat.st_size }) @@ -115,8 +124,8 @@ async def post(self): if ZenodoAPIHandler.zAPI == None: self.finish({'status': 'Please Log In before trying to '}) else: - response = await upload(ZenodoAPIHandler.zAPI, form_data) - self.finish({'status': response}) + response, recordID = await upload(ZenodoAPIHandler.zAPI, form_data) + self.finish({'status': response, 'recordID': recordID}) """ if response == None: self.finish({'status': '0'}) else: @@ -124,11 +133,53 @@ async def post(self): else: self.finish(json.dumps('null')) +class DownloadFileHandler(APIHandler): + async def post(self): + data = self.get_json_body() + file_name = data.get('file_name', '') + recordID = data.get('record_id', '') + + # Define the path to save the file in the server's HOME directory + home_dir = os.getenv("HOME") # Get the HOME directory + if not home_dir: + self.set_status(500) + self.finish({'status': 'Error: HOME directory not found.'}) + return + + # Get the file name from the URL (this assumes the URL ends with the file name) + file_url = f'https://zenodo.org/records/{recordID}/files/{file_name}' + if '/' in file_name: + file_name = file_name.replace('/', '_') + #self.finish({'status': file_name}) + file_path = os.path.join(home_dir, file_name) # Full path to save the file + + try: + response = requests.get(file_url) + + if response.status_code != 200: + self.set_status(500) + self.finish({'status': f'Failed to download file: {response.status_code}'}) + return + + # Stream and write the file directly to the home directory + with open(file_path, 'wb') as f: + f.write(response.content) # Write the file body to the specified path + + + # File saved successfully in the remote home directory + #self.set_header('Content-Type', 'application/json') + self.finish({'status': response.status_code}) + return + + except Exception as e: + self.set_status(500) + self.finish({'status': f'Error during request: {e}'}) class ServerInfoHandler(APIHandler): async def get(self): home_dir = os.getenv("HOME") + #home_dir = os.getcwd() # Respond with the $HOME directory self.finish({'root_dir': home_dir}) @@ -140,14 +191,15 @@ def setup_handlers(web_app): handlers = [ (url_path_join(base_path, 'env'), EnvHandler), (url_path_join(base_path, 'code'), CodeHandler), - (url_path_join(base_path, 'xsrf_token'), XSRFTokenHandler), + #(url_path_join(base_path, 'xsrf_token'), XSRFTokenHandler), #(url_path_join(base_path, 'test-connection'), ZenodoTestHandler), (url_path_join(base_path, 'search-records'), SearchRecordHandler), (url_path_join(base_path, 'search-communities'), SearchCommunityHandler), (url_path_join(base_path, 'record-info'), RecordInfoHandler), (url_path_join(base_path, 'files'), FileBrowserHandler), (url_path_join(base_path, 'server-info'), ServerInfoHandler), - (url_path_join(base_path, 'zenodo-api'), ZenodoAPIHandler) + (url_path_join(base_path, 'zenodo-api'), ZenodoAPIHandler), + (url_path_join(base_path, 'download-file'), DownloadFileHandler) ] web_app.add_handlers(".*$", handlers) \ No newline at end of file diff --git a/zenodo_jupyterlab/server/upload.py b/zenodo_jupyterlab/server/upload.py index 4518e0f..8a8ae51 100644 --- a/zenodo_jupyterlab/server/upload.py +++ b/zenodo_jupyterlab/server/upload.py @@ -23,16 +23,27 @@ async def createMetadata(zAPI, recordID, form_data): response = zAPI.set_deposit_metadata(recordID, json_metadata) return response +async def uploadFiles(zAPI, recordID, fileArray): + for file in fileArray: + fileName = file.split('/')[-1] + try: + response = zAPI.upload_file_deposit(recordID, fileName, file) + except: + return None + return response + async def upload(zAPI, form_data): if zAPI == None: return None try: recordID = await createDeposit(zAPI) response = await createMetadata(zAPI, str(recordID), form_data) - if response != None: - return "Success" - else: - return "Adding the metadata returned a None response." + if response == None: + return None + response = await uploadFiles(zAPI, recordID, form_data.get('filePaths')) + if response == None: + return "File failure in checking" + return "200", recordID except: - return None + return None, ''