Created
June 3, 2013 17:25
-
-
Save boo1ean/5699745 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdlib.h> | |
#include <dirent.h> | |
#include <utime.h> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <sys/sendfile.h> | |
#include <iostream> | |
#include <vector> | |
#include <string> | |
#include <sstream> | |
#include <fstream> | |
#include <cstring> | |
#include <algorithm> | |
using namespace std; | |
#define CURRENT_DIR "." | |
#define TARGET_DIR "./result" | |
#define YES "y" | |
#define REQUIRED_ARGS_NUMBER 1 + 1 | |
#define HR "========================================================\n" | |
// Custom types | |
typedef vector<string> StringVector; | |
class File | |
{ | |
enum | |
{ | |
ACCESS_ERROR = -1, | |
BUFFER_SIZE = 1024 | |
}; | |
vector<string> &split(const string &s, char delim, vector<string> &elems) { | |
stringstream ss(s); | |
string item; | |
while (getline(ss, item, delim)) { | |
elems.push_back(item); | |
} | |
return elems; | |
} | |
vector<string> split(const string &s, char delim) { | |
vector<string> elems; | |
split(s, delim, elems); | |
return elems; | |
} | |
protected: | |
string path; | |
public: | |
enum | |
{ | |
PATH_SEPARATOR = '/' | |
}; | |
class NotExistException : exception | |
{ | |
virtual const char * what() const throw() | |
{ | |
return "Node doesn't exist."; | |
} | |
}; | |
class RemoveException : exception | |
{ | |
virtual const char * what() const throw() | |
{ | |
return "Can't remove file."; | |
} | |
}; | |
class CopyException : exception | |
{ | |
virtual const char * what() const throw() | |
{ | |
return "Can't complete copy operation."; | |
} | |
}; | |
static bool exists(string path) | |
{ | |
return ACCESS_ERROR != access(path.c_str(), F_OK); | |
} | |
static bool isDir(string path) | |
{ | |
struct stat stat = File::stat(path); | |
return S_IFDIR & stat.st_mode; | |
} | |
static struct stat stat(string path) | |
{ | |
struct stat stat; | |
::stat(path.c_str(), &stat); | |
return stat; | |
} | |
File(string path) : path(path) | |
{ | |
// Throw exception if file doesn't exist | |
if (!File::exists(path)) { | |
throw NotExistException(); | |
} | |
} | |
inline string getPath() const { return path; } | |
// Get file stats | |
struct stat stat() const | |
{ | |
return File::stat(path); | |
} | |
// Get filename | |
string name() | |
{ | |
return split(path, PATH_SEPARATOR).back(); | |
} | |
void remove() | |
{ | |
if (::remove(path.c_str()) != 0) { | |
throw RemoveException(); | |
} | |
} | |
void copy(string dest) | |
{ | |
dest += name(); | |
int src_fd; | |
int dest_fd; | |
struct stat src_stat; | |
off_t offset = 0; | |
// Open the input file | |
src_fd = open(getPath().c_str(), O_RDONLY); | |
if (-1 == src_fd) { | |
throw CopyException(); | |
} | |
// Stat the input file to obtain its size | |
fstat(src_fd, &src_stat); | |
//Open the output file for writing, with the same permissions as the source file | |
dest_fd = open(dest.c_str(), O_WRONLY | O_CREAT, src_stat.st_mode); | |
if (-1 == dest_fd) { | |
throw CopyException(); | |
} | |
// Copy the bytes from one file to the other | |
if (src_stat.st_size != sendfile(dest_fd, src_fd, &offset, src_stat.st_size)) { | |
throw CopyException(); | |
} | |
close(src_fd); | |
close(dest_fd); | |
} | |
bool operator==(const File &other) const | |
{ | |
ifstream lFile(path.c_str(), ifstream::in | ifstream::binary); | |
ifstream rFile(other.getPath().c_str(), ifstream::in | ifstream::binary); | |
if(!lFile.is_open() || !rFile.is_open()) { | |
return false; | |
} | |
char *lBuffer = new char[BUFFER_SIZE](); | |
char *rBuffer = new char[BUFFER_SIZE](); | |
do { | |
lFile.read(lBuffer, BUFFER_SIZE); | |
rFile.read(rBuffer, BUFFER_SIZE); | |
if (memcmp(lBuffer, rBuffer, BUFFER_SIZE) != 0) | |
{ | |
delete[] lBuffer; | |
delete[] rBuffer; | |
return false; | |
} | |
} while (lFile.good() || rFile.good()); | |
delete[] lBuffer; | |
delete[] rBuffer; | |
return true; | |
} | |
bool operator<(const File &other) const | |
{ | |
return stat().st_mtime < other.stat().st_mtime; | |
} | |
}; | |
class Dir : File | |
{ | |
public: | |
typedef vector<File> List; | |
class InvalidNodeTypeException : exception | |
{ | |
virtual const char * what() const throw() | |
{ | |
return "Specified object is not a directory."; | |
} | |
}; | |
Dir(string path) : File(path) | |
{ | |
if (!File::isDir(path)) { | |
throw InvalidNodeTypeException(); | |
} | |
} | |
// Get list of filenames | |
List files() | |
{ | |
List listing; | |
struct dirent * de = NULL; | |
DIR * dir = opendir(path.c_str()); | |
while (de = readdir(dir)) | |
{ | |
if (DT_DIR & de->d_type) { | |
continue; | |
} | |
try { | |
listing.push_back(File(path + de->d_name)); | |
} catch (NotExistException e) { | |
cout << "Something went wrong." << endl; | |
exit(EXIT_FAILURE); | |
} | |
} | |
closedir(dir); | |
return listing; | |
} | |
}; | |
void slashify(string &target) | |
{ | |
if (File::PATH_SEPARATOR != target.at(target.size() - 1)) { | |
target += File::PATH_SEPARATOR; | |
} | |
} | |
int main(int argc, char * argv[]) | |
{ | |
string source_dir = CURRENT_DIR; | |
slashify(source_dir); | |
Dir * dir; | |
try { | |
dir = new Dir(source_dir); | |
} catch (File::NotExistException e) { | |
cout << "Directory doesn't exist: \"" << source_dir << "\"" << endl; | |
return EXIT_FAILURE; | |
} catch (Dir::InvalidNodeTypeException e) { | |
cout << "Specified node is not a directory: \"" << source_dir << "\"" << endl; | |
return EXIT_FAILURE; | |
} | |
if (-1 == mkdir(TARGET_DIR, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { | |
cout << "Can't create result directory." << endl; | |
return EXIT_FAILURE; | |
} | |
string target_dir = TARGET_DIR; | |
slashify(target_dir); | |
Dir::List files = dir->files(); | |
Dir::List to_remove; | |
Dir::List to_copy; | |
do { | |
Dir::List bundle; | |
bundle.push_back(files.at(0)); | |
files.erase(files.begin()); | |
if (!files.size()) { | |
break; | |
} | |
int j = 0; | |
do { | |
if (bundle.at(0) == files.at(j)) { | |
bundle.push_back(files.at(j)); | |
files.erase(files.begin() + j); | |
} else { | |
j++; | |
} | |
} while (j < files.size()); | |
if (bundle.size() < 2) { | |
continue; | |
} | |
sort(bundle.begin(), bundle.end()); | |
cout << "Duplicates group:" << endl; | |
for (int i = 0; i < bundle.size(); ++i) { | |
cout << bundle.at(i).name() << endl; | |
} | |
cout << endl; | |
for (int i = 0; i < bundle.size() - 1; ++i) { | |
to_remove.push_back(bundle.at(i)); | |
} | |
to_copy.push_back(bundle.back()); | |
} while (files.size()); | |
if (to_copy.size()) { | |
cout << "List of copied files:" << endl; | |
for (int i = 0; i < to_copy.size(); ++i) { | |
to_copy.at(i).copy(target_dir); | |
cout << to_copy.at(i).name() << endl; | |
} | |
} else { | |
cout << "There are no files to copy." << endl; | |
} | |
cout << endl; | |
// Copy latest files | |
if (to_remove.size()) { | |
cout << "List of files to delete:" << endl; | |
for (int i = 0; i < to_remove.size(); ++i) { | |
cout << to_remove.at(i).name() << endl; | |
} | |
} else { | |
cout << "There are no files to delete." << endl; | |
} | |
// Remove duplicates | |
for (int i = 0; i < to_remove.size(); ++i) { | |
string decision; | |
cout << "Remove \"" << to_remove.at(i).name() << "\"?(y/n): "; | |
cin >> decision; | |
if (YES == decision) { | |
to_remove.at(i).remove(); | |
} else { | |
cout << "Skip: " << to_remove.at(i).getPath() << endl; | |
} | |
} | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment