Skip to content

Commit

Permalink
Implementation of removing files ScriptRepository
Browse files Browse the repository at this point in the history
Implements the remove and remove_local methods.

re #7031
  • Loading branch information
gesnerpassos committed May 28, 2013
1 parent f35c0b4 commit a6ed4ab
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ namespace API{
void upload(const std::string & file_path, const std::string & comment,
const std::string & author,
const std::string & email);
// remove file from the central repository and from local folder
void remove(const std::string & file_path, const std::string & comment,
const std::string & author,
const std::string & email);
// remove file from local folder
void remove_local(const std::string & file_path);

/* Return true if there is a local repository installed*/
bool isValid(void);
Expand All @@ -117,8 +123,12 @@ namespace API{




virtual void doDownloadFile(const std::string & url_file, const std::string & local_file_path = "");
// convenient method to allow to perform the unit tests on remove files.
virtual std::string doDeleteRemoteFile(const std::string & url, const std::string & file_path,
const std::string & author, const std::string & email,
const std::string & comment);
protected:
void parseCentralRepository(Repository & repo);

Expand All @@ -127,7 +137,12 @@ namespace API{
void parseDownloadedEntries(Repository & repo);

void ensureValidRepository();

// auxiliary method that implements the removal of files.
void impl_remove_file(const std::string & file_path,
bool only_local = true,
const std::string & comment = std::string(),
const std::string & author = std::string(),
const std::string & email = std::string());

bool isEntryValid(const std::string & path);

Expand Down
264 changes: 264 additions & 0 deletions Code/Mantid/Framework/ScriptRepository/src/ScriptRepositoryImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,270 @@ namespace API
}
}

/**
* 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 identify the file to remove
* @param comment: justification to remove this file (will be used as git commit message)
* @param author: identification of the requester for deleting wich must be the author of the file as well
* @param email: email of the requester
*
* @exception ScriptRepoException justifying the reason to failure.
*/
void ScriptRepositoryImpl::remove(const std::string & file_path,
const std::string & comment,
const std::string & author,
const std::string & email){
impl_remove_file(file_path, false, comment, author, email);
}

/** Remove the file from the local folder and update the internal variables to correct
* display the status of the file.
*
* @path file_path: The path (relative to the repository) or absolute to identify the file to remove
*/
void ScriptRepositoryImpl::remove_local(const std::string & file_path){
impl_remove_file(file_path);
}

/** method that effectivelly remove the entries locally and remotelly. It may be called with just
* one parameter, for removing locally
* @code
* impl_remove_file(<local_file_path>); // remove locally
* @endcode
*
* Or, it requires, all the parameters, for removing from the central repository as well.
*
* It is called inside ::remove and ::remove_local
*
* @param file_path: identify the file
* @param only_local: remove the file only from local folder
* @param comment: git commit message
* @param author: identify the requester
* @param email: the email of the author (for the git commit)
*
* Only local files can be removed
*/
void ScriptRepositoryImpl::impl_remove_file(const std::string & file_path,
bool only_local,
const std::string & comment,
const std::string & author,
const std::string & email)

{
std::string relative_path = convertPath(file_path);
const char * not_allowed_exc = "You are not allowed to remove files from the repository that you have not installed and you are not the owner";

// get the status, because only local files can be removed
SCRIPTSTATUS status = fileStatus(relative_path);
if (status == REMOTE_ONLY)
throw ScriptRepoException(not_allowed_exc);


g_log.information() << "ScriptRepository deleting " << file_path << " ..." << std::endl;
if (!only_local){
// request to remove the file from the central repository


// There are more restriction when trying to remove from central repository
// only local_changed and both_changed are acceptable for removing
if (!(status == BOTH_UNCHANGED || status == LOCAL_CHANGED))
throw ScriptRepoException(not_allowed_exc);

// prepare the request, and call doDeleteRemoteFile to request the server to remove the file
std::string remote_delete = remote_upload;
boost::replace_all(remote_delete, "publish", "remove");
std::stringstream answer;
answer << doDeleteRemoteFile(remote_delete, file_path, author, email, comment);
g_log.debug() << "Answer from doDelete: " << answer.str() << std::endl;


// analyze the answer from the server, to see if the file was removed or not.
std::string info;
std::string detail;
std::string published_date;
ptree pt;
try{
read_json(answer, pt);
info = pt.get<std::string>("message","");
detail = pt.get<std::string>("detail","");
std::string cmd = pt.get<std::string>("shell","");
if (!cmd.empty())
detail.append("\nFrom Command: ").append(cmd);

}catch (boost::property_tree::json_parser_error & ex){
// this should not occurr in production. The answer from the
// server should always be a valid json file
g_log.debug() << "Bad answer: " << ex.what() << std::endl;
throw ScriptRepoException("Bad answer from the Server",
ex.what());
}

g_log.debug() << "Checking if success info=" << info << std::endl;
// check if the server removed the file from the central repository
if (info!= "success")
throw ScriptRepoException(info, detail); // no


g_log.notice() << "ScriptRepository " << file_path << " removed from central repository"<< std::endl;

// delete the entry from the repository.json. In reality, the repository.json should change at the
// remote repository, and we could only download the new one, but, practically, at the server, it will
// take sometime to be really removed, so, for practical reasons, this is dealt with locally.
//
{
ptree pt;
std::string filename = std::string(local_repository).append(".repository.json");
try{
read_json(filename,pt);
pt.erase(relative_path);// remove the entry
#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
write_json(filename,pt);
#if defined(_WIN32) || defined(_WIN64)
//set the .repository.json and .local.json hidden
SetFileAttributes( filename.c_str(), FILE_ATTRIBUTE_HIDDEN);
#endif
}catch (boost::property_tree::json_parser_error & ex){
std::stringstream ss;
ss << "corrupted central copy of database : " << filename;

g_log.error() << "ScriptRepository: " << ss.str()
<< "\nDetails: deleting entries - json_parser_error: " << ex.what() << std::endl;
throw ScriptRepoException(ss.str(), ex.what());
}
}

// update the repository list variable
// now, it is local_only and it is not inside remote.
// this is necessary for the strange case, where removing locally may fail.
RepositoryEntry & entry = repo.at(relative_path);
entry.status = LOCAL_ONLY;
entry.remote = false;

}// file removed on central repository

//delete the file
// get the absolute path of this file
try{
// now, remove file locally.
std::string absolute_path = local_repository + relative_path;
Poco::File local(absolute_path);
local.remove();
}catch(Poco::Exception & ex){
throw ScriptRepoException("You do not have right to remove this file in your computer",
ex.displayText());
}

// now that we have remove the local file and from the repository.json,
// the parseDownloadedEntries is able to remove it from the local.json
// delete the entry from the local.json
parseDownloadedEntries(repo);


RepositoryEntry & entry = repo.at(relative_path);
if (entry.remote)
entry.status = REMOTE_ONLY; // it was removed only locally
else
// this means that this value was removed from the central repository
// and from the local repository. So, it can be removed from the
// cached variable
repo.erase(relative_path);

}

/** Implements the request to the server to delete one file. It is created as a virtual protected member
* to allow creating unittest mocking the dependency on the internet connection. This method requires
* internet connection.
*
* @param url: url from the server that serves the request of removing entries
* @param file_path: relative path to the file inside the repository
* @param author: requester
* @param email: email from author
* @param comment: to be converted in git commit
* @return The answer from the server (json string)
*
* The server requires that the path, author, email and comment be given in order to create the commit
* for the git repository. Besides, it will ensure that the author and email are the same to the author
* and email for the last commit, in order not to allow deleting files that others are owner.
*
*/
std::string ScriptRepositoryImpl::doDeleteRemoteFile(const std::string & url, const std::string & file_path,
const std::string & author, const std::string & email,
const std::string & comment){

using namespace Poco::Net;
std::stringstream answer;
try{
// create the poco httprequest object
Poco::URI uri(url);
std::string path(uri.getPathAndQuery());
HTTPClientSession session(uri.getHost(), uri.getPort());
HTTPRequest req(HTTPRequest::HTTP_POST, path,
HTTPMessage::HTTP_1_0);
g_log.debug() << "Receive request to delete file " << file_path << " using " << url << std::endl;


// fill up the form required from the server to delete one file, with the fields
// path, author, comment, email
HTMLForm form;
form.add("author",author);
form.add("mail", email);
form.add("comment",comment);
form.add("file_n",file_path);

// send the request to the server
form.prepareSubmit(req);
std::ostream& ostr = session.sendRequest(req);
form.write(ostr);

// get the answer from the server
HTTPResponse response;
std::istream & rs = session.receiveResponse(response);

g_log.debug() << "ScriptRepository delete status: "
<< response.getStatus() << " " << response.getReason() << std::endl;

{
// get the answer from the server
std::stringstream server_reply;
std::string server_reply_str;
Poco::StreamCopier::copyStream(rs, server_reply);
server_reply_str= server_reply.str();
// remove the status message from the end of the reply,
// in order not to get exception from the read_json parser
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;
}catch(Poco::Exception & ex){
throw ScriptRepoException(ex.displayText(), ex.className());
}
return answer.str();
}


/** The ScriptRepositoryImpl is set to be valid when the local repository path
points to a valid folder that has also the .repository.json and .local.json files.
Expand Down

0 comments on commit a6ed4ab

Please sign in to comment.