【项目】云备份
云备份
- 云备份
- 概述
- 框架
- 功能演示
- 服务端
- 客户端
- 公共模块
- 文件操作模块
- 目录操作模块
- 服务端模块
- 功能划分
- 功能细分模块
- 数据管理
- 热点管理
- 客户端模块
- 功能划分
- 功能细分模块
- 数据管理
- 目录检查
- 文件备份
云备份
概述
自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。并且能够随时通过浏览器进行查看并且下载,其中下载过程支持断点续传功能,而服务器也会对上传文件进行热点管理,将非热点文件进行压缩存储,节省磁盘空间。
框架
实现客户端和服务端双端程序,合作实现总体的自动云备份功能
本项目有三大模块:
- 客户端模块
- 服务端模块
- 公共模块
除了自己实现的模块之外,还引入了三方库作为辅助:
- Jsoncpp进行序列化辅助
- Httplib进行网络传输辅助
- c++17–filestream进行目录检测辅助
功能演示
服务端
-
若客户端有文件上传,则会自动下载到服务端的backupDir目录下
-
若此文件超过30s时间没有被获取则会自动压缩到packDir目录下
-
从浏览器访问备份呢列表
-
点击文件名下载文件:
当服务端接收到下载命令时,若此文件已在压缩目录下,则先将其解压到备份目录中,再将内容发送给客户端
-
若此时有用户访问了服务端并下载了某个文件,下载一半时,服务端异常关闭导致用户下载中断,服务端恢复后,用户能继续正常的从上次中断的地方继续下载, 实现断点续传
客户端
-
找到backup目录,若没有时则创建(可以考虑写成读取配置文件)
第一次载入时,若没有此目录会自动创建
-
不断轮询检测目录backup,一旦有文件新增或更新,就会发送到远端,实现云备份
-
注意,不一定所有更新都会发送到远端,若有一份很大的文件正在拷贝进backup目录,相当于这份文件在实时更新,但实际是此文件还没有拷贝完毕,不能发送到远端,因此客户端的策略是对某个更新的文件,若此文件上一次更新时间与当前时间相比超过了3s钟才进行云备份
公共模块
公共模块是完成一系列对文件或者目录上的操作,本项目中细分为文件操作模块和目录操作模块,以及日志等简单的公共模块
文件操作模块
namespace cloud
{//example: you can nest your own log systemvoid LOG(const char* str){printf("[%s |%d]: %s\n", __FILE__, __LINE__, str);}//for operationnamespace 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 temporarilyif (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 changedConfig *config = Config::GetInstance(); // single instancestd::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 BackuoInfoclass 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());//UnserializationJson::Value root;JsonUtil::UnSerialize(body, &root);//insert into tablefor (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 tablestd::vector<BackupFileInfo> allData;GetAll(&allData);//add to jsonJson::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!");//serializationstd::string body;JsonUtil::Serialize(root, &body);LOG("serialization success!");//write to fileFileUtil 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 pathDirUtil du(_backupFileDirPath);std::vector<std::string> files;du.ScanDirectory(&files);//check all files in dirfor (auto &file : files){//if hot file, continueif(!HotJudge(file)){LOG("file is not hot, continue!");continue;}//not a hot file, pack up//get backupinfoBackupFileInfo info;if(!dataManager->GetInfoByFilePath(file, &info)){info.NewBackupFileInfo(file);}//use info to compress fileFileUtil fu(file);if(!fu.Compress(info._packPath)){LOG(std::string("compress file failed: " + file).c_str());continue;}//remove filefu.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 -- 浏览器下载请求```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 htmlstd::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-mtimeFileUtil 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.datstd::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 tablebool InitLoad(){}//persistent storage, for next time to recovervoid Storage(){}//if file is a new one, load into tablevoid 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 xxxprivate: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 arrayDirUtil 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 uploadedif (!IsNeedUpload(pathName)){continue;}// upload if neededif (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 arrayDirUtil 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 uploadedif (!IsNeedUpload(pathName)){continue;}// upload if neededif (Upload(pathName)){LOG("upload successfully");_dataManager->Update(pathName, CreateIdByPathName(pathName));}else LOG("upload error");}Sleep(1);LOG("-------------loop once end--------------");}
}// end RunModule
};//end BackUp
相关文章:

【项目】云备份
云备份 云备份概述框架 功能演示服务端客户端 公共模块文件操作模块目录操作模块 服务端模块功能划分功能细分模块数据管理热点管理 客户端模块功能划分功能细分模块数据管理目录检查文件备份 云备份 概述 自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。…...

