云备份
- 云备份
- 概述
- 框架
- 功能演示
- 服务端
- 客户端
- 公共模块
- 文件操作模块
- 目录操作模块
- 服务端模块
- 功能划分
- 功能细分模块
- 数据管理
- 热点管理
- 客户端模块
- 功能划分
- 功能细分模块
- 数据管理
- 目录检查
- 文件备份
云备份
概述
自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。并且能够随时通过浏览器进行查看并且下载,其中下载过程支持断点续传功能,而服务器也会对上传文件进行热点管理,将非热点文件进行压缩存储,节省磁盘空间。
框架
实现客户端和服务端双端程序,合作实现总体的自动云备份功能
本项目有三大模块:
- 客户端模块
- 服务端模块
- 公共模块
除了自己实现的模块之外,还引入了三方库作为辅助:
- Jsoncpp进行序列化辅助
- Httplib进行网络传输辅助
- c++17–filestream进行目录检测辅助
功能演示
服务端
-
若客户端有文件上传,则会自动下载到服务端的backupDir目录下
-
若此文件超过30s时间没有被获取则会自动压缩到packDir目录下
-
从浏览器访问备份呢列表
-
点击文件名下载文件:
当服务端接收到下载命令时,若此文件已在压缩目录下,则先将其解压到备份目录中,再将内容发送给客户端
-
若此时有用户访问了服务端并下载了某个文件,下载一半时,服务端异常关闭导致用户下载中断,服务端恢复后,用户能继续正常的从上次中断的地方继续下载, 实现断点续传
客户端
-
找到backup目录,若没有时则创建(可以考虑写成读取配置文件)
第一次载入时,若没有此目录会自动创建
-
不断轮询检测目录backup,一旦有文件新增或更新,就会发送到远端,实现云备份
-
注意,不一定所有更新都会发送到远端,若有一份很大的文件正在拷贝进backup目录,相当于这份文件在实时更新,但实际是此文件还没有拷贝完毕,不能发送到远端,因此客户端的策略是对某个更新的文件,若此文件上一次更新时间与当前时间相比超过了3s钟才进行云备份
公共模块
公共模块是完成一系列对文件或者目录上的操作,本项目中细分为文件操作模块和目录操作模块,以及日志等简单的公共模块
文件操作模块
namespace cloud
{
//example: you can nest your own log system
void LOG(const char* str)
{
printf("[%s |%d]: %s\n", __FILE__, __LINE__, str);
}
//for operation
namespace fs = std::experimental::filesystem;
class FileUtil
{
private:
std::string _fileName;
public:
FileUtil(const std::string& fileName) :_fileName(fileName) {}
std::string FileName()
{
return fs::path(_fileName).filename().string();
}
time_t LastMTime()
{
struct stat st;
if (stat(_fileName.c_str(), &st) < 0)
{
LOG("file error");
return -1;
}
return st.st_mtime;
}
size_t FileSize() noexcept
{
struct stat st;
if (stat(_fileName.c_str(), &st) < 0)
{
LOG("get file size error");
return 0;
}
return st.st_size;
}
bool GetContentByPosLen(std::string* body, size_t pos, size_t len)
{
size_t fsize = FileSize();
if (pos + len > fsize)
{
LOG("file len error");
return false;
}
std::ifstream ifs;
ifs.open(_fileName, std::ios::binary);
if (!ifs.is_open())
{
LOG("open failed");
return false;
}
ifs.seekg(pos, std::ios::beg);
body->resize(len);
ifs.read(&(*body)[0], len);
if (!ifs.good())
{
LOG("read failed");
ifs.close();
return false;
}
ifs.close();
return true;
}
bool SetContent(const std::string& body)
{
std::ofstream ofs;
ofs.open(_fileName, std::ios::binary);
if (!ofs.is_open())
{
LOG("open file error");
return false;
}
ofs.write(&body[0], body.size());
if (!ofs.good())
{
LOG("write error");
ofs.close();
return false;
}
ofs.close();
return true;
}
};// end FileUtil
}// end namespace
目录操作模块
class DirUtil
{
private:
std::string _dirPath;
public:
explicit DirUtil(const std::string& dirName) :_dirPath(dirName) {}
bool Exists()
{
return fs::exists(_dirPath);
}
bool ScanDirectory(std::vector<std::string>* fileArray)
{
if (!Exists())
{
return fs::create_directories(_dirPath);
}
for (auto& file : fs::directory_iterator(_dirPath))
{
//to do, directory continue temporarily
if (fs::is_directory(file))
continue;
fileArray->push_back(fs::path(file).relative_path().string());
}
return true;
}
};//end DirUtil
服务端模块
功能划分
-
针对客户端上传的文件进行备份存储
-
能够对文件进行热点文件管理,对非热点文件进行压缩存储,节省磁盘空间。
-
支持客户端浏览器查看访问文件列表。
-
支持客户端浏览器下载文件,并且下载支持断点续传。
-
备份信息持久化存储和状态恢复
功能细分模块
数据管理
数据管理模块需要完成配置文件的加载以及数据的管理维护工作,因此大部分设计都集中在这一块,简要概括以下几点:
-
配置文件的加载单独划分为一个模块,并设计为单例模式,由数据管理者启动时加载并维护
配置文件需要有以下信息,做到服务端功能灵活使用
- 热点判断时间
- 服务器监听窗口
- 下载的url前缀路径
- 压缩包后缀名称
- 备份文件存放目录
- 压缩包存放目录
- 服务器ip地址
- 数据信息持久化存储文件带路径名称
Config类:
-
namespace cloud { #define CONFIG_FILE "./cloud.conf" class Config{ private: Config(){ ReadConfigFile(); } static Config *_instance; static std::mutex _mutex; private: int _hot_time; int _server_port; std::string _server_ip; std::string _download_prefix; std::string _packfile_suffix; std::string _pack_dir; std::string _back_dir; std::string _backup_file; bool ReadConfigFile() { FileUtil fu(CONFIG_FILE); std::string body; if(fu.GetContent(&body) == false){ std::cout << "load config file failed!\n"; return false; } Json::Value root; if (JsonUtil::UnSerialize(body, &root) == false){ std::cout << "parse config file failed!\n"; return false; } _hot_time = root["hot_time"].asInt(); _server_port = root["server_port"].asInt(); _server_ip = root["server_ip"].asString(); _download_prefix = root["download_prefix"].asString(); _packfile_suffix = root["packfile_suffix"].asString(); _pack_dir = root["pack_dir"].asString(); _back_dir = root["back_dir"].asString(); _backup_file = root["backup_file"].asString(); return true; } public: static Config *GetInstance() { if (_instance == NULL){ _mutex.lock(); if (_instance == NULL) { _instance = new Config(); } _mutex.unlock(); } return _instance; } int GetHotTime() { return _hot_time; } int GetServerPort() { return _server_port; } std::string GetServerIp() { return _server_ip; } std::string GetDownloadPrefix() { return _download_prefix; } std::string GetPackFileSuffix() { return _packfile_suffix; } std::string GetPackDir() { return _pack_dir; } std::string GetBackDir() { return _back_dir; } std::string GetBackupFile() { return _backup_file; } }; Config *Config::_instance = NULL; std::mutex Config::_mutex; } #endif
易错点:类内静态成员变量需要在类外初始化
-
Jsoncpp
服务器启动时,需要根据配置文件中的备份文件将上一次退出时服务器的备份信息状态加载进本次服务的启动中恢复状态,因此需要将备份数据的信息管理起来,但由于服务端对数据需要的管理较多,若单纯的使用字符串分割符切割存储肯定是不行的,因此使用Json序列化的方式,将数据管理信息序列化后再存储进备份信息文件中,启动服务器时,根据配置文件找到备份信息文件并通过反序列化加载数据管理信息模块进行状态恢复。
配置文件信息也属于数据管理模块的范畴,只不过重新封装一个解耦和,也是通过Json对配置文件信息进行反序列化,而配置文件需要用特定的格式完成编写以便序列化加载配置信息
使用Json
-
使用命令安装,环境:CentOS Linux release 7.9.2009 (Core)
sudo yum install epel-release sudo yum install jsoncpp-devel
安装完后,在系统头文件路径下就会有json需要使用的头文件(自动放的)
-
使用时,只需要包含此头文件的系统路径即可
//example #include <iostream> #include <string> #include <memory> #include <fstream> #include <jsoncpp/json/value.h> #include <jsoncpp/json/json.h> void TestJsonCpp() { std::string str = R"({"name":"nicky", "age":18, "score":[50,80,90]})"; Json::Value root; Json::CharReaderBuilder builder; std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); std::string err; reader->parse(str.c_str(), str.c_str() + str.size(), &root, &err); std::cout << root["name"].asCString() << std::endl; std::cout << root["age"].asInt() << std::endl; int sz = root["score"].size(); for (int i = 0; i < sz; ++i) { std::cout << root["score"][i].asFloat() << std::endl; } } int main() { TestJsonCpp(); return 0; }
-
注意编译时需要链接上jsoncpp的静态库:
g++ xxx.cpp -o xxx -ljsoncpp
JsonUtil类
-
class JsonUtil{ public: static bool Serialize(const Json::Value &root, std::string *str){ Json::StreamWriterBuilder swb; std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter()); std::stringstream ss; if (sw->write(root, &ss) != 0) { std::cout << "json write failed!\n"; return false; } *str = ss.str(); return true; } static bool UnSerialize(const std::string &str, Json::Value *root){ Json::CharReaderBuilder crb; std::unique_ptr<Json::CharReader> cr(crb.newCharReader()); std::string err; bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err); if (ret == false) { std::cout << "parse error: " << err << std::endl; return false; } return true; } };
-
-
DataManager
客户端使用简单的映射来建立数据信息管理的唯一性–<file, file-fsize-fmtime>,而由于服务端还需要管理上传文件的压缩包路径和文件备份路径,因此我们选择将文件的信息单独分出来一个模块,用来管理文件的备份信息–BackupInfo,而DataManager中的映射表关系就是建立文件名和文件对应的备份信息之间的映射
namespace cloud { class BackupFileInfo { public: bool _pack_flag; size_t _fsize; time_t _mtime; time_t _atime; std::string _urlPath; std::string _realPath; std::string _packPath; BackupFileInfo(){} explicit BackupFileInfo(const std::string &realPath) { FileUtil fu(realPath); if (!fu.Exists()) { LOG("backup path not exist!"); return; } _pack_flag = false; _fsize = fu.FileSize(); _mtime = fu.LastMTime(); _atime = fu.LastATime(); _realPath = realPath; // get from config -- which can be changed Config *config = Config::GetInstance(); // single instance std::string packDir = config->GetPackDir(); std::string packSuffix = config->GetPackSuffix(); std::string downPrefix = config->GetDownPrefix(); _packPath = packDir + fu.FileName() + packSuffix; // ./packDir/a.txt.lz _urlPath = downPrefix + fu.FileName(); // /download/a.txt } }; // end BackuoInfo class DataManager { private: std::string _persistentPath; pthread_rwlock_t _rwLock; std::unordered_map<std::string, cloud::BackupFileInfo> _backupTable; public: DataManager() { _persistentPath = Config::GetInstance()->GetPersistentPath(); pthread_rwlock_init(&_rwLock, NULL); InitLoad(); } void Insert(BackupFileInfo &info) { pthread_rwlock_wrlock(&_rwLock); _backupTable[info._realPath] = info; pthread_rwlock_unlock(&_rwLock); LOG("Insert into table success!"); } void InitLoad() { FileUtil fu(_persistentPath); if(!fu.Exists()) { LOG("persistent path not exist!"); return; } std::string body; fu.GetContentByPosLen(&body, 0, fu.FileSize()); //Unserialization Json::Value root; JsonUtil::UnSerialize(body, &root); //insert into table for (auto &item : root) { BackupFileInfo info; info._atime = item["atime"].asInt64(); info._mtime = item["mtime"].asInt64(); info._fsize= item["fsize"].asInt64(); info._pack_flag = item["packflag"].asBool(); info._packPath = item["packPath"].asString(); info._realPath = item["realPath"].asString(); info._urlPath = item["urlPath"].asString(); Insert(info); } LOG("InitLoad success!"); } void GetAll(std::vector<BackupFileInfo> *allData) { pthread_rwlock_rdlock(&_rwLock); for (auto &item : _backupTable) { allData->push_back(item.second); } pthread_rwlock_unlock(&_rwLock); LOG("GetAll success!"); } void Storage() { //get all data in table std::vector<BackupFileInfo> allData; GetAll(&allData); //add to json Json::Value root; for (auto &item : allData) { Json::Value value; value["atime"] = (Json::Int64)item._atime; value["mtime"] = (Json::Int64)item._mtime; value["fsize"] = (Json::Int64)item._fsize; value["packflag"] = item._pack_flag; value["packPath"] = item._packPath; value["realPath"] = item._realPath; value["urlPath"] = item._urlPath; root.append(value); } LOG("add to json success!"); //serialization std::string body; JsonUtil::Serialize(root, &body); LOG("serialization success!"); //write to file FileUtil fu(_persistentPath); fu.SetContent(body); LOG("write to file success!"); LOG("Storage success!"); } ~DataManager() { pthread_rwlock_destroy(&_rwLock); Storage(); } }; // end DataManager } // end namespace #endif
热点管理
对服务器上备份的文件进行检测,哪些文件长时间没有被访问,则认为是非热点文件,则压缩存储,节省磁盘空间。
实现思路:
遍历所有的文件,检测文件的最后一次访问时间,与当前时间进行相减得到差值,这个差值如果大于设定好的非热点判断时间则认为是非热点文件,则进行压缩存放到压缩路径中,删除源文件遍历所有的文件:
1.从数据管理模块中遍历所有的备份文件信息2.遍历备份文件夹,获取所有的文件进行属性获取,最终判断选择第二种:遍历文件夹,每次获取文件的最新数据进行判断,并且还可以解决数据信息缺漏的题
1.遍历备份目录,获取所有文件路径名称
2.逐个文件获取最后一次访问时间与当前系统时间进行比较判断
3.对非热点文件进行压缩处理,删除源文件
4.修改数据管理模块对应的文件信息(压缩标志-》true)
使用bundle
使用第三方模块bundle来对文件进行压缩和解压:
-
从git克隆bundle库:
sudo yum install git git clone https://github.com/r-lyeh-archived/bundle.git
-
将bundle库中的bundle.h和bundle.cpp拷贝到工程目录下
-
使用时,包含bundle的头文件并在编译时链接cpp即可:
-
//example:compress: #include <iostream> #include <string> #include <fstream> #include "bundle.h" void TestBundle(int argc, char* argv[]) { if(argc < 3) { std::cout << "Usage: ./cloud <origin path> <bundle name>" << std::endl; return ; } std::string origin_path = argv[1]; std::string bundle_name = argv[2]; std::ifstream ifs; std::ofstream ofs; std::cout << origin_path << std::endl; std::cout << bundle_name << std::endl; ifs.open(origin_path, std::ios::binary); if(!ifs.is_open()) { std::cout << "Open file failed!" << std::endl; return ; } ifs.seekg(0, std::ios::end); size_t fsize = ifs.tellg(); ifs.seekg(0, std::ios::beg); std::string body; std::cout << fsize << std::endl; body.resize(fsize); ifs.read(&body[0], fsize); ofs.open(bundle_name, std::ios::binary); if(!ofs.is_open()) { std::cout << "Open file failed!" << std::endl; return ; } std::string packed = bundle::pack(bundle::LZIP, body); ofs.write(packed.c_str(), packed.size()); ifs.close(); ofs.close(); } int main(int argc, char* argv[]) { TestBundle(argc, argv); return 0; }
-
//example : uncompress #include <iostream> #include <fstream> #include "bundle.h" void TestUncompress(int argc, char* argv[]) { if(argc < 3) { std::cout << "Usage: ./uncompress origin_path unpacked" << std::endl; return; } std::ifstream ifs; std::ofstream ofs; ifs.open(argv[1], std::ios::binary); if(!ifs.is_open()) { std::cout << "open file failed " << std::endl; return; } std::string body; ifs.seekg(0, std::ios::end); size_t fsize = ifs.tellg(); ifs.seekg(0, std::ios::beg); body.resize(fsize); ifs.read(&body[0], fsize); if(!ifs.good()) { std::cout << "read failed " <<std::endl; return; } ofs.open(argv[2], std::ios::binary); if(!ifs.is_open()) { std::cout << "open failed " << std::endl; return; } std::string unpacked = bundle::unpack(body); ofs.write(&unpacked[0], unpacked.size()); ifs.close(); ofs.close(); } int main(int argc, char* argv[]) { TestUncompress(argc, argv); }
-
编译时,需要带上bundle的源文件并链接上线程库:
-
尝试压缩某个文件并计算压缩前文件的MD5值
-
尝试解压刚压缩的文件并计算解压后的MD5值
-
比较md5值,发现相同,即文件内容一致,压缩与解压成功
-
HotManager类:
extern cloud::DataManager* dataManager;
namespace cloud
{
class HotManager
{
private:
int _hotTime;
std::string _packFileDirPath;
std::string _backupFileDirPath;
std::string _packfileSuffix;
public:
HotManager()
{
Config* config = Config::GetInstance();
_packFileDirPath = config->GetPackDir();
_backupFileDirPath = config->GetBackupDir();
_packfileSuffix = config->GetPackSuffix();
_hotTime = config->GetHotTime();
DirUtil du1(_packFileDirPath);
if(!du1.Exists())
{
if(!du1.CreateDirectory())
{
LOG("create pack dir failed!");
}
}
DirUtil du2(_backupFileDirPath);
if (!du2.Exists())
{
if (!du2.CreateDirectory())
{
LOG("create backup dir failed!");
}
}
LOG("hot manager init success!");
}
bool HotJudge(std::string &file)
{
FileUtil fu(file);
time_t lastATime = fu.LastATime();
time_t curTime = time(NULL);
if(curTime - lastATime > _hotTime)
{
return true;
}
return false;
}
void RunModule()
{
while (true)
{
//scan dir path
DirUtil du(_backupFileDirPath);
std::vector<std::string> files;
du.ScanDirectory(&files);
//check all files in dir
for (auto &file : files)
{
//if hot file, continue
if(!HotJudge(file))
{
LOG("file is not hot, continue!");
continue;
}
//not a hot file, pack up
//get backupinfo
BackupFileInfo info;
if(!dataManager->GetInfoByFilePath(file, &info))
{
info.NewBackupFileInfo(file);
}
//use info to compress file
FileUtil fu(file);
if(!fu.Compress(info._packPath))
{
LOG(std::string("compress file failed: " + file).c_str());
continue;
}
//remove file
fu.Remove();
info._pack_flag = true;
dataManager->Update(info);
}
sleep(1);
}
}
}; // end HotManager
} // end namespace
#endif
#### 业务处理
此项目中,客户端能通过浏览器访问服务端ip,服务端基于客户端访问的ip地址来返回相应的页面展示目前已经备份在服务器的文件,并可通过点击文件将备份文件重新下载至本地
因此我们需要维护一个模块,用来处理客户端的发送请求,并根据请求做出相应的响应,我们使用到了c++的库httplib来完成HTTP网络通信,此项目中,我们只需维护3个url请求:
+ listshow 或者是 ’/’ -- 网页展示
+ upload -- 客户端上传请求
+ dowload -- 浏览器下载请求
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C86134%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20240909183712693.png&pos_id=img-liw4TmM1-1725878337219)
```c++
static void ListShow(const httplib::Request &req, httplib::Response &res)
{
LOG(std::string("receive request: " + req.path).c_str());
std::vector<BackupFileInfo> files;
dataManager->GetAll(&files);
// construct html
std::stringstream ss;
ss << "<html><head><title>Download</title></head>";
ss << "<body><h1>Download</h1><table>";
for (auto &file : files)
{
ss << "<tr>";
std::string filename = FileUtil(file._realPath).FileName();
ss << "<td><a href='" << file._urlPath << "'>" << filename << "</a></td>";
ss << "<td align='right'>" << TimeToStr(file._mtime) << "</td>";
ss << "<td align='right'>" << file._fsize / 1024 << "k</td>";
ss << "</tr>";
}
ss << "</table></body></html>";
res.body = ss.str();
res.set_header("Content-Type", "text/html");
res.status = 200;
LOG(std::string("list show success, file count: " + std::to_string(files.size())).c_str());
return;
}
static void Upload(const httplib::Request &req, httplib::Response &res)
{
auto ret = req.has_file("file");
if (!ret)
{
LOG("no file");
res.status = 400;
return;
}
const auto &file = req.get_file_value("file");
std::string backupFileDirPath = Config::GetInstance()->GetBackupDir();
std::string fullPath = backupFileDirPath + FileUtil(file.filename).FileName();
FileUtil fu(fullPath);
fu.SetContent(file.content);
BackupFileInfo info;
info.NewBackupFileInfo(fullPath);
dataManager->Insert(info);
LOG("process upload success");
return;
}
static std::string TimeToStr(time_t time)
{
std::string tmp = std::ctime(&time);
return tmp;
}
static std::string GetETag(const BackupFileInfo &info)
{
// etag : filename-fsize-mtime
FileUtil fu(info._realPath);
std::string etag = fu.FileName();
etag += "-";
etag += std::to_string(info._fsize);
etag += "-";
etag += std::to_string(info._mtime);
return etag;
}
static void Download(const httplib::Request &req, httplib::Response &res)
{
LOG(std::string("receive request: " + req.path).c_str());
BackupFileInfo info;
if (!dataManager->GetInfoByUrl(req.path, &info))
{
LOG("url not found: ");
res.status = 400;
return;
}
if (info._pack_flag == true)
{
FileUtil fu(info._packPath);
if (!fu.UnCompress(info._realPath))
{
LOG("uncompress error");
res.status = 400;
return;
}
fu.Remove();
info._pack_flag = false;
dataManager->Update(info);
}
bool retrans = false;
std::string old_etag;
if (req.has_header("If-Range"))
{
old_etag = req.get_header_value("If-Range");
if (old_etag == GetETag(info))
{
retrans = true;
LOG("retrans open");
}
}
FileUtil fu(info._realPath);
if (retrans)
{
std::string range = req.get_header_value("Range");
fu.GetContentByPosLen(&res.body, 0, fu.FileSize());
res.set_header("Accept-Ranges", "bytes");
res.set_header("ETag", GetETag(info));
res.set_header("Content-Type", "application/octet-stream");
// res.set_header("Content-Range", "bytes start-end/fsize");
res.status = 206;
}
else
{
fu.GetContentByPosLen(&res.body, 0, fu.FileSize());
res.set_header("Accept-Ranges", "bytes");
res.set_header("ETag", GetETag(info));
res.set_header("Content-Type", "application/octet-stream");
res.status = 200;
}
}
客户端模块
功能划分
- 能够自动检测客户机指定文件夹中的文件,并判断是否需要备份(目前遇到目录是跳过,后期可更新)
- 将需要备份的文件逐个上传到服务器
- 备份信息持久化存储与状态恢复
功能细分模块
数据管理
为了更方便的判断指定文件夹中的文件是否是需要备份的文件,将备份的文件信息利用 先描述,再组织 的方式管理起来,并提供将备份的数据信息持久化存储和还原的接口,使得本次进程生命周期结束后不会丢失掉已经管理的备份数据信息,并在下一次进程重启时,加载已经备份的数据信息
由于客户端只需要知道某个文件的大小和其上次修改时间就能够管理这份文件,因此不需要使用json序列化存储,只需要用unordered_map建立简单的映射关系就可以完成管理工作
在本项目中,规定某个文件的唯一标识格式为:文件名-文件大小-文件上次修改时间,建立文件和文件唯一标识的映射关系,快速判断目录内文件是否为需要备份的文件,并在退出客户端时将这个表持久化存储,下一次启动客户端时加载已经备份的信息即可。
class DataManager
{
private:
std::string _backupFile; //persistent storage file name -- back.dat
std::unordered_map<std::string, std::string> _dataTable;
public:
DataManager(const std::string& backupFile) :
_backupFile(backupFile)
{
InitLoad();
}
//load the info like "a.txt a.txt-123456-20240823" into table
bool InitLoad()
{}
//persistent storage, for next time to recover
void Storage()
{}
//if file is a new one, load into table
void Update(const std::string& pathName, const std::string& id)
{}
};//end DataManager
目录检查
- c++17–filesystem库
客户端其实本质上就是一个不断对一个文件夹进行检查的死循环,因此需要将这个模块管理起来,提供成员方法来使其模块化,进入循环后,不断扫描检测文件夹,判断其是否满足更新条件,满足则上传到云备份并记录到数据管理模块中
目录模块的实现在公共模块中
文件备份
- Httplib库
此模块是客户端的主要任务模块,通过不断轮询检查扫描文件夹,判断文件是否需要上传,并完成其中的上传和数据管理的处理,其中上传云备份是最重要的部分,我们使用到了httblib库来完成网络传输
class BackUp
{
#define SERVER_IP "xxx"
#define SERVER_PORT xxx
private:
std::string _backDir;
DataManager* _dataManager;
public:
explicit BackUp(const std::string& backDir, const std::string& backFile):
_backDir(backDir)
{}
~BackUp()
{}
std::string CreateIdByPathName(const std::string& pathName)
{}
bool IsNeedUpload(const std::string &pathName)
{
// upload : 1. a new file -- by checking dataTable
// 2. an old file which have been modified compared with last time -- by checking identifier
// judge if a file are modifing, if a file is not be modified in three second,
// it can be considered that this file can be uploaded
// a new file or an old file that able to upload
}
bool Upload(const std::string& pathName)
{
//httplib construct and send by POST
}
bool RunModule()
{
while (1)
{
// scan file in dir, add all files into array
DirUtil dirUtil(_backDir);
std::vector<std::string> pathNames;
if (!dirUtil.ScanDirectory(&pathNames))
{
LOG("scan directory error");
Sleep(1);
}
for (auto& pathName : pathNames)
{
// judge if the file should be uploaded
if (!IsNeedUpload(pathName))
{
continue;
}
// upload if needed
if (Upload(pathName))
{
LOG("upload successfully");
_dataManager->Update(pathName, CreateIdByPathName(pathName));
}
else LOG("upload error");
}
Sleep(1);
LOG("-------------loop once end--------------");
}
}// end RunModule
};//end BackUp
//httplib construct and send by POST
}
bool RunModule()
{
while (1)
{
// scan file in dir, add all files into array
DirUtil dirUtil(_backDir);
std::vector<std::string> pathNames;
if (!dirUtil.ScanDirectory(&pathNames))
{
LOG("scan directory error");
Sleep(1);
}
for (auto& pathName : pathNames)
{
// judge if the file should be uploaded
if (!IsNeedUpload(pathName))
{
continue;
}
// upload if needed
if (Upload(pathName))
{
LOG("upload successfully");
_dataManager->Update(pathName, CreateIdByPathName(pathName));
}
else LOG("upload error");
}
Sleep(1);
LOG("-------------loop once end--------------");
}
}// end RunModule
};//end BackUp