客户端要实现的功能是:对指定文件夹中的文件自动进行备份上传。但是并不是所有的文件每次都需要上传,我们需要能够判断,哪些文件需要上传,哪些不需要,因此需要将备份的文件信息给管理起来,作为下一次文件是否需要备份的判断。因此需要被管理的信息包含以下:
所以也需要对文件进行操作的fileutil
工具,这个其实与服务端的文件实用工具类颇为相似,直接复制过来即可。
同时也需要对文件进行管理,需要datamanager
模块。
在之后就是对文件进行上传的文件备份backup
模块
与服务端类似。
namespace fs = std::experimental::filesystem;
class FileUtil {
private:
std::string _name;
public:
FileUtil(const std::string &name) :_name(name) {}
size_t FileSize();
time_t LastATime();
time_t LastMTime();
std::string FileName();
bool GetPosLen(std::string *content, size_t pos, size_t len);
bool GetContent(std::string *content);
bool SetContent(const std::string &content);
bool Exists();
bool CreateDirectory();
bool ScanDirectory(std::vector<std::string> *arry);
};
与服务端的差别是在_table 中的val 存储的值的类型不同,其余的也几乎都相同,都是对文件数据的增查改,对文件数据进行永久化储存,程序运行时的初始化。
class DataManager{
private:
std::unordered_map<std::string, std::string> _table;
std::string _back_file;
public:
DataManager(const std::string back_file);
bool InitLoad();//程序运行时加载以前的数据
bool Storage();//持久化存储
bool Insert(const std::string &key, const std::string &val);
bool Update(const std::string &key, const std::string &val);
bool GetOneByKey(const std::string &key, std::string *val);
};
搭建客户端,然后循环检测被管理目录下的文件是否需要被备份。
#define SRV_IP "1.1.1.1"
#define SRV_PORT 9000
class BackUp {
private:
DataManager *_data;
std::string _back_dir;
std::string _back_file;
bool Upload(const std::string &filename);
bool IsCanBeUpload(const std::string &filename);
std::string GetFileIdentifier(const std::string &filename);
public:
BackUp(const std::string &back_dir, const std::string &back_file)
: _back_dir(back_dir)
, _back_file(back_file){}
bool RunModule();
};
需要注意的部分是,在判断文件是否需要被备份时的条件,具体会在代码部分指出。
整个客户端大致就是如此了。
util.hpp
#pragma once
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#include
#include
#include
#include
#include
#include
#include
#include
// FileUtile 工具提供了对文件的增删查改的功能,
// 也提供了对目录的查看功能,和创建目录的功能
namespace Cloud
{
namespace fs = std::experimental::filesystem;
class FileUtil
{
private:
std::string _Filename;
public:
FileUtil(std::string fname) : _Filename(fname)
{
// std::cout << fname << std::endl;
}
size_t Filesize() // 提取文件大小
{
struct stat st;
if (stat(_Filename.c_str(), &st) < 0)
{
std::cout << "get Filesize fail"
<< std::endl;
return 0;
}
return st.st_size;
}
std::string Filename() // 提取文件名
{
// /a/b/文件名
size_t pos = _Filename.find_last_of("\\");
if (pos == std::string::npos)
{
return _Filename;
}
return _Filename.substr(pos + 1);
}
time_t LastMtime() // 提取文件最后一次的修改时间(文件内容)
{
struct stat st;
if (stat(_Filename.c_str(), &st) < 0)
{
std::cout << "get File LastMtime fail"
<< std::endl;
return -1;
}
return st.st_mtime;
}
time_t LastAtime() // 提取文件最后一次的访问时间
{
struct stat st;
if (stat(_Filename.c_str(), &st) < 0)
{
std::cout << "get File LastAtime fail"
<< std::endl;
return -1;
}
return st.st_atime;
}
time_t LastCtime() // 提取文件最后一次的修改时间(文件内容 || 文件属性)
{
struct stat st;
if (stat(_Filename.c_str(), &st) < 0)
{
std::cout << "get File LastCtime fail"
<< std::endl;
return -1;
}
return st.st_ctime;
}
bool Remove()
{
if (this->Exists() == false) {
return true;
}
remove(_Filename.c_str());
return true;
}
bool GetPosLen(std::string &body, size_t pos, size_t len)
{
size_t fsize = this->Filesize();
if (pos + len > fsize)
{
std::cout << "get file len is error\n";
return false;
}
std::ifstream ifs;
ifs.open(_Filename, std::ios::binary);
if (ifs.is_open() == false)
{
std::cout << "read open file failed!\n";
return false;
}
ifs.seekg(pos, std::ios::beg);
body.resize(len);
ifs.read(&body[0], len);
if (ifs.good() == false)
{
std::cout << "get file content failed\n";
ifs.close();
return false;
}
ifs.close();
return true;
}
bool GetContent(std::string &body)
{
size_t fsize = this->Filesize();
return GetPosLen(body, 0, fsize);
}
bool SetContent(const std::string &body)
{
std::ofstream ofs;
ofs.open(_Filename, std::ios::binary);
if (ofs.is_open() == false)
{
std::cout << "write open file failed!\n";
return false;
}
ofs.write(&body[0], body.size());
if (ofs.good() == false)
{
std::cout << "write file content failed!\n";
ofs.close();
return false;
}
ofs.close();
return true;
}
bool Exists()
{
return fs::exists(_Filename);
}
bool CreateDirectory()
{
if (this->Exists())
return true;
return fs::create_directories(_Filename);
}
bool ScanDirectory(std::vector<std::string> &arry)
{
CreateDirectory();
for (auto &p : fs::directory_iterator(_Filename))
{
if (fs::is_directory(p) == true)
{
continue;
}
// relative_path 带有路径的文件名
arry.push_back(fs::path(p).relative_path().string());
}
return true;
}
};
data.hpp
#pragma once
#include "util.hpp"
#include
#include
namespace Cloud
{
#define SEP " "
class DataManager {
private:
std::string _backup_file; // 存储文件信息的文件
std::unordered_map<std::string, std::string> _table;
int Split(std::string& str, std::string sep, std::vector<std::string>* arry)
{
int count = 0;
size_t pos = 0, idx = 0;
while (true)
{
pos = str.find(sep, idx);
if (pos == idx)
{
idx += sep.size();
continue;
}
if (pos == std::string::npos)
{
break;
}
arry->push_back(str.substr(idx, pos - idx));
idx = pos + sep.size();
count++;
}
if (idx < str.size())
{
arry->push_back(str.substr(idx));
count++;
}
return count;
}
public:
DataManager(const std::string& backup_file):_backup_file(backup_file)
{
InitLoad();
}
bool Storage() { // 将_table里的文件信息写入 back_file
// 1、获取_table的信息
std::stringstream ss;
for (const auto& a : _table)
{
//2. 将所有信息进行有格式化的组织
ss << a.first << SEP << a.second << std::endl;
}
//3. 写入到 back_file 文件
FileUtil fu(_backup_file);
fu.SetContent(ss.str());
return true;
}
bool InitLoad() { // 在DataManager 实列化对象时,将_backup_file 里的文件信息提取到 _table中
// 1. 从 file中提取数据
std::string body;
FileUtil fu(_backup_file);
fu.GetContent(body);
// 2. 将数据进行分割,然后放入 _table中。
std::vector<std::string> arry;
Split(body, "\n", &arry);
for (auto& a : arry)
{
std::vector<std::string> tmp;
Split(a, SEP, &tmp);
if (tmp.size() != 2)
{
continue;
}
_table[tmp[0]] = tmp[1];
}
return true;
}
bool Insert(const std::string& key, const std::string& val) {
_table[key] = val;
Storage();
return true;
}
bool Update(const std::string& key, const std::string& val) {
_table[key] = val;
Storage();
return true;
}
bool GetOneByKey(const std::string key, std::string* val) {
auto it = _table.find(key);
if (it == _table.end())
{
return false;
}
*val = it->second;
return true;
}
};
}
cloud.hpp
#pragma once
#include"httplib.h"
#include"util.hpp"
#include"data.hpp"
namespace Cloud {
#define SERVER_IP "60.204.140.244"
#define SERVER_PORT 9090
class Backup{
private:
std::string _back_dir; //需要管理的目录
DataManager* _data;
bool IsNeedUpload(const std::string& filename)
{
// 1. 如果文件未被备份过,则需要进行备份 2. 如果文件被修改过,则需要重新备份
FileUtil fu(filename);
std::string old_id;
if (_data->GetOneByKey(filename, &old_id)) // 查看文件是否被备份
{
//std::cout << old_id << std::endl;
std::string new_id = GetFileIdentifier(filename);
if (old_id == new_id)
{
return false;
}
//一个文件比较大,正在徐徐的拷贝到这个目录下,拷贝需要一个过程,
//如果每次遍历则都会判断标识不一致需要上传一个几十G的文件会上传上百次
//因此应该判断一个文件一段时间都没有被修改过了,则才能上传
// 合理的判断方式应该是判断该文件是否被其他线程占用,是否处于被使用的状态 ,我们采取简单一点的时间判断
else
{
if (time(NULL) - fu.LastMtime() < 10)
return false;
}
}
//std::cout << old_id << std::endl;
return true;
}
bool Upload(const std::string& file)
{
// 1. 获取数据
FileUtil fu(file);
std::string body;
fu.GetContent(body);
//std::cout << fu.Filename() <<": " << fu.Filesize() << std::endl;
// 2. 搭建客户端,填充客户端信息
httplib::Client client(SERVER_IP, SERVER_PORT);
httplib::MultipartFormData item;
item.content = body;
item.filename = fu.Filename();
item.content_type = "application/octet-stream"; // 表示上传的数据类型为任一数据类型,以二进制形式上传
item.name = "file";
httplib::MultipartFormDataItems items;
items.push_back(item);
// 3. 上传文件
auto res = client.Post("/Upload", items);
if (!res || res->status != 200)
{
return false;
}
return true;
}
std::string GetFileIdentifier(const std::string& filename) {
// a.txt-fsize-mtime
FileUtil fu(filename);
std::stringstream ss;
ss << fu.Filename() << "-" << fu.Filesize() << "-" << fu.LastMtime();
return ss.str();
}
public:
Backup(const std::string& back_dir, const std::string back_file):_back_dir(back_dir)
{
_data = new DataManager(back_file);
}
bool RunModel()
{
while (true)
{
//1. 遍历文件列表,获取信息
FileUtil fu(_back_dir);
std::vector<std::string> arry;
fu.ScanDirectory(arry);
//std::cout << arry.size() << std::endl;
for (auto& a : arry)
{
//std::cout << a << std::endl;
// 获取唯一标识符 ,判断是否需要备份
if (IsNeedUpload(a)==true)
{
std::cout << "文件需要上传\n";
if (Upload(a) == true)
{
std::cout <<GetFileIdentifier(a) << "文件上传成功\n";
_data->Insert(a, GetFileIdentifier(a));
}
}
}
Sleep(1000);
}
return true;
}
};
}