WebGL系列教程二(环境搭建及初始化Shader)
目录 1 前言2 新建html页面3 着色器介绍3.1 顶点着色器、片元着色器与光栅化的概念3.2 声明顶点着色器3.3 声明片元着色器 4 坐标系(右手系)介绍5 着色器初始化5.1 给一个画布canvas5.2 获取WebGL对象5.3 创建着色器对象5.4 获取着色器对象的源5.5 绑定着色器的源5.6 编译着色器…...

keepalive和nginx高可用集群
keepalived 和 nginx 高可用集群搭建 主备模式 zyj86主机和zyj87主机安装nginx和keepalived yum install nginx keepalived -y systemctl enable --now nginx.service keepalived.service主调度器配置 编辑zyj86主机(主)配置文件 vi /etc/keepalived…...

二分查找题总结
二分查找题总结 hot100搜索插入位置搜索二维矩阵在排序数组中查找元素的第一个和最后一个位置搜索旋转排序数组寻找旋转排序数组中的最小值寻找两个正序数组的中位数 hot100 搜索插入位置 题目链接: 35.搜索插入位置 代码: class Solution {public in…...

仕考网:公务员面试流程介绍
通知进面信息——资格审查——面试签到——抽签候考 面试形式: 面试分为结构化和无领导小组两种形式 1.在结构化面试中,当轮到某位考生时,引导员将在候考室宣布其编号,随后考生跟随引导人员前往考场入口。考生在开始考试时需回…...

(十五)SpringCloudAlibaba-Sentinel持久化到Nacos
前言 在前面我们已经将Sentinel配置的规则持久化到系统的文件中。本章节我们将Sentinel持久化到Nacos中; 传送门(Sentinel数据持久化到文件)https://blog.csdn.net/weixin_45876411/article/details/140742963 默认情况下 Sentinel 只能接收到 Nacos 推送的消息,但…...

GitHub图床
GitHub图床 文章目录 GitHub图床图床介绍Github访问GitHub手动修改hostsgithub520 加速器创建账户创建仓库创建token PicGoTypora 图床介绍 图床 存放图片的地方 为什么设置图床呢 在我认识图床之前, 有一个问题 [^放在typora上面的图片, 其实是一个链接, 并且将图片存放在本地…...

记一次高版本view-design的组件迁移到自身项目的低版本
背景 npm i -S view-design当前老项目使用view-design这个组件库,但是当我们去官网查看该组件库最新版本,竟然发现没有博主想用的image/ImagePreivew这两个基础组件 说实话,有点离谱了哈!! 自己造轮子? …...

QT运行ROS工程
文章目录 使用QT创建ROS工程项目配置修改cmake环境配置运行设置 运行 使用QT创建ROS工程 工程名字和路径 下一步(直接选择默认选项就可以)->完成 完成之后 是这样的 接下来在工作空间里面创建功能包 鼠标选中src点击右键->添加新文件 name::功能包的名字…...

电脑技巧:如何在Win11电脑上调整设置,让屏幕更加护眼?
目录 一、调整屏幕亮度 二、启用夜间模式 三、调整色彩设置 四、使用第三方护眼软件 五、保持良好的用眼习惯 总结 随着长时间使用电脑的人越来越多,护眼问题也变得越来越重要。Win11作为更新的操作系统,提供了更多的设置选项来帮助我们保护眼睛。本文将详细介绍如何在…...

【数据结构】排序算法篇二
【数据结构】排序算法篇二 1. 快速排序(hoare版本)(1)基本思想:(2)动态图解:(3)代码实现:(4)特性总结: 2. 快速…...

python进阶篇-day09-数据结构与算法(非线性结构与排序算法)
非线性结构(树状结构) 特点: 每个节点都可以有n个子节点(后继节点) 和 n个父节点(前驱节点) 代表: 树, 图...... 概述 属于数据结构之 非线性结构的一种, 父节点可以有多个子节点(后续节点) 特点 有且只有1个根节点 每个节点都可以有1个父节点及任意个子节点, 前提: 根节点除…...

线性代数基础
Base 对于矩阵 A,对齐做 SVD 分解,即 U Σ V s v d ( A ) U\Sigma V svd(A) UΣVsvd(A). 其中 U 为 A A T AA^T AAT的特征向量,V 为 A T A A^TA ATA的特征向量。 Σ \Sigma Σ 的对角元素为降序排序的特征值。显然,U、V矩阵…...

LCR 021
题目:LCR 021 解法一:计算链表长度 遍历两次,第一次获取链表长度 L(包括虚拟节点),第二次遍历到第 L-n 个节点(从虚拟节点遍历) public ListNode removeNthFromEnd(ListNode head, …...

【阿雄不会写代码】全国职业院校技能大赛GZ036第四套
也不说那么多了,要用到这篇博客,肯定也知道他是干嘛的,给博主点点关注点点赞!!!这样博主才能更新更多免费的教程,不然就直接丢付费专栏里了,需要相关文件请私聊...

Vue组件:使用$emit()方法监听子组件事件
1、监听自定义事件 父组件通过使用 Prop 为子组件传递数据,但如果子组件要把数据传递回去,就需要使用自定义事件来实现。父组件可以通过 v-on 指令(简写形式“”)监听子组件实例的自定义事件,而子组件可以通过调用内建…...

数据分析-埋点
1、数据埋点的定义 针对特定用户行为或事件进行捕获、处理何发送的相关技术及其实施过程。 2、数据埋点的原理 埋点是数据采集的重要方式。通过在页面上植入代码,监控用户行为(例:页面加载、按钮点击等)。用户一旦触发了该事件,就会根据埋点信息将相关数…...

【文心智能体】通过工作流使用知识库来实现信息查询输出,一键查看旅游相关信息,让出行多一份信心
欢迎来到《小5讲堂》 这是《文心智能体平台》系列文章,每篇文章将以博主理解的角度展开讲解。 温馨提示:博主能力有限,理解水平有限,若有不对之处望指正! 目录 创建灵感基本配置头像名称和简介人物设定角色与目标思考路…...

服务器监控工具都是监控服务器的哪些性能和指标
服务器监控工具通常用于确保服务器及其相关服务的正常运行。这些工具可以帮助管理员快速识别并解决问题,从而减少停机时间和性能下降的风险。以下是服务器监控工具通常会监控的一些主要内容: 系统健康状态: CPU使用率 内存(RAM&…...

不小心删除丢失了所有短信?如何在 iPhone 上查找和恢复误删除的短信
不小心删除了一条短信,或者丢失了所有短信?希望还未破灭,下面介绍如何在 iPhone 上查找和恢复已删除的短信。 短信通常都是非正式和无关紧要的,但短信中可能包含非常重要的信息。因此,如果您删除了一些短信以清理 iPh…...

【skyvern 快速上手】一句话让AI帮你实现爬虫+自动化
目录 skyvern介绍主要特点工作流程 部署(重点介绍源码部署)源码部署docker快速部署 运行(基于源码)后端前端 快速使用示例总结 skyvern介绍 Skyvern 是一款利用大语言模型(LLM)和计算机视觉技术来自动化浏…...

【C++ Primer Plus习题】14.1
大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream> #include "wine.h" …...

在Ubuntu上运行QtCreator相关程序
背景:希望尝试在Linux系统上跑一下使用QtCreator相关的程序,因为有一些工作岗位要求有Linux上使用Qt的经验。 (1)我是把Windows上的程序移过来的,Windows上文件名称是不区分大小写的。 而Ubuntu上是区分的 所以一部分头文件需要进行修改&am…...

MybatisPlus 快速入门
目录 简介 安装 Spring Boot2 Spring Boot3 Spring 配置 Spring Boot 工程 Spring 工程 常见注解 条件构造器 流式查询 使用示例 批量操作 使用示例 自定义SQL Service接口 CRUD 扩展功能 代码生成 安装插件 通用枚举 配置枚举处理器 插件功能 配置示例…...

Java.lang中的String类和StringBuilder类介绍和常用方法
目录 Java.lang中的String类和StringBuilder类介绍和常用方法 String类介绍 String类的底层成员介绍 基本介绍 回顾String传址调用问题 String类对象的创建方式 String面试题 创建对象or不创建对象 创建了几个对象and共有几个对象 String常用方法 判断字符串是否相等方法 获取字…...

notepad++软件介绍(含安装包)
Notepad 是一款开源的文本编辑器,主要用于编程和代码编辑。它是一个功能强大的替代品,常常被用来替代 Windows 系统自带的记事本。 Notepad win系统免费下载地址 以下是 Notepad 的一些主要特点和功能: 多语言支持:Notepad 支持多…...

chapter13-常用类——(章节小结)——day17
498-常用类阶段梳理...

RTX AI PC 和工作站上部署多样化 AI 应用支持 Multi-LoRA
今天的大型语言模型(LLMs)在许多用例中都取得了前所未有的成果。然而,由于基础模型的通用性,应用程序开发者通常需要定制和调整这些模型,以便专门针对其用例开展工作。 完全微调需要大量数据和计算基础设施࿰…...

C++ STL-deque容器入门详解
1.1 deque容器基本概念 功能: 双端数组,可以对头端进行插入删除操作 deque与vector区别: vector对于头部的插入删除效率低,数据量越大,效率越低deque相对而言,对头部的插入删除速度回比vector快vector访…...

数据结构之折半查找
折半查找(Binary Search),也称为二分查找,是一种在有序数组中查找特定元素的搜索算法。其工作原理是,通过不断将待查找的区间分成两半,并判断待查找的元素可能存在于哪一半,然后继续在存在可能性…...