Mantid
Loading...
Searching...
No Matches
CatalogPublish.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
9
16
17#include <Poco/Net/AcceptCertificateHandler.h>
18#include <Poco/Net/HTTPRequest.h>
19#include <Poco/Net/HTTPResponse.h>
20#include <Poco/Net/HTTPSClientSession.h>
21#include <Poco/Net/PrivateKeyPassphraseHandler.h>
22#include <Poco/Net/SSLException.h>
23#include <Poco/Net/SSLManager.h>
24#include <Poco/SharedPtr.h>
25#include <Poco/StreamCopier.h>
26#include <Poco/URI.h>
27
28#include <boost/regex.hpp>
29#include <filesystem>
30#include <fstream>
31
32namespace Mantid::ICat {
33DECLARE_ALGORITHM(CatalogPublish)
34
35
36void CatalogPublish::init() {
37 declareProperty(std::make_unique<API::FileProperty>("FileName", "", API::FileProperty::OptionalLoad),
38 "The file to publish.");
39 declareProperty(std::make_unique<API::WorkspaceProperty<API::Workspace>>(
41 "The workspace to publish.");
42 declareProperty("NameInCatalog", "",
43 "The name to give to the file being saved. The file name or workspace "
44 "name is used by default. "
45 "This can only contain alphanumerics, underscores or periods.");
46 declareProperty("InvestigationNumber", "", "The investigation number where the published file will be saved to.");
47 declareProperty("DataFileDescription", "", "A short description of the datafile you are publishing to the catalog.");
48 declareProperty("Session", "", "The session information of the catalog to use.");
49}
50
53 // Used for error checking.
54 std::string ws = getPropertyValue("InputWorkspace");
55 std::string filePath = getPropertyValue("FileName");
56 std::string nameInCatalog = getPropertyValue("NameInCatalog");
58
59 // Prevent invalid/malicious file names being saved to the catalog.
60 boost::regex re("^[a-zA-Z0-9_.]*$");
61 if (!boost::regex_match(nameInCatalog.begin(), nameInCatalog.end(), re)) {
62 throw std::runtime_error("The filename can only contain characters, "
63 "numbers, underscores and periods");
64 }
65
66 // Error checking to ensure a workspace OR a file is selected. Never both.
67 if ((ws.empty() && filePath.empty()) || (!ws.empty() && !filePath.empty())) {
68 throw std::runtime_error("Please select a workspace or a file to publish. Not both.");
69 }
70
71 // Cast a catalog to a catalogInfoService to access publishing functionality.
72 auto catalogInfoService = std::dynamic_pointer_cast<API::ICatalogInfoService>(
73 API::CatalogManager::Instance().getCatalog(getPropertyValue("Session")));
74
75 // Check if the catalog created supports publishing functionality.
76 if (!catalogInfoService)
77 throw std::runtime_error("The catalog that you are using does not support "
78 "publishing to the archives.");
79
80 // The user want to upload a file.
81 if (!filePath.empty()) {
82 std::filesystem::path path(filePath);
83 std::string fileName = path.filename().string();
84 // If the user has not set the name to save the file as, then use the
85 // filename of the file being uploaded.
86 if (nameInCatalog.empty()) {
87 setProperty("NameInCatalog", fileName);
88 g_log.notice("NameInCatalog has not been set. Using filename instead: " + fileName + ".");
89 }
90 } else // The user wants to upload a workspace.
91 {
92 if (nameInCatalog.empty()) {
93 setProperty("NameInCatalog", workspace->getName());
94 g_log.notice("NameInCatalog has not been set. Using workspace name instead: " + workspace->getName() + ".");
95 }
96
97 // Save workspace to a .nxs file in the user's default directory.
99 // Overwrite the filePath string to the location of the file (from which the
100 // workspace was saved to).
101 filePath =
102 Mantid::Kernel::ConfigService::Instance().getString("defaultsave.directory") + workspace->getName() + ".nxs";
103 }
104
105 // Obtain the mode to used base on file extension.
106 std::ios_base::openmode mode = isDataFile(filePath) ? std::ios_base::binary : std::ios_base::in;
107 // Stream the contents of the file the user wants to publish & store it in
108 // file.
109 std::ifstream fileStream(filePath.c_str(), mode);
110 // Verify that the file can be opened correctly.
111 if (fileStream.rdstate() & std::ios::failbit)
112 throw Mantid::Kernel::Exception::FileError("Error on opening file at: ", filePath);
113 // Publish the contents of the file to the server.
114 publish(fileStream,
115 catalogInfoService->getUploadURL(getPropertyValue("InvestigationNumber"), getPropertyValue("NameInCatalog"),
116 getPropertyValue("DataFileDescription")));
117 // If a workspace was published, then we want to also publish the history of a
118 // workspace.
119 if (!ws.empty())
120 publishWorkspaceHistory(catalogInfoService, workspace);
121}
122
128void CatalogPublish::publish(std::istream &fileContents, const std::string &uploadURL) {
129 try {
130 Poco::URI uri(uploadURL);
131 std::string path(uri.getPathAndQuery());
132
133 Poco::SharedPtr<Poco::Net::InvalidCertificateHandler> certificateHandler =
134 new Poco::Net::AcceptCertificateHandler(true);
135 // Currently do not use any means of authentication. This should be updated
136 // IDS has signed certificate.
137 const Poco::Net::Context::Ptr context =
138 new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, "", "", "", Poco::Net::Context::VERIFY_NONE);
139 // Create a singleton for holding the default context. E.g. any future
140 // requests to publish are made to this certificate and context.
141 Poco::Net::SSLManager::instance().initializeClient(nullptr, certificateHandler, context);
142 Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort(), context);
143
144 // Send the HTTP request, and obtain the output stream to write to. E.g. the
145 // data to publish to the server.
146 Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_PUT, path, Poco::Net::HTTPMessage::HTTP_1_1);
147 // Sets the encoding type of the request. This enables us to stream data to
148 // the server.
149 request.setChunkedTransferEncoding(true);
150 std::ostream &os = session.sendRequest(request);
151 // Copy data from the input stream to the server (request) output stream.
152 Poco::StreamCopier::copyStream(fileContents, os);
153
154 // Close the request by requesting a response.
155 Poco::Net::HTTPResponse response;
156 // Store the response for use IF an error occurs (e.g. 404).
157 std::istream &responseStream = session.receiveResponse(response);
158
159 // Obtain the status returned by the server to verify if it was a success.
160 Poco::Net::HTTPResponse::HTTPStatus HTTPStatus = response.getStatus();
161 // The error message returned by the IDS (if one exists).
162 std::string IDSError = CatalogAlgorithmHelper().getIDSError(HTTPStatus, responseStream);
163 // Cancel the algorithm and display the message if it exists.
164 if (!IDSError.empty()) {
165 // As an error occurred we must cancel the algorithm.
166 // We cannot throw an exception here otherwise it is caught below as
167 // Poco::Exception catches runtimes,
168 // and then the I/O error is thrown as it is generated above first.
169 this->cancel();
170 // Output an appropriate error message from the JSON object returned by
171 // the IDS.
172 g_log.error(IDSError);
173 }
174 } catch (Poco::Net::SSLException &error) {
175 throw std::runtime_error(error.displayText());
176 }
177 // This is bad, but is needed to catch a POCO I/O error.
178 // For more info see comments (of I/O error) in CatalogDownloadDataFiles.cpp
179 catch (Poco::Exception &) {
180 }
181}
182
188bool CatalogPublish::isDataFile(const std::string &filePath) {
189 std::filesystem::path path(filePath);
190 std::string extension = path.extension().string();
191 if (!extension.empty() && extension[0] == '.') {
192 extension = extension.substr(1); // remove leading dot
193 }
194 std::transform(extension.begin(), extension.end(), extension.begin(), tolower);
195 return extension == "raw" || extension == "nxs";
196}
197
205 // Create the save nexus algorithm to use.
206 auto saveNexus = Mantid::API::AlgorithmManager::Instance().create("SaveNexus");
207 saveNexus->initialize();
208 // Set the required properties & execute.
209 saveNexus->setProperty("InputWorkspace", workspace->getName());
210 saveNexus->setProperty("FileName", Mantid::Kernel::ConfigService::Instance().getString("defaultsave.directory") +
211 workspace->getName() + ".nxs");
212 saveNexus->execute();
213}
214
222 std::stringstream ss;
223 // Obtain the workspace history as a string.
225 // Use the name the use wants to save the file to the server as and append .py
226 std::filesystem::path nameInCatalog(getPropertyValue("NameInCatalog"));
227 std::string fileName = nameInCatalog.stem().string() + ".py";
228 // Publish the workspace history to the server.
229 publish(ss, catalogInfoService->getUploadURL(getPropertyValue("InvestigationNumber"), fileName,
230 getPropertyValue("DataFileDescription")));
231}
232
239 auto wsHistory = Mantid::API::AlgorithmManager::Instance().createUnmanaged("GeneratePythonScript");
240 wsHistory->initialize();
241 wsHistory->setProperty("InputWorkspace", workspace->getName());
242 wsHistory->execute();
243 return wsHistory->getPropertyValue("ScriptText");
244}
245} // namespace Mantid::ICat
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
double error
IPeaksWorkspace_sptr workspace
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Kernel::Logger & g_log
Definition Algorithm.h:422
void cancel() override
Raises the cancel flag.
@ OptionalLoad
to specify a file to read but the file doesn't have to exist
A property class for workspaces.
const std::string getIDSError(const Poco::Net::HTTPResponse::HTTPStatus &HTTPStatus, std::istream &responseStream)
Obtain the error message returned by the IDS.
CatalogPublish is responsible for publishing user data to the data archive.
void saveWorkspaceToNexus(Mantid::API::Workspace_sptr &workspace)
Saves the workspace as a nexus file to the user's default directory.
void exec() override
Override algorithm execute method.
void publishWorkspaceHistory(Mantid::API::ICatalogInfoService_sptr &catalogInfoService, Mantid::API::Workspace_sptr &workspace)
Publish the history of a given workspace.
const std::string generateWorkspaceHistory(Mantid::API::Workspace_sptr &workspace)
Generate the history of a given workspace.
bool isDataFile(const std::string &filePath)
True if the extension of the file is a datafile.
void publish(std::istream &fileContents, const std::string &uploadURL)
Stream the contents of a file to a given URL.
Records the filename and the description of failure.
Definition Exception.h:98
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void notice(const std::string &msg)
Logs at notice level.
Definition Logger.cpp:126
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
std::shared_ptr< ICatalogInfoService > ICatalogInfoService_sptr
@ Input
An input workspace.
Definition Property.h:53