Newer
Older
// from mantid
#include "MantidScriptRepository/ScriptRepositoryImpl.h"
#include "MantidAPI/ScriptRepositoryFactory.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/Exception.h"
#include "MantidKernel/InternetHelper.h"
#include "MantidKernel/Logger.h"
#include "MantidKernel/NetworkProxy.h"
#include "MantidKernel/ProxyInfo.h"
#include <utility>
using Mantid::Kernel::DateAndTime;
using Mantid::Kernel::Logger;
using Mantid::Kernel::ConfigService;
using Mantid::Kernel::ConfigServiceImpl;
using Mantid::Kernel::ProxyInfo;
using Mantid::Kernel::NetworkProxy;
// from poco
#include <Poco/Path.h>
#include <Poco/File.h>
#include <Poco/TemporaryFile.h>
#include <Poco/URI.h>
#include <Poco/Exception.h>
#include <Poco/Net/NetException.h>
/*#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
*/
#include <Poco/Net/HTMLForm.h>
#include "Poco/Net/FilePartSource.h"
// Visual Studio complains with the inclusion of Poco/FileStream
// disabling this warning.
#if defined(_WIN32) || defined(_WIN64)
#pragma warning(push)
#pragma warning(disable : 4250)
#include <Poco/FileStream.h>
#include <Poco/NullStream.h>
#include <Winhttp.h>
#include <Poco/NullStream.h>
#include <Poco/StreamCopier.h>
#include <Poco/DirectoryIterator.h>
#include <Poco/DateTimeParser.h>
#include <Poco/DateTimeFormatter.h>
// from boost
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/algorithm/string.hpp>
#include <json/json.h>
using boost::property_tree::ptree; // Todo remove once using jsoncpp, plus boost ptree includes
namespace Mantid {
namespace API {
namespace {
/// static logger
Kernel::Logger g_log("ScriptRepositoryImpl");
}
const char *timeformat = "%Y-%b-%d %H:%M:%S";
const char *emptyURL =
"The initialization failed because no URL was given that points "
"to the central repository.\nThis entry should be defined at the "
"properties file, "
"at ScriptRepository";
/**
Write json object to file
*/
void writeJsonFile(const std::string& filename, Json::Value json, const std::string& error)
{
Poco::FileOutputStream filestream(filename);
if (!filestream.good()) {
g_log.error() << error << std::endl;
}
Json::StyledWriter writer;
filestream << writer.write(json);
filestream.close();
}
/**
Read json object from file
*/
Json::Value readJsonFile(const std::string& filename, const std::string& error)
{
Poco::FileInputStream filestream(filename);
if (!filestream.good()) {
g_log.error() << error << std::endl;
}
Json::Reader json_reader;
Json::Value read;
json_reader.parse(filestream, read);
return read;
}
/**
Write string to file
*/
void writeStringFile(const std::string& filename, const std::string& stringToWrite, const std::string& error)
{
Poco::FileStream filestream(filename);
if (!filestream.good()) {
g_log.error() << error << std::endl;
filestream << stringToWrite;
filestream.close();
}
/**
Test if a file with this filename already exists
*/
bool fileExists(const std::string& filename)
{
Poco::File test_file(filename);
if (test_file.exists()) {
return true;
}
return false;
}
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
DECLARE_SCRIPTREPOSITORY(ScriptRepositoryImpl)
/**
The main information that ScriptrepositoryImpl needs to be able
to operate are where the local repository is (or will be), and
the url for the mantid web server.
Usually these values are available at the Mantid properties files,
so, it is possible to construct the ScriptrepositoryImpl without
parameters.
But, for flexibility reasons, (for example, testing with other
repositories), a more general constructor is provided.
In case a string is passed to the constructor different from the
default one, it will have precedence, but it will not override what
is defined by the Mantid properties files. These values will be valid
only for that instance.
Currently, two properties are defined: ScriptLocalRepository, and
ScriptRepository.
@code
// get ScriptRepository and ScriptLocalRepository values from Mantid Config
Service
ScriptrepositoryImpl sharing();
// apply given values
ScriptrepositoryImpl sharing("/tmp/gitrep",
"http://repository.mantidproject.com");
@endcode
*/
ScriptRepositoryImpl::ScriptRepositoryImpl(const std::string &local_rep,
const std::string &remote)
: valid(false) {
// get the local path and the remote path
std::string loc, rem;
ConfigServiceImpl &config = ConfigService::Instance();
remote_upload = config.getString("UploaderWebServer");
if (local_rep.empty() || remote.empty()) {
loc = config.getString("ScriptLocalRepository");
rem = config.getString("ScriptRepository");
} else {
local_repository = local_rep;
remote_url = remote;
}
// the parameters given from the constructor have precedence
if (local_rep.empty())
local_repository = loc;
else
local_repository = local_rep;
if (remote.empty())
remote_url = rem;
else
remote_url = remote;
// empty remote url is not allowed
if (remote_url.empty()) {
g_log.error() << emptyURL << std::endl;
throw ScriptRepoException(emptyURL, "Constructor Failed: remote_url.empty");
}
if (remote_url[remote_url.size() - 1] != '/')
remote_url.append("/");
// if no folder is given, the repository is invalid.
if (local_repository.empty())
return;
if (local_repository[local_repository.size() - 1] != '/')
local_repository.append("/");
g_log.debug() << "ScriptRepository creation pointing to " << local_repository
<< " and " << remote_url << "\n";
// check if the repository is valid.
// parsing the ignore pattern
std::string ignore = ignorePatterns();
boost::replace_all(ignore, "/", "\\/");
boost::replace_all(ignore, ";", "|");
boost::replace_all(ignore, ".", "\\.");
boost::replace_all(ignore, "*", ".*");
ignoreregex = std::string("(").append(ignore).append(")");
// A valid repository must pass 3 tests:
// - An existing folder
// - This folder must have the .repository.json file
// - This folder must have the .local.json file
// These tests will be done with Poco library
Poco::Path local(local_repository);
std::string aux_local_rep;
if (local.isRelative()) {
aux_local_rep = std::string(Poco::Path::current()).append(local_repository);
local_repository = aux_local_rep;
}
Poco::File local_rep_dir(local);
std::string repository_json =
std::string(local_repository).append(".repository.json");
Poco::File rep_json(repository_json);
if (!local_rep_dir.exists() || !rep_json.exists()) {
g_log.information() << "ScriptRepository was not installed at "
<< local_repository << std::endl;
return; // this is an invalid repository, because it was not created
// (installed)
std::string repository_json =
std::string(local_repository).append(".local.json");
Poco::File rep_json(repository_json);
if (!rep_json.exists()) {
g_log.error() << "Corrupted ScriptRepository at " << local_repository
<< ". Please, remove this folder, and install "
"ScriptRepository again" << std::endl;
} catch (Poco::FileNotFoundException & /*ex*/) {
g_log.error()
<< "Testing the existence of repository.json and local.json failed"
<< std::endl;
return;
}
// this is necessary because in windows, the absolute path is given
// with \ slash.
boost::replace_all(local_repository, "\\", "/");
if (local_repository[local_repository.size() - 1] != '/')
local_repository.append("/");
repo.clear();
valid = true;
}
ScriptRepositoryImpl::~ScriptRepositoryImpl() throw() {}
/**
Check the connection with the server through the ::doDownloadFile method.
@path server : The url that will be used to connect.
*/
void ScriptRepositoryImpl::connect(const std::string &server) {
doDownloadFile(server);
}
/** Implements the ScriptRepository::install method.
- creation of the folder for the ScriptRepository (if it does not exists).
- download of the repository.json file (Make it hidden)
- creation of the local.json file. (Make if hidden)
The installation will also upate the ScriptLocalRepository setting, if
necessary,
to match the given path.
If it success, it will change the status of the ScriptRepository as valid.
@note Any directory may be given, from existing directories a new directory.
If an existing directory is given, the installation will install the two
necessary
files to deal with this folder as a ScriptRepository.
@param path : Path for a folder inside the local machine.
*/
void ScriptRepositoryImpl::install(const std::string &path) {
using Poco::DirectoryIterator;
if (remote_url.empty()) {
std::stringstream ss;
ss << "ScriptRepository is configured to download from a invalid URL "
"(empty URL)."
<< "\nThis URL comes from the property file and it is called "
"ScriptRepository.";
throw ScriptRepoException(ss.str());
}
std::string folder = std::string(path);
Poco::File repository_folder(folder);
std::string rep_json_file = std::string(path).append("/.repository.json");
std::string local_json_file = std::string(path).append("/.local.json");
if (!repository_folder.exists()) {
repository_folder.createDirectories();
}
// install the two files inside the given folder
g_log.debug() << "ScriptRepository attempt to doDownload file " << path
<< std::endl;
// download the repository json
doDownloadFile(std::string(remote_url).append("repository.json"),
rep_json_file);
g_log.debug() << "ScriptRepository downloaded repository information"
<< std::endl;
// creation of the instance of local_json file
if (!fileExists(local_json_file))
{
writeStringFile(local_json_file, "{\n}",
"ScriptRepository failed to create local repository");
g_log.debug() << "ScriptRepository created the local repository information"
<< std::endl;
#if defined(_WIN32) || defined(_WIN64)
// set the .repository.json and .local.json hidden
SetFileAttributes(local_json_file.c_str(), FILE_ATTRIBUTE_HIDDEN);
SetFileAttributes(rep_json_file.c_str(), FILE_ATTRIBUTE_HIDDEN);
#endif
// save the path to the config service
//
ConfigServiceImpl &config = ConfigService::Instance();
std::string loc = config.getString("ScriptLocalRepository");
if (loc != path) {
config.setString("ScriptLocalRepository", path);
config.saveConfig(config.getUserFilename());
}
local_repository = path;
// this is necessary because in windows, the absolute path is given
// with \ slash.
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
boost::replace_all(local_repository, "\\", "/");
if (local_repository[local_repository.size() - 1] != '/')
local_repository.append("/");
valid = true;
}
void ScriptRepositoryImpl::ensureValidRepository() {
if (!isValid()) {
std::stringstream ss;
ss << "ScriptRepository is not installed correctly. The current path for "
"ScriptRepository is " << local_repository
<< " but some important files that are required are corrupted or not "
"present."
<< "\nPlease, re-install the ScriptRepository!\n"
<< "Hint: if you have a proper installation in other path, check the "
"property ScriptLocalRepository "
<< "at the Mantid.user.properties and correct it if necessary.";
throw ScriptRepoException(ss.str(), "CORRUPTED");
}
}
/** Implements ScriptRepository::info
For each entry inside the repository, there are some usefull information, that
are stored in
a ::ScriptInfo struct.
Use this method, to get information about the description, last modified date,
the auto update
flag and the author.
@param input_path: The path (relative or absolute) to the file/folder entry,
@return ScriptInfo with the information of this file/folder.
@note: This method requires that ::listFiles was executed at least once.
*/
ScriptInfo ScriptRepositoryImpl::info(const std::string &input_path) {
ensureValidRepository();
std::string path = convertPath(input_path);
ScriptInfo info;
try {
RepositoryEntry &entry = repo.at(path);
info.author = entry.author;
info.pub_date = entry.pub_date;
info.auto_update = entry.auto_update;
info.directory = entry.directory;
} catch (const std::out_of_range &ex) {
std::stringstream ss;
ss << "The file \"" << input_path
<< "\" was not found inside the repository!";
throw ScriptRepoException(ss.str(), ex.what());
}
return info;
}
const std::string &
ScriptRepositoryImpl::description(const std::string &input_path) {
ensureValidRepository();
std::string path = convertPath(input_path);
try {
RepositoryEntry &entry = repo.at(path);
return entry.description;
} catch (const std::out_of_range &ex) {
std::stringstream ss;
ss << "The file \"" << input_path
<< "\" was not found inside the repository!";
throw ScriptRepoException(ss.str(), ex.what());
}
}
/**
Implement the ScriptRepository::listFiles.
It will fill up the ScriptRepositoryImpl::Repository variable in order to
provide
information about the status of the file as well.
In order to list all the values from the repository, it uses three methods:
- ::parseCentralRepository
- ::parseDonwloadedEntries
- ::parseLocalRepository
After this, it will perform a reverse iteration on all the entries of the
repository
in order to evaluate the status (::findStatus) of every file, and will also get
the
status of every directory, by accumulating the influency of every directory.
The listFiles will list:
- all files in the central repository
- all files in the local repository
@return It returns a list of all the files and directories (the relative path
inside the repository).
*/
std::vector<std::string> ScriptRepositoryImpl::listFiles() {
ensureValidRepository();
repo.clear();
assert(repo.size() == 0);
try {
parseCentralRepository(repo);
parseLocalRepository(repo);
parseDownloadedEntries(repo);
// it will not catch ScriptRepositoryExc, because, this means, that it was
// already processed.
// it will proceed in this situation.
} catch (Poco::Exception &ex) {
g_log.error() << "ScriptRepository failed to list all entries inside the "
"repository. Details: " << ex.className() << ":> "
<< ex.displayText() << std::endl;
} catch (std::exception &ex) {
g_log.error() << "ScriptRepository failed to list all entries inside the "
"repository. Details: " << ex.what() << std::endl;
}
std::vector<std::string> out(repo.size());
size_t i = repo.size();
// evaluate the status for all entries
// and also fill up the output vector (in reverse order)
Mantid::API::SCRIPTSTATUS acc_status = Mantid::API::BOTH_UNCHANGED;
std::string last_directory = "";
for (Repository::reverse_iterator it = repo.rbegin(); it != repo.rend();
++it) {
// for every entry, it takes the path and RepositoryEntry
std::string entry_path = it->first;
RepositoryEntry &entry = it->second;
// g_log.debug() << "Evaluating the status of " << entry_path << std::endl;
// fill up the output vector
out[--i] = it->first;
// g_log.debug() << "inserting file: " << it->first << std::endl;
// for the directories, update the status of this directory
if (entry.directory) {
entry.status = acc_status;
if (!entry.remote)
entry.status = Mantid::API::LOCAL_ONLY;
last_directory = entry_path;
} else {
// for the files, it evaluates the status of this file
if (entry.local && !entry.remote) {
// entry local only
entry.status = LOCAL_ONLY;
} else if (!entry.local && entry.remote) {
// entry remote only
entry.status = REMOTE_ONLY;
} else {
// there is no way of not being remote nor local!
// entry is local and is remote
// the following status are available:
// BOTH_CHANGED, BOTH_UNCHANGED, REMOTE_CHANGED, LOCAL_CHANGED.
enum CHANGES { UNCH = 0, REMO = 0X1, LOC = 0X2, BOTH = 0X3 };
int st = UNCH;
// the file is local_changed, if the date of the current file is
// diferent
// from the downloaded one.
if (entry.current_date != entry.downloaded_date)
st |= LOC;
// the file is remote_changed if the date of the pub_date file is
// diferent from the local downloaded pubdate.
if (entry.pub_date > entry.downloaded_pubdate)
st |= REMO;
switch (st) {
case UNCH:
entry.status = BOTH_UNCHANGED;
case REMO:
entry.status = REMOTE_CHANGED;
case LOC:
entry.status = LOCAL_CHANGED;
} // end evaluating the file status
// is this entry a child of the last directory?
if (!last_directory.empty()) {
if (entry_path.find(last_directory) == std::string::npos) {
// no, this entry is not a child of the last directory
// restart the status
acc_status = Mantid::API::BOTH_UNCHANGED;
// update the status of the parent directory:
// the strategy here is to compare binary the current status with the
// acc_state
switch (acc_status | entry.status) {
// pure matching, meaning that the matching is done with the same state
// or with BOTH_UNCHANGED (neutral)
case BOTH_UNCHANGED: // BOTH_UNCHANGED IS 0, so only 0|0 match this option
case REMOTE_ONLY: // REMOTE_ONLY IS 0x01, so only 0|0x01 and 0x01|0x01 match
// this option
case LOCAL_ONLY:
case LOCAL_CHANGED:
case REMOTE_CHANGED:
acc_status = (SCRIPTSTATUS)(acc_status | entry.status);
break;
case LOCAL_ONLY | LOCAL_CHANGED:
acc_status = LOCAL_CHANGED;
break;
case REMOTE_ONLY | REMOTE_CHANGED:
acc_status = REMOTE_CHANGED;
break;
default:
acc_status = BOTH_CHANGED;
break;
/**
Implements the ScriptRepository::download.
@note Require that ::listFiles been called at least once.
The download is able to download files or directories. Internally,
it will assign the job to the ::download_diretory or ::download_file.
This method, just ensure that the entry is valid (wich means,
it is inside the repository).
@param input_path: The path for the file/folder to be downloaded.
@note As a result of the download a new file, the local repository
information .local.repository will be changed.
void ScriptRepositoryImpl::download(const std::string &input_path) {
ensureValidRepository();
std::string file_path = convertPath(input_path);
try {
RepositoryEntry &entry = repo.at(file_path);
if (entry.directory)
download_directory(file_path);
else
download_file(file_path, entry);
} catch (const std::out_of_range &ex) {
// fixme: readable exception
throw ScriptRepoException(ex.what());
}
}
/**
Go recursively to download all the children of an input directory.
@param directory_path : the path for the directory.
*/
void ScriptRepositoryImpl::download_directory(
const std::string &directory_path) {
std::string directory_path_with_slash =
std::string(directory_path).append("/");
bool found = false;
for (Repository::iterator it = repo.begin(); it != repo.end(); ++it) {
// skip all entries that are not children of directory_path
// the map will list the entries in alphabetical order, so,
// when it first find the directory, it will list all the
// childrens of this directory, and them,
// it will list other things, so we can, break the loop
if (it->first.find(directory_path) != 0) {
if (found)
break; // for the sake of performance
else
continue;
found = true;
if (it->first != directory_path &&
it->first.find(directory_path_with_slash) != 0) {
// it is not a children of this entry, just similar. Example:
// TofConverter/README
// TofConverter.py
// these two pass the first test, but will not pass this one.
found = false;
continue;
// now, we are dealing with the children of directory path
if (!it->second.directory)
download_file(it->first, it->second);
else {
// download the directory.
// we will not download the directory, but create one with the
// same name, and update the local json
Poco::File dir(std::string(local_repository).append(it->first));
dir.createDirectories();
it->second.status = BOTH_UNCHANGED;
it->second.downloaded_date = DateAndTime(
Poco::DateTimeFormatter::format(dir.getLastModified(), timeformat));
it->second.downloaded_pubdate = it->second.pub_date;
updateLocalJson(it->first, it->second);
} // end downloading directory
// update the status
it->second.status = BOTH_UNCHANGED; // update this entry
} // end interaction with all entries
}
/**
Download the real file from the remote_url.
@todo describe better this method.
*/
void ScriptRepositoryImpl::download_file(const std::string &file_path,
RepositoryEntry &entry) {
SCRIPTSTATUS state = entry.status;
// if we have the state, this means that the entry is available
if (state == LOCAL_ONLY || state == LOCAL_CHANGED) {
std::stringstream ss;
ss << "The file " << file_path
<< " can not be download because it has only local changes."
<< " If you want, please, publish this file uploading it";
throw ScriptRepoException(ss.str());
}
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
if (state == BOTH_UNCHANGED)
// instead of throwing exception, silently assumes that the download was
// done.
return;
// download the file
std::string url_path = std::string(remote_url).append(file_path);
Poco::TemporaryFile tmpFile;
doDownloadFile(url_path, tmpFile.path());
std::string local_path = std::string(local_repository).append(file_path);
g_log.debug() << "ScriptRepository download url_path: " << url_path << " to "
<< local_path << std::endl;
std::string dir_path;
try {
if (state == BOTH_CHANGED) {
// make a back up of the local version
Poco::File f(std::string(local_repository).append(file_path));
std::string bck = std::string(f.path()).append("_bck");
g_log.notice() << "The current file " << f.path()
<< " has some local changes"
<< " so, a back up copy will be created at " << bck
<< std::endl;
f.copyTo(bck);
}
// ensure that the path to the local_path exists
size_t slash_pos = local_path.rfind('/');
Poco::File file_out(local_path);
if (slash_pos != std::string::npos) {
dir_path =
std::string(local_path.begin(), local_path.begin() + slash_pos);
if (!dir_path.empty()) {
Poco::File dir_parent(dir_path);
if (!dir_parent.exists()) {
dir_parent.createDirectories();
if (!file_out.exists())
file_out.createFile();
} catch (Poco::FileAccessDeniedException &) {
std::stringstream ss;
ss << "You cannot create file at " << local_path << ". Not downloading ...";
throw ScriptRepoException(ss.str());
}
{
Poco::File local(local_path);
entry.downloaded_date = DateAndTime(
Poco::DateTimeFormatter::format(local.getLastModified(), timeformat));
entry.downloaded_pubdate = entry.pub_date;
entry.status = BOTH_UNCHANGED;
}
// Update pythonscripts.directories if necessary
// (TEST_DOWNLOAD_ADD_FOLDER_TO_PYTHON_SCRIPTS)
if (!dir_path.empty()) {
const char *python_sc_option = "pythonscripts.directories";
ConfigServiceImpl &config = ConfigService::Instance();
std::string python_dir = config.getString(python_sc_option);
if (python_dir.find(dir_path) == std::string::npos) {
// this means that the directory is not inside the
// pythonscripts.directories
// add to the repository
python_dir.append(";").append(dir_path);
config.setString(python_sc_option, python_dir);
config.saveConfig(config.getUserFilename());
// the previous code make the path available for the following
// instances of Mantid, but, for the current one, it is necessary
// do add to the python path...
}
}
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
updateLocalJson(file_path, entry); /// FIXME: performance!
g_log.debug() << "ScriptRepository download " << local_path << " success!"
<< std::endl;
}
/**
@todo Describe
*/
SCRIPTSTATUS ScriptRepositoryImpl::fileStatus(const std::string &input_path) {
/// @todo: implement the trigger method to know it we need to revised the
/// directories trees.
ensureValidRepository();
std::string file_path = convertPath(input_path);
// g_log.debug() << "Attempt to ask for the status of "<< file_path <<
// std::endl;
try {
RepositoryEntry &entry = repo.at(file_path);
return entry.status;
} catch (const std::out_of_range &ex) {
std::stringstream ss;
ss << "The file \"" << input_path
<< "\" was not found inside the repository!";
throw ScriptRepoException(ss.str(), ex.what());
}
// this line will never be executed, just for avoid compiler warnings.
return BOTH_UNCHANGED;
}
/**
* Uploads one file to the ScriptRepository web server, pushing, indirectly, to
*the
* git repository. It will send in a POST method, the file and the following
*fields:
* - author : Will identify the author of the change
* - email: Will identify the email of the author
* - comment: Description of the nature of the file or of the update
*
* It will them upload to the URL pointed to UploaderWebServer. It will them
*receive a json file
* with some usefull information about the success or failure of the attempt to
*upload.
* In failure, it will be converted to an appropriated ScriptRepoException.
*/
void ScriptRepositoryImpl::upload(const std::string &file_path,
const std::string &comment,
const std::string &author,
const std::string &email)
{
using namespace Poco::Net;
try {
g_log.notice() << "ScriptRepository uploading " << file_path << " ..."
<< std::endl;
Kernel::InternetHelper inetHelper;
HTMLForm form(HTMLForm::ENCODING_MULTIPART);
// add the fields author, email and comment
form.add("author", author);
form.add("mail", email);
form.add("comment", comment);
// deal with the folder
std::string relative_path = convertPath(file_path);
std::string absolute_path = local_repository + relative_path;
std::string folder = "./";
size_t pos = relative_path.rfind('/');
if (pos != std::string::npos)
folder += std::string(relative_path.begin(), relative_path.begin() + pos);
if (folder[folder.size() - 1] != '/')
folder += "/";
g_log.information() << "Uploading to folder: " << folder << std::endl;
form.add("path", folder);
// inserting the file
FilePartSource *m_file = new FilePartSource(absolute_path);
form.addPart("file", m_file);
inetHelper.setBody(form);
std::stringstream server_reply;
int status;
try {
status = inetHelper.sendRequest(remote_upload, server_reply);
} catch (Kernel::Exception::InternetError &ie) {
status = ie.errorCode();
}
g_log.information() << "ScriptRepository upload status: " << status
<< std::endl;
std::stringstream answer;
{ // remove the status message from the end of the reply, in order not to
// get exception from the read_json parser
std::string server_reply_str;
server_reply_str = server_reply.str();
size_t pos = server_reply_str.rfind("}");
if (pos != std::string::npos)
answer << std::string(server_reply_str.begin(),
server_reply_str.begin() + pos + 1);
else
answer << server_reply_str;
g_log.debug() << "Form Output: " << answer.str() << std::endl;
std::string info;
std::string detail;
std::string published_date;
Json::Value pt;
Json::Reader json_reader;
if (!json_reader.parse(answer, pt)) {
throw ScriptRepoException("Bad answer from the Server");
info = pt.get("message", "").asString();
detail = pt.get("detail", "").asString();
published_date = pt.get("pub_date", "").asString();
std::string cmd = pt.get("shell", "").asString();
if (!cmd.empty())
detail.append("\nFrom Command: ").append(cmd);
if (info == "success") {
g_log.notice() << "ScriptRepository:" << file_path << " uploaded!"
<< std::endl;
// update the file
RepositoryEntry &entry = repo.at(file_path);
Poco::File local(absolute_path);
entry.downloaded_date = DateAndTime(Poco::DateTimeFormatter::format(
local.getLastModified(), timeformat));
// update the pub_date and downloaded_pubdate with the pub_date given by
// the upload.
// this ensures that the status will be correctly defined.
if (!published_date.empty())
entry.pub_date = DateAndTime(published_date);
entry.downloaded_pubdate = entry.pub_date;
entry.status = BOTH_UNCHANGED;
g_log.information() << "ScriptRepository update local json " << std::endl;
updateLocalJson(file_path, entry); /// FIXME: performance!
// add the entry to the repository.json. The
// repository.json should change at the
// remote repository, and we could just download the new one, but
// we can not rely on the server updating it fast enough.
// So add to the file locally to avoid race condition.
RepositoryEntry &remote_entry = repo.at(file_path);
if (!published_date.empty())
remote_entry.pub_date = DateAndTime(published_date);
remote_entry.status = BOTH_UNCHANGED;
g_log.debug() << "ScriptRepository updating repository json "
<< std::endl;
updateRepositoryJson(file_path, remote_entry);
} else
throw ScriptRepoException(info, detail);
} catch (Poco::Exception &ex) {
throw ScriptRepoException(ex.displayText(), ex.className());
}
}
/*
* Adds an entry to .repository.json
* This is necessary when uploading a file to keep .repository.json and
* .local.json in sync, and thus display correct file status in the GUI.
* Requesting an updated .repository.json from the server is not viable
* at such a time as it would create a race condition.
* @param path: relative path of uploaded file
* @param entry: the entry to add to the json file
*/
void ScriptRepositoryImpl::updateRepositoryJson(const std::string &path,
const RepositoryEntry &entry) {
Json::Value repository_json;
std::string(local_repository).append(".repository.json");
repository_json = readJsonFile(filename, "Error reading .repository.json file");
if (!repository_json.isMember(path)) {
// Create Json value for entry
Json::Value entry_json;
entry_json["author"] = entry.author;
entry_json["description"] = entry.description;
entry_json["directory"] = (entry.directory ? "true" : "false");
entry_json["pub_date"] = entry.pub_date.toFormattedString();
// Add Json value for entry to repository Json value
repository_json[path] = entry_json;
g_log.debug() << "Update LOCAL JSON FILE" << std::endl;
#if defined(_WIN32) || defined(_WIN64)
// set the .repository.json and .local.json not hidden to be able to edit it
SetFileAttributes(filename.c_str(), FILE_ATTRIBUTE_NORMAL);
#endif
writeJsonFile(filename, repository_json, "Error writing .repository.json file");
#if defined(_WIN32) || defined(_WIN64)
// set the .repository.json and .local.json hidden
SetFileAttributes(filename.c_str(), FILE_ATTRIBUTE_HIDDEN);
#endif
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* Delete one file from the local and the central ScriptRepository
* It will send in a POST method, with the file path to find the path :
* - author : Will identify the author of the change
* - email: Will identify the email of the author
* - comment: Description of the nature of the file or of the update
*
* It will them send the request to the URL pointed to UploaderWebServer,
*changing the word
* publish to remove. For example:
*
* http://upload.mantidproject.org/scriptrepository/payload/remove
*
* The server will them create a git commit deleting the file. And will reply
*with a json string
* with some usefull information about the success or failure of the attempt to
*delete.
* In failure, it will be converted to an appropriated ScriptRepoException.
*
* Requirements: in order to be allowed to delete files from the central
*repository,
* it is required that the state of the file must be BOTH_UNCHANGED or
*LOCAL_CHANGED.
*
* @param file_path: The path (relative to the repository) or absolute to