/****************************************************************************************
* *
* Written by Stanic Igor 2012/2013 *
* This file contains examples of what FileSystemController and supporting classes *
* can do and basic examples how to use it. It is the linking part between console *
* frontend and underlying functionalities provided by FileSystemController. *
* Most of functionalities are implemented as static functions. You need to actually *
* instantiate FileSystemController only for purposes of underlying FileSystemWatcher *
* class. This will automate the cleanup process on D-TOR calling once we don't need *
* file system event notifications any more. *
* *
****************************************************************************************/
#ifndef COMMAND_PROCESSOR_H
#define COMMAND_PROCESSOR_H
#include "BaseDecl.h"
#ifdef WINDOWS
#include "CustomLocale.hpp"
#endif
#include <boost/program_options.hpp>
#include "FileSystemController.h"
using namespace std;
namespace bpo = boost::program_options;
#ifdef WINDOWS
#define TCOUT wcout
class NotifyingClass
{
public:
void process(EventData ek)
{
wstring action;
switch(ek.first)
{
case EventKind::added:
action = L"File Added";
break;
case EventKind::removed:
action = L"File Removed";
break;
case EventKind::modified:
action = L"File Modified";
break;
case EventKind::oldName:
action = L"File Renamed - Old Name";
break;
case EventKind::newName:
action = L"File Renamed - New Name";
break;
case EventKind::watcherSignOut:
action = L"Watcher Signing Out!";
break;
}
wcout <<L"**********************************************************************************" << endl;
wcout << TO_STRING_TYPE("EVENT NOTIFY ON ACTION -> ") << action << L" FOR FILE/FOLDER: " << ek.second << endl;
wcout <<L"**********************************************************************************" << endl;
}
};
#elif defined(LINUX)
#define TCOUT cout
class NotifyingClass
{
public:
void process(EventData ek)
{
string event;
switch(ek.first)
{
case EventKind::fileRead:
event = "Read File Event";
break;
case EventKind::fileModify:
event = "Modify File Event";
break;
case EventKind::attribChange:
event = "Attribute Change Event";
break;
case EventKind::openFile:
event = "Opening File Event";
break;
case EventKind::fileClosed:
event = "Closing File Event";
break;
case EventKind::fileReadClosed:
event = "File Read Closed Event";
break;
case EventKind::movedFrom:
event = "Moved From Event";
break;
case EventKind::movedTo:
event = "Moved To Event";
break;
case EventKind::created:
event = "Created Event";
break;
case EventKind::deleted:
event = "Deleted Event";
break;
case EventKind::watchTargetDeleted:
event = "Target Watch Deleted Event";
break;
case EventKind::watchTargetMoved:
event = "Target Watch Moved Event";
break;
case EventKind::watcherSignOut:
event = "SignOut Event";
break;
}
cout << "****************************************************" << endl;
cout << "Event ocurred -> " << event << " ON FILE/FOLDER: " << ek.second << endl;
cout << "****************************************************" << endl;
}
};
#endif
void Print(const STRING_TYPE& str)
{
TCOUT <<CHRARR_CON("--------------------------------------------------------------------")<< endl;
TCOUT << str << endl;
TCOUT <<CHRARR_CON("--------------------------------------------------------------------")<< endl;
}
inline STRING_TYPE PrintSize(ULLONG size)
{
STRING_TYPE addition = TO_STRING_TYPE(" bytes");
STRING_TYPE result;
if(size > 10*1024 && size < 800 * 1024)
{
addition = TO_STRING_TYPE(" Kb");
result = Helpers::ToString<double,STR_SET>((double)size/1024) + addition;
}
else if(size >= 800 * 1024 && size < 800 * 1024 * 1024)
{
addition = TO_STRING_TYPE(" Mb");
result = Helpers::ToString<double,STR_SET>((double)size/(1024 * 1024)) + addition;
}
else if( size >= 800 * 1024 * 1024)
{
addition = TO_STRING_TYPE(" Gb");
result = Helpers::ToString<double,STR_SET>((double)size/(1024 * 1024 * 1024)) + addition;
}
else
result = Helpers::ToString<ULLONG,STR_SET>(size) + addition;
return result;
}
inline void PrintResults(fInfosPtr results, bool verbose = false)
{
vector<F_INFO>::iterator iter;
if(results == nullptr) TCOUT << CHRARR_CON("NO RESULTS!") << endl;
for(iter = results->begin(); iter != results->end(); ++iter)
{
if(verbose)
TCOUT << CHRARR_CON("-------------------------------------------------") << endl;
TCOUT << (iter->isDirectory ? TO_STRING_TYPE("Directory-> ") + iter->path : TO_STRING_TYPE("File-> ") + iter->path);
if(verbose)
TCOUT << endl;
if(verbose)
{
TCOUT << CHRARR_CON(" Size: ") << (iter->isDirectory ? PrintSize(FileSystemController::GetFileOrFolderSize(iter->path)) :
PrintSize(iter->length)) << endl;
}
else
{
if(!iter->isDirectory)
TCOUT << CHRARR_CON(" Size: ") << PrintSize(iter->length) << endl;
else
TCOUT << endl;
}
if(verbose)
{
TCOUT
#ifdef LINUX
<< (iter->isSymLink? TO_STRING_TYPE("** Symbolic Link ** "): TO_STRING_TYPE("** Regular File/Folder **"))
<< std::endl
<< (verbose && iter->isSymLink? TO_STRING_TYPE(" Underlying Link: ") + iter->sym_Link_Path : EMPTYSTR)
#endif
<< std::endl
<< (verbose ? (TO_STRING_TYPE(" Last Written on:") + TO_STRING_TYPE(boost::posix_time::to_simple_string(iter->time))) : EMPTYSTR) << endl;
#ifdef WINDOWS
if(iter->isArchive())
TCOUT << TO_STRING_TYPE("Archive") << TO_STRING_TYPE(",");
if(iter->isCompressed())
TCOUT << TO_STRING_TYPE("Compressed") << TO_STRING_TYPE(",");
if(iter->isEncrypted())
TCOUT << TO_STRING_TYPE("Encrypted") << TO_STRING_TYPE(",");
if(iter->isHidden())
TCOUT << TO_STRING_TYPE("Hidden") << TO_STRING_TYPE(",");
if(iter->isOffline())
TCOUT << TO_STRING_TYPE("Offline") << TO_STRING_TYPE(",");
if(iter->isReadOnly())
TCOUT << TO_STRING_TYPE("ReadOnly") << TO_STRING_TYPE(",");
if(iter->isSystem())
TCOUT << TO_STRING_TYPE("System") << TO_STRING_TYPE(",");
if(iter->isTemporary())
TCOUT << TO_STRING_TYPE("Temporary") << endl;
TCOUT << endl;// << CHRARR_CON("***********************") << endl;
#elif defined(LINUX)
//TODO: add if needed
//if(iter->file_statistics.st_mode & S_ISVTX )
//TCOUT << CHRARR_CON("Has sticky bit") << endl;
if(iter->isHidden())
TCOUT << "Hidden" << endl;
if(iter->permission_denied())
TCOUT << "Permission Denied" << endl;
#endif
}
}
Print(TO_STRING_TYPE("Number of entries: ") + Helpers::ToString<vector<F_INFO>::size_type, STR_SET>(results->size()));
}
#ifdef LINUX
inline void PrintTrashResults(fInfosPtr results)
{
int count = 0;
for(auto iter = results->begin(); iter != results->end(); ++iter)
{
TCOUT << CHRARR_CON("-----------------------") << endl;
TCOUT << ++count << ". " << (iter->isDirectory ? TO_STRING_TYPE("Directory-> ") + iter->path : TO_STRING_TYPE("File-> ") + iter->path)
/**/
<< (iter->isSymLink? TO_STRING_TYPE(" ** Symbolic Link ** "): EMPTYSTR)
<< TO_STRING_TYPE(" Last Written on: ") << TO_STRING_TYPE(boost::posix_time::to_simple_string(iter->time)) << endl;
TCOUT << CHRARR_CON(" Size: ") << PrintSize(FileSystemController::GetFileOrFolderSize(iter->orgTrashPath)) << endl;
}
Print(TO_STRING_TYPE("Number of results: ") + Helpers::ToString<vector<F_INFO>::size_type, STR_SET>(results->size()));
}
#endif
inline void PrintErrors(const OpResults& res)
{
if(res.hasErrors())
{
OpResults errColl = res.errors();
for(const OperationResult& r:errColl)
TCOUT << TO_STRING_TYPE("Error-> ") << TO_STRING_TYPE("Error Message: ") << r.errorMsg << endl;
}
}
void WaitUntil(const function<bool(const std::string&)>& pred)
{
//wcin.ignore() doesn't seem to work with current version of MINGW 32
std::string opt;
while(!pred(opt))
{
cin >> opt;
cin.ignore(INT_MAX, '\n');
}
}
vector<STRING_TYPE> separatePaths(const std::string& arg)
{
auto vTemp = Helpers::splitString(arg, ';');
#ifdef WINDOWS
vector<STRING_TYPE> toRet(vTemp.size());
transform(vTemp.begin(), vTemp.end(), toRet.begin(),[](const std::string& s)
{
return TO_STRING_TYPE(s);
});
return toRet;
#elif defined(LINUX)
return vTemp;
#endif
}
OpResults exec_commands(bpo::variables_map& vmap)
{
OpResults toReturn;
#ifdef WINDOWS
//This is some custom implementation which will at least
//show unicode char representations like sSzZcC...etc.
//It's terrible but couldn't find anything else that works...
custom_locale::init();
#endif
//test_wildcard BEGIN
if(vmap.count("test_wildcard"))
{
STRING_TYPE path(TO_STRING_TYPE(vmap["test_wildcard"].as<string>()));
//Actual resolving file/foledr paths represented by '*' wildcard combinations
//Supported combinations are *.* | x*.* | *.y | x*.y | *x.y | *x*.y | where
// 'x' is a part of file/folder name and 'y' is file extension defined
// Only *.* and x*.* apply to both files and folders, other combinations
//can be used to express multiple files only.
vector<STRING_TYPE> resolved_paths(FileSystemController::TestWCard(path));
Print(TO_STRING_TYPE("Resolving entered path with \'*\' wildcard(s):"));
for(auto& p : resolved_paths)
TCOUT << p << endl;
TCOUT << endl;
return toReturn;
}
//test_wildcard END
//FileSystemWatcher BEGIN
if(vmap.count("watch_location") || vmap.count("watch_here"))
{
vector<STRING_TYPE> locations;
if(vmap.count("watch_here"))
locations.push_back(CHRARR_CON(""));
else
{
locations = separatePaths(vmap["watch_location"].as<string>());
if(locations.empty())//OperationResult(bool result, T1&& source, T2&& errorMsg)
{
toReturn.AddResult(OperationResult(false, CHRARR_CON("watch_location"),
CHRARR_CON("you didn't provide any locations to watch")));
return toReturn;
}
for(const auto& loc : locations)
{
if(!boost::filesystem::exists(loc))
{
toReturn.AddResult(OperationResult(false, CHRARR_CON("watch_location"), CHRARR_CON("invalid location provided")));
return toReturn;
}
}
}
//Set the kind of changes you want to watch in specified location. BasicChanges will handle most of expected stuff
//like file/folder deletion, renaming, creation and such. If you want to be able to watch changes like attribute/permission
//changes on Windows or file openread/openwrite on Linux use other enum option
ChangeKind changesWatch = ((bool)vmap.count("watch_full") ? ChangeKind::FullChanges : ChangeKind::BasicChanges);
//This functor will be used as an event handler.
//Please note that you will probably need to implement some kind
//of Consumer implementation to work properly in more serious
//envirnment where events need to be processed sequentially.
std::unique_ptr<NotifyingClass> notificator(new NotifyingClass);
//Container for storing multiple instances of FileSystemController.
//Each instance can watch only one file location at the time
vector<unique_ptr<FileSystemController> > controllers;
for(const auto& loc : locations)
{
try
{
//initialize instance of FileSystemController with path to location we want to watch
//and changes we want to pay attention for.
unique_ptr<FileSystemController> cont(new FileSystemController(loc, changesWatch));
//Get internal instance of FileSystemWatcher
FileSystemWatcher* fsw = cont->getFSWatcher();
//Register event handler for handling filesystem notifications
fsw->RegisterListener(notificator.get(), &NotifyingClass::process);
Print(TO_STRING_TYPE("Starting Location Watching on path: ") + loc);
//If there are errors which occured during initialization of FileSystemWatcher on
//location specified, get them.
if(cont->getFSWatcherErrors().hasErrors())
{
toReturn += cont->getFSWatcherErrors();
}
controllers.push_back(std::move(cont));
}
catch(...)
{
//Add results to container dedicated to handle operation outcomes
toReturn.AddResult(OperationResult(false, TO_STRING_TYPE("Initialization of FileSytemController"),
TO_STRING_TYPE("Undefined error")));
}
}
function<bool(const std::string&)> func = [&controllers, &toReturn](const std::string& str)
{
std::string instr(str);
if(!str.empty())
{
instr = Helpers::toLower(instr);
if(instr == "q" || instr == "quit")
{
for(auto& cont : controllers)
{
toReturn += cont.get()->getFSWatcherErrors();
}
//We don't actually need to do anything to stop watching file system location
//over FileSystemWatcher instance. All we need is make it go out of scope
//and that its D-TOR is called which will do appropriate cleanup
return true;
}
else if(instr == "p")
{
for(auto& cont : controllers)
{
//Pause WATCHING on specific location
cont.get()->getFSWatcher()->setPaused(!cont.get()->getFSWatcher()->getPaused());
}
}
else
Print(TO_STRING_TYPE("Don't know what is: ") + TO_STRING_TYPE(instr) + CHRARR_CON(" Available options: q or quit and p for pause notification start/stop"));
}
return false;
};
if(!toReturn.hasErrors())
WaitUntil(func);
return toReturn;
}
//FileSystemWatcher END
//list_trash Implementation BEGIN
if(vmap.count("list_trash"))
{
#ifdef WINDOWS
//List recovery paths of items in Recycle Bin in case of Windows OS
vector<std::wstring> recoveryPaths(FileSystemController::ListRecycleBin());
int count = 0;
Print(CHRARR_CON("Trash/Recycle Bin content:"));
for_each(recoveryPaths.begin(), recoveryPaths.end(), [&count](const wstring& path)
{
TCOUT << ++count << CHRARR_CON(".\t");
TCOUT << TO_STRING_TYPE("Recycle Bin Recovery path: ") << path << endl;
});
#elif defined(LINUX)
//List $Trash contents on linux. In case of Linux we have greater control over what
//info we can get since the files are stored in usual way on hidden location.
unique_ptr<vector<F_INFO> > recoveryPaths(FileSystemController::ListTrash(&toReturn));
PrintTrashResults(recoveryPaths.get());
#endif
return toReturn;
}
//list_trash Implementation END
//list items on location BEGIN
if(vmap.count("list") || vmap.count("l"))
{
STRING_TYPE listPath(TO_STRING_TYPE(vmap["list"].as<string>()));
if(!listPath.empty())//this means list current location
{
if(!boost::filesystem::exists(listPath))
{
toReturn.AddResult(OperationResult(false, listPath, CHRARR_CON("Location provided doesn't exist")));
return toReturn;
}
}
//Define if hidden/system file system items should be present in the collection also
bool allEntries = false;
if(vmap.count("all"))
allEntries = true;
//Define how retrieved items should be sorted
//over _SortInfo enum value
_SortInfo sorting = _SortInfo::dirFirst_asc;
if(vmap.count("desc"))
sorting = _SortInfo::dirFirst_desc;
//Get actual file/folder infos for specific location
unique_ptr<vector<F_INFO> > files(FileSystemController::GetEntries(listPath, sorting, allEntries));
PrintResults(files.get(), vmap.count("v"));
return toReturn;
}
//list items on location END
//make_path BEGIN
if(vmap.count("make_path"))
{
vector<STRING_TYPE> valsMP = separatePaths(vmap["make_path"].as<string>());
//Build path for each entry separated by ';'
//If path already exists function won't do any changes.
//Otherwise, it will build the missing parts of the path
for(auto& path : valsMP)
toReturn += FileSystemController::BuildPath(path);
return toReturn;
}
//make_path END
//copy/move option BEGIN
if(vmap.count("copy") || vmap.count("move"))
{
string from;
STRING_TYPE to;
if(vmap.count("from"))
from = vmap["from"].as<string>();
if(vmap.count("to"))
to = TO_STRING_TYPE((vmap["to"].as<string>()));
string cpmvOpt;
if(vmap.count("copy"))
cpmvOpt = "copy";
else
cpmvOpt = "move";
bool overwrite = false;
bool merge = false;
if(vmap.count("overwrite"))
overwrite = true;
if(vmap.count("merge"))
merge = true;
vector<STRING_TYPE> valsCM = separatePaths(from);
if(valsCM.empty() || to.empty())
{
toReturn.AddResult(OperationResult(false, CHRARR_CON("from/to option"), CHRARR_CON("path isn't defined")));
return toReturn;
}
//Copy and Move calls. First two args define source and destination.
//Overwrite will overwite everything exisiting with the same name on destination location.
//Merge will ensure that all item missing on destination location are added from source, and
//all already present will stay unchanged after operation is done.
for(auto& path : valsCM)
{
if(cpmvOpt == "copy")
toReturn += FileSystemController::Copy(path, to, overwrite, merge);
else
toReturn += FileSystemController::Move(path, to, overwrite);//move doesn't allow merging
}
return toReturn;
}
//copy/move option END
//Delete option BEGIN
if(vmap.count("pdel"))
{
vector<STRING_TYPE> valsPDEL = separatePaths(vmap["pdel"].as<string>());
for(auto& path : valsPDEL)
{
//Permanent delete item on defined destination
toReturn += FileSystemController::Delete(path);
}
return toReturn;
}
//Delete option END
#ifdef WINDOWS
//Move to R.Bin BEGIN
if(vmap.count("to_trash"))
{
vector<STRING_TYPE> paths = separatePaths(vmap["to_trash"].as<string>());
if(paths.empty())
{
toReturn.AddResult(OperationResult(false, TO_STRING_TYPE("to_trash option"),
TO_STRING_TYPE("no valid paths")));
return toReturn;
}
//Delete each location to Recycle Bin on Windows OS
for(auto& p : paths)
toReturn += FileSystemController::MoveToRecycleBin(p);
return toReturn;
}
//Move to R.Bin END
#elif defined(LINUX)
if(vmap.count("to_trash"))
{
vector<STRING_TYPE> paths = separatePaths(vmap["to_trash"].as<string>());
if(paths.empty())
{
toReturn.AddResult(OperationResult(false, TO_STRING_TYPE("to_trash option"),
TO_STRING_TYPE("no valid paths")));
return toReturn;
}
//Delete each location to Trash on Linux OS
for(auto& p : paths)
toReturn += FileSystemController::MoveToTrash(p);
return toReturn;
}
if(vmap.count("size_trash"))
{
//Get summary of Trash content size on Linux OS
auto trSize = FileSystemController::getTrashSize();
Print(TO_STRING_TYPE("Current Trash size is:") + PrintSize(trSize));
return toReturn;
}
if(vmap.count("from_trash"))
{
string restorationPath;
string alternatePath;
int restorationPathNumber = vmap["from_trash"].as<int>();
if(restorationPathNumber < 1)
{
toReturn.AddResult(OperationResult(false, "from_trash option", "value is invalid"));
return toReturn;
}
if(vmap.count("restore_path"))
{
alternatePath = vmap["restore_path"].as<string>();
if(alternatePath.empty())
{
toReturn.AddResult(OperationResult(false, "restore_path option", "value is invalid"));
return toReturn;
}
}
unique_ptr<vector<F_INFO> > recoveryPaths(FileSystemController::ListTrash(&toReturn));
if(toReturn.hasErrors())
return toReturn;
if(restorationPathNumber > (int)recoveryPaths->size())
{
toReturn.AddResult(OperationResult(false, "from_trash option", "value is invalid"));
return toReturn;
}
F_INFO restorer = recoveryPaths->operator[](restorationPathNumber - 1);
//Restore item from Trash on Linux OS. First arg is actual location of deleted file in Trash, overwrite bool arg defines if
//item can be restored over presently existing one on recovery location and alternate path can be optionally used to define some
//other more convenient path for item to be restored on.
toReturn += FileSystemController::RestoreFile(restorer.orgTrashPath, (bool)vmap.count("overwrite"), (alternatePath.empty() ? EMPTYSTR : alternatePath));
return toReturn;
}
//undelete from trash;
#endif
//Rename BEGIN
if(vmap.count("rn"))
{
STRING_TYPE from;
STRING_TYPE to;
if(vmap.count("from"))
from = TO_STRING_TYPE(vmap["from"].as<string>());
if(vmap.count("to"))
to = TO_STRING_TYPE((vmap["to"].as<string>()));
if(from.empty() || to.empty())
{
toReturn.AddResult(OperationResult(false, TO_STRING_TYPE("to/from options"), TO_STRING_TYPE("source or destination are empty")));
return toReturn;
}
//Reneame file system file/folder. Second arg accepts ONLY NAME, not the full path as in case of other functions
toReturn += FileSystemController::Rename(from, to);
return toReturn;
}
//Rename END
//SEARCH BEGIN
if(vmap.count("search_loc"))
{
STRING_TYPE searchPath(TO_STRING_TYPE(vmap["search_loc"].as<string>()));
if(!vmap.count("search_string"))
{
toReturn.AddResult(OperationResult(false, TO_STRING_TYPE("search_string option"),
TO_STRING_TYPE("you have to define at least search_loc and search_string options")));
return toReturn;
}
STRING_TYPE searchFor(TO_STRING_TYPE(vmap["search_string"].as<string>()));
if(searchFor.empty())
{
toReturn.AddResult(OperationResult(false, TO_STRING_TYPE("search_string option"),
TO_STRING_TYPE("you have to define at least search_loc and search_string options")));
return toReturn;
}
//Create search criteria to filter file system items. You can define string to search for, if that string is part of name or
//the whole path, if search is case-sensitive(by default it isn't), if you search for exact match to file name and
//you can shorten search time in case you expect only couple of matches, spacially in case when start search location is
//something as '/' or 'C:\'
SearchCriteria sc(searchFor, !(bool)vmap.count("search_path"), (bool)vmap.count("case_sensitive"),
(bool)vmap.count("exact_match"), (int)vmap["result_count"].as<UINT>());
Print(TO_STRING_TYPE("Searching for requested results:"));
//Get file infos as a result of search operation with specific criteria
//You can define search criteria as SearchCriteria and SearchFunction(contains function<> object with defined predicate)
//Also, there is another overload of FindFile() function which will create SearchCriteria object for you.
unique_ptr<vector<F_INFO> > finfos(FileSystemController::FindFile(searchPath, (SearchWrapperBase*)&sc, (bool)vmap.count("all")));
PrintResults(finfos.get(), vmap.count("v"));
Print(TO_STRING_TYPE("Number of matches: ") + Helpers::ToString<int, STR_SET>(finfos->size()));
return toReturn;
}
//SEARCH END
return toReturn;
}
#endif