【云备份项目总结】客户端篇

在这里插入图片描述

项目总结

  • 整体回顾
    • util.hpp
    • data.hpp
    • cloud.hpp
  • 代码

客户端的代码与服务端的代码实现有很多相似之处,我们也只编写一个简单的客户端代码。

整体回顾

客户端要实现的功能是:对指定文件夹中的文件自动进行备份上传。但是并不是所有的文件每次都需要上传,我们需要能够判断,哪些文件需要上传,哪些不需要,因此需要将备份的文件信息给管理起来,作为下一次文件是否需要备份的判断。因此需要被管理的信息包含以下:

  1. 文件路径名称
  2. 文件唯一标识:由文件名,最后一次修改时间,文件大小组成的一串信息

所以也需要对文件进行操作的fileutil工具,这个其实与服务端的文件实用工具类颇为相似,直接复制过来即可。

同时也需要对文件进行管理,需要datamanager模块。
在之后就是对文件进行上传的文件备份backup模块

util.hpp

与服务端类似。

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);
};

data.hpp

与服务端的差别是在_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);
};

cloud.hpp

搭建客户端,然后循环检测被管理目录下的文件是否需要被备份。

#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 <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <memory>
#include <experimental/filesystem>

//   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<unordered_map>
#include<sstream>


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;
		}
	};

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/141806.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2020年09月 Scratch(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题(共25题,每题2分,共50分) 第1题 下面哪个积木能够调节左右声道的音量? A: B: C: D: 答案:C 第2题 当我们进行数学计算时,需要用到下面哪个模块中的积木? A: B:

【已解决】vscode 配置C51和MDK环境配置

使用命令 gcc -v -E -x c - 看自己gcc 有没有安装好 也可以在自己的vscode中新建一个终端 gcc -v g -v 首先把自己的C51 和MDK 路径 设置好 vscode 中设置 C51 和 MDK 的路径 这是你keil 中写 51单片机和 STM32 的 如果你出现什么include 的什么波浪线&#xff0c;那估计…

如何用python生成动态随机验证码图片

相信大部分小伙伴在登录注册页面都看到过这样的验证码图片&#xff1a; 今天就带大家用实现一波这种验证码图片的生成&#xff0c;这在Django开发中可以拿来即用~ 1. 首先导入必要的库&#xff1a; import random from PIL import Image, ImageDraw, ImageFont, ImageFilter…

编译原理-至下而上的语法分析

文章目录 至下而上分析的基本问题归约短语规范归约符号栈的使用 算符优先分析优先关系算符文法及优先关表构造如何求FIRSTVT和LASTVT算符优先分析算法优先函数 至下而上分析的基本问题 归约 用一个寄存符号的先进后出栈&#xff0c;把输入符号一个一个地移进栈里&#xff0c;…

SAP 52策略测试简介

我们在前面测试了50策略按单生产,创建完计划独立需求后,通过主数据中的独立集中的字段控制下层物料是否能通过计划订单转成生产订单和采购订单。 52策略其实和50策略非常的相似。52策略就是按库存生产,创建完计划独立需求后的结果和50策略是一样的。 1、我们先看下50策略和…

从零基础到精通:Flutter开发的完整指南

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 第一部分&#xff1a;入…

Linux:给openlab搭建web网站

httpd服务器建立综合练习 建立网站需求&#xff1a; 1.基于域名 www.openlab.com 可以访问网站内容为 welcome to openlab!!! 2.给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料和缴费网站&#xff0c; &#xff08;1&#xff09;、基于 www.openlab.com/stud…

LiveMedia视频监控汇聚管理平台功能方案之REST HTTP接口服务(六)

LiveMedia视频监控汇聚管理平台全面支持HTTP接口与其他系统对接&#xff0c;接口包含登陆、视频设备/组织结构添加、修改、删除、实时视频、录像回放、定位、设备控制、报警通知及报警联动等&#xff0c;第三方系统可以无缝的把视频中间件当作自身系统中的一个组件来调用和同步…

常用的软件测试组织架构模型

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;加入1000人软件测试技术学习交流群&#x1f4e2;资源分享&#xff1a;进了字节跳动之后&#xff0c;才…

Slax Linux 强化了会话管理和引导参数选项

导读Slax Linux 的创始人和维护者Tomas Matejicek 宣布 了他的微型和便携 GNU/Linux 发行版的新版本&#xff0c;带来了各种增强和错误修复。 新的 Slax Linux 版本&#xff08;基于 Debian 的 12.1 版本和基于 Slackware 的 15.0.2 版本&#xff09;引入了在可写设备上运行发…

黑客通过ScreenConnect远程访问入侵医疗机构

导语&#xff1a;最近&#xff0c;安全研究人员发现黑客利用ScreenConnect远程访问工具对美国多家医疗机构进行攻击。这些威胁行为利用了Transaction Data Systems&#xff08;TDS&#xff09;使用的本地ScreenConnect实例&#xff0c;该公司是一家在全美50个州都有业务的药店供…

Monkey压力测试

环境搭建 提前下好java&#xff08;我的是java8&#xff09; https://www.androiddevtools.cn/ 下载Android SDK 解压后点击安装 可以使用雷神模拟器模拟手机连接 dumpsys activity activities | grep “mFocusedActivity”&#xff08;获取当前运行进程包名&#xff09; ex…

【数据结构—— 栈的实现(数组栈)】

数据结构—— 栈的实现 一.栈1.1栈的概念及结构 二.栈的实现2.1头文件的实现——&#xff08;Strck.h&#xff09;2.2 源文件的实现——&#xff08;Strck.c&#xff09;2.3 源文件的实现——&#xff08;test.c&#xff09; 三.栈的实际数据测试展示3.1正常的后进先出方式3.2 …

对自动化测试的一些展望与理解

1. 需求和目标 在我开展自动化测试之前&#xff0c;其实该项目以前的测试人员也已经写了很多的接口测试用例&#xff0c;但是大多数用例处于“半瘫痪”状态&#xff0c;在CI上无人维护&#xff08;听说起初是有人维护的&#xff0c;但是后来用例多了&#xff0c;维护的人每次花…

技术管理责任制度《一》

一、技术管理机构责任制 (1) 按各级技术人员的职责范围&#xff0c;分工负责&#xff0c;做好经常性的技术业务工作。 (2) 组织贯彻执行国家有关技术政策和上级办法的技术标准、规定、规程、和各项技术管理制。 (3) 负责收集和提供技术情报、技术资料、技术建议和技术措施等。 …

元宇宙时代,数字员工正成为企业服务的黄金担当!

未来&#xff0c;你的同事可能不是“人” 自2021年“元宇宙”爆火之后&#xff0c;作为连接现实世界和元宇宙的媒介之一&#xff0c;虚拟人开始大量跑步入场。伴随着虚拟数字人相关技术包括CG、语音识别、图像识别、动捕等的共同成熟&#xff0c;让数字虚拟产业在今年渐入佳境…

Rust-使用dotenvy加载和使用环境变量

系统的开发&#xff0c;测试和部署离不开环境变量&#xff0c;今天分享在Rust的系统开发中&#xff0c;使用dotenvy来读取和使用环境变量。 安装 cargo add dotenvy dotenv_codegen 加载环境变量 use dotenvy::dotenv;fn main() {dotenv().expect(".env不存在");…

公司让我开发一个管理系统,有了它,So easy!

目录 一、前言 二、低代码如何快速开发&#xff1f; 1.可视化开发 2.预构建的组件和模板 3.集成的开发和测试工具 4.跨平台兼容性 5.可伸缩性和可扩展性 三、前后端分离的开发框架 技术架构 一、前言 长期以来&#xff0c;常规软件开发是一项艰苦而详尽的工作。开发人员编写代表…

设计模式之适配器(Adapter)

Adapter Wapper 接口转换器 如果一个类不能直接访问另一个类的时候&#xff0c;中间加一个Adapter转换器就能访问了 常见例子: 电压转接头 java.io jdbc-odbc bridge(不是桥接模式) ASM Transformer java io里面的读文件操作: FileInputStream是字节流读文件&#xff0c;就像…

多线程JUC

创建线程的三种方法 runnable还需要通过currentthread来获取当前方法&#xff0c;才能使用Thread中的方法 第三种实现方法过程&#xff1a; 1.创建一个类MyCallable实现Callable接口 2.重写call (是有返回值的&#xff0c;表示多线程运行的结果) 3.创建MyCallable的对象 (表示多…