文章目录
- 专栏导读
- 1.客户端数据管理模块实现
- 2.客户端文件检测模块实现
- 3.客户端文件备份模块设计
- 4.客户端文件备份模块实现
专栏导读
🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。
🌸专栏简介:本文收录于 C++项目——云备份
🌸相关专栏推荐:C语言初阶系列、C语言进阶系列 、C++系列、数据结构与算法、Linux
🌸项目Gitee链接:https://gitee.com/li-yuanjiu/cloud-backup
1.客户端数据管理模块实现
客户端要实现的功能是对指定文件夹中的文件自动进行备份上传
。但是并不是所有的文件每次都需要上传,我们需要能够判断,哪些文件需要上传,哪些不需要,因此需要将备份的文件信息给管理起来,作为下一次文件是否需要备份的判断。因此需要被管理的信息包含以下:
文件路径名称
;文件唯一标识
:由文件名,最后一次修改时间,文件大小组成的一串信息;
客户端数据管理模块可直接由服务端数据管理模块改造得到,因为其只需要服务端数据管理模块代码中一小部分功能。
#ifndef __MY_DATA__
#define __MY_DATA__
#include <unordered_map>
#include <sstream>
#include "util.hpp"
namespace cloud
{
class DataManager
{
public:
DataManager(const std::string &backup_file) :_backup_file(backup_file)
{
InitLoad();
}
bool Storage()
{
// 1.获取所有的备份信息
std::stringstream ss;
auto it = _table.begin();
for (; it != _table.end(); ++it)
{
// 2.将所有信息进行指定持久化格式的组织
ss << it->first << " " << it->second << "\n";
}
// 3.持久化存储
FileUtil fu(_backup_file);
fu.SetContent(ss.str());
return true;
}
bool InitLoad()
{
// 1.从文件中读所有数据
FileUtil fu(_backup_file);
std::string body;
fu.GetContent(&body);
// 2.进行数据解析,添加到表当中
std::vector<std::string> array;
Split(body, "\n", &array);
for (auto& a : array)
{
std::vector<std::string> tmp;
Split(a, " ", &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 Updata(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;
}
private:
int Split(const std::string &str, const std::string &sep, std::vector<std::string>* array)
{
int count = 0;
size_t pos = 0, idx = 0;
while (1)
{
pos = str.find(sep, idx);
if (pos == std::string::npos)
{
break;
}
if (pos == idx)
{
idx = pos + sep.size();
continue;
}
std::string tmp = str.substr(idx, pos - idx);
array->push_back(tmp);
count++;
idx = pos + sep.size();
}
if (idx < str.size())
{
array->push_back(str.substr(idx));
count++;
}
return count;
}
private:
std::string _backup_file; // 备份信息持久化存储文件
std::unordered_map<std::string, std::string> _table;
};
}
#endif
2.客户端文件检测模块实现
同样的,客户端文件实用工具类其实与服务端的文件实用工具类雷同,只是功能需求并没有服务端那么多,复制过来即可。
#ifndef __MY_UTIL__
#define __MY_UTIL__
/*
1.获取文件大小
2.获取文件最后一次修改时间
3.获取文件最后一次访问时间
4.获取文件路径名中的文件名称 /abc/test.txt -> test.txt
5.向文件写入数据
6.获取文件数据
7.获取文件指定位置 指定数据长度
8.判断文件是否存在
9.创建目录
10.浏览获取目录下的所有文件路径名
11.压缩文件
12.解压缩所有文件
*/
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <experimental/filesystem>
#include <sys/stat.h>
namespace cloud
{
namespace fs = std::experimental::filesystem;
class FileUtil
{
public:
FileUtil(const std::string &filename) :_filename(filename)
{}
int64_t FileSize()
{
struct stat st;
if(stat(_filename.c_str(), &st) < 0)
{
std::cout << "get file size failed!" << std::endl;
return 0;
}
return st.st_size;
}
time_t LastMTime()
{
struct stat st;
if(stat(_filename.c_str(), &st) < 0)
{
std::cout << "get last modify time failed!" << std::endl;
return -1;
}
return st.st_mtime;
}
time_t LastATime()
{
struct stat st;
if(stat(_filename.c_str(), &st) < 0)
{
std::cout << "get last access time failed!" << std::endl;
return -1;
}
return st.st_atime;
}
std::string FileName()
{
size_t pos = _filename.find_last_of("/");
if(pos == std::string::npos)
{
return _filename;
}
return _filename.substr(pos+1);
}
bool GetPosLen(std::string *body, size_t pos, size_t len)
{
size_t fsize = FileSize();
if(pos + len > fsize)
{
std::cout << "get file len error" << std::endl;
return false;
}
std::ifstream ifs;
ifs.open(_filename, std::ios::binary);
if(ifs.is_open() == false)
{
std::cout << "open file failed!" << std::endl;
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" << std::endl;
ifs.close();
return false;
}
ifs.close();
return true;
}
bool GetContent(std::string *body)
{
size_t fsize = 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" << std::endl;
return false;
}
ofs.write(&body[0], body.size());
if(ofs.good() == false)
{
std::cout << "write open file failed" << std::endl;
ofs.close();
return false;
}
ofs.close();
return true;
}
bool Exists()
{
return fs::exists(_filename);
}
bool Remove()
{
if(Exists() == false)
{
return true;
}
remove(_filename.c_str());
return true;
}
bool CreateDirectory()
{
if(Exists()) return true;
return fs::create_directories(_filename);
}
bool ScanDirectory(std::vector<std::string> *array)
{
CreateDirectory();
for(auto& p : fs::directory_iterator(_filename))
{
if(fs::is_directory(p) == true) continue;
// relative_path 带有路径的文件名
array->push_back(fs::path(p).relative_path().string());
}
return true;
}
private:
std::string _filename;
};
}
#endif
3.客户端文件备份模块设计
回顾客户端自动将指定文件夹中的文件备份到服务器流程:
遍历指定文件夹
;逐一判断文件是否需要备份
;需要备份的文件进行上传备份
;
客户端文件备份类主要包含以下成员:
#define SERVER_ADDR "47.108.25.253" // 服务器IP
#define SERVER_PORT 8989 // 服务器端口
class Backup
{
public:
Backup(const std::string& back_dir, const std::string& back_file);
// 生成文件的唯一标识
std::string GetFileIdentifier(std::string filename);
// 上传文件函数
bool Upload(const std::string& filename);
// 判断是否需要上传
bool IsNeedUpload(const std::string& filename);
// 主逻辑执行函数
bool RunMoudle();
private:
std::string _back_dir; // 监控的文件目录
DataManager* _data;
};
4.客户端文件备份模块实现
#ifndef __MY_CLOUD__
#define __MY_CLOUD__
#include "data.hpp"
#include "httplib.h"
#include <Windows.h>
#define SERVER_ADDR "47.108.25.253"
#define SERVER_PORT 8989
namespace cloud
{
class Backup
{
public:
Backup(const std::string& back_dir, const std::string& back_file)
:_back_dir(back_dir)
{
_data = new DataManager(back_file);
}
std::string GetFileIdentifier(std::string filename)
{
FileUtil fu(filename);
std::stringstream ss;
ss << fu.FileName() << "-" << fu.FileSize() << "-" << fu.LastMTime();
return ss.str();
}
bool Upload(const std::string& filename)
{
// 1.获取文件数据
FileUtil fu(filename);
std::string body;
fu.GetContent(&body);
// 2.搭建http客户端上传文件数据
httplib::Client client(SERVER_ADDR, SERVER_PORT);
httplib::MultipartFormData item;
item.content = body;
item.filename = fu.FileName();
item.content_type = "application/octet-stream";
httplib::MultipartFormDataItems items;
items.push_back(item);
auto res = client.Post("/upload", items);
if (!res || res->status != 200)
{
return false;
}
return true;
}
bool IsNeedUpload(const std::string& filename)
{
// 需要上传的文件判断条件:文件是新增的,不是新增的但是被修改过
// 文件是新增的:看一下有没有备份信息
// 不是新增的但是被修改过:有历史信息,但是历史信息的唯一标识符与当前最新的唯一标识符不一致
std::string id;
if (_data->GetOneByKey(filename, &id) != false)
{
// 有历史信息
std::string new_id = GetFileIdentifier(filename);
if (new_id != id)
{
return false;
}
}
//判断一个文件是否有一段时间没有被修改过了
FileUtil fu(filename);
if (time(NULL) - fu.LastMTime() < 3)
{
return false;
}
return true;
}
bool RunMoudle()
{
while (1)
{
// 1.遍历获取指定文件夹中所有文件
FileUtil fu(_back_dir);
std::vector<std::string> array;
fu.ScanDirectory(&array);
// 2.逐个判断是否需要上传
for (auto& a : array)
{
if (IsNeedUpload(a) == false)
continue;
// 3.如果需要,则上传文件
if (Upload(a) == true)
{
_data->Insert(a, GetFileIdentifier(a));
}
}
Sleep(1);
}
}
private:
std::string _back_dir;
DataManager* _data;
};
}
#endif