Golang实现一个批量自动化执行树莓派指令的软件(4)上传

简介

话接上篇 Golang实现一个批量自动化执行树莓派指令的软件(3)下载
, 继续实现上传

环境描述

运行环境: Windows, 基于Golang, 暂时没有使用什么不可跨平台接口, 理论上支持Linux/MacOS
目标终端:树莓派DebianOS(主要做用它测试)

实现

接口定义

type IUploader interface {
	/*
		Upload 下载的同步接口, 会堵塞执行
			from : 上传的路径本地路径
			to   : 保存的远程路径
	*/
	Upload(from, to string) error
	/*
		UploadWithCallback 下载的同步/异步接口
			from : 上传的路径本地路径
			to   : 保存的远程路径
			processCallback : 进度回调函数,每次上传文件的时候被调用, 返回当前上传进度信息
					from : 当前上传的路径本地路径
					to   : 当前保存的远程路径
					num : 上传的文件总数
					uploaded     : 已上传的文件数
			finishedCallback : 完成上传时调用
			background : 表示是同步执行还是异步执行
	*/
	UploadWithCallback(from, to string,
		process func(from, to string, num, uploaded uint),
		finishedCallback func(err error), background bool) error
}

接口实现

package sshutil

import (
	"fmt"
	"github.com/pkg/sftp"
	"io"
	"os"
	"path"
	"time"
)

var oneTimeMaxSizeToWrite = 8192 // 单次最大写文件大小

func IsDir(path string) bool {
	info, err := os.Stat(path)
	if err != nil {
		return false
	}
	return info.IsDir()
}

func IsFile(path string) bool {
	info, err := os.Stat(path)
	if err != nil {
		return false
	}
	return !info.IsDir()
}

type uploader struct {
	client       *sftp.Client
	uploadSize   uint
	uploadNumber uint
	uploaded     uint

	started  bool
	canceled chan struct{}
}

func newUploader(client *sftp.Client) (*uploader, error) {
	return &uploader{client: client, canceled: make(chan struct{})}, nil
}

func (u *uploader) Upload(from, to string) error {
	return u.upload(from, to, nil, nil)
}

func (u *uploader) UploadWithCallback(from, to string,
	process func(from, to string, num, uploaded uint),
	finishedCallback func(err error), background bool) error {
	if !background {
		return u.upload(from, to, process, finishedCallback)
	} else {
		go u.upload(from, to, process, finishedCallback)
	}
	return nil
}

func (u *uploader) Cancel() error {
	if u.started {
		select {
		case u.canceled <- struct{}{}:
		case <-time.After(time.Second * 2): // 取消时间过长,取消失败
			return fmt.Errorf("time out waiting for cancel")
		}
	}
	return nil
}

func (u *uploader) Destroy() error {
	err := u.Cancel()
	close(u.canceled)
	return err
}

func (u *uploader) uploadFolderCount(localPath string) (needUpload, size uint, err error) {
	var (
		infos    []os.DirEntry
		fileInfo os.FileInfo
		c, s     uint
	)
	infos, err = os.ReadDir(localPath)

	for _, info := range infos {
		if info.IsDir() {
			c, s, err = u.uploadFolderCount(path.Join(localPath, info.Name()))
			if nil != err {
				return
			}
			needUpload += c
			size += s
			continue
		}
		needUpload += 1
		fileInfo, _ = info.Info()
		size += uint(fileInfo.Size())
	}
	err = nil
	return
}

func (u *uploader) uploadFileCount(localpath string) (uint, uint, error) {
	var (
		isExist bool
		isDir   bool
	)
	info, err := os.Stat(localpath)
	if err != nil {
		isExist = !os.IsNotExist(err)
		isDir = false
	} else {
		isExist = true
		isDir = info.IsDir()
	}

	if !isExist {
		return 0, 0, nil
	}
	if !isDir {
		return 1, uint(info.Size()), nil
	}

	return u.uploadFolderCount(localpath)
}

func (u *uploader) upload(localPath, remotePath string,
	process func(from, to string, num, uploaded uint),
	finishedCallback func(err error)) (err error) {

	whenErrorCall := func(e error) error {
		if nil != finishedCallback {
			go finishedCallback(e)
		}
		return e
	}

	u.started = true
	defer func() {
		u.started = false
	}()

	u.uploadNumber, u.uploadSize, err = u.uploadFileCount(localPath)
	if nil != err {
		return whenErrorCall(err)
	}

	var isDir = IsDir(localPath)
	if isDir {
		return u.uploadFolder(localPath, remotePath, process, finishedCallback)
	}

	return u.uploadFile(localPath, remotePath, process, finishedCallback)
}

func (u *uploader) writeFile(reader io.Reader, writer io.Writer) (err error) {
	var buffer = make([]byte, oneTimeMaxSizeToWrite)
	var n int
	for {
		n, err = reader.Read(buffer)
		if n < oneTimeMaxSizeToWrite {
			if io.EOF == err {
				err = nil
				if n > 0 {
					_, err = writer.Write(buffer[0:n])
					if err != nil {
						return err
					}
				}
				break
			}
		}
		_, err = writer.Write(buffer)
		if err != nil {
			return err
		}
	}
	return nil
}

func (u *uploader) uploadFile(localPath, remotePath string,
	process func(from, to string, num, uploaded uint),
	finishedCallback func(err error)) error {
	whenErrorCall := func(e error) error {
		if nil != finishedCallback {
			go finishedCallback(e)
		}
		return e
	}

	var (
		srcFile        *os.File
		dstFile        *sftp.File
		remoteFileName string
		err            error
	)
	srcFile, err = os.Open(localPath)
	if err != nil {
		return whenErrorCall(err)
	}
	defer srcFile.Close()

	remoteFileName = path.Join(remotePath, path.Base(localPath))
	dstFile, err = u.client.Create(remoteFileName)
	if err != nil {
		return whenErrorCall(err)
	}
	defer dstFile.Close()

	err = u.writeFile(srcFile, dstFile)
	if nil != err {
		return whenErrorCall(err)
	}

	u.uploaded += 1
	if nil != process {
		go process(localPath, remoteFileName, u.uploadNumber, u.uploaded)
	}
	return whenErrorCall(err)
}

func (u *uploader) uploadFolder(localPath, remotePath string,
	process func(from, to string, num, uploaded uint),
	finishedCallback func(err error)) (err error) {

	whenErrorCall := func(e error) error {
		if nil != finishedCallback {
			go finishedCallback(e)
		}
		return e
	}

	err = u.client.MkdirAll(remotePath)
	if nil != err {
		return whenErrorCall(err)
	}

	localFileInfos, err := os.ReadDir(localPath)
	if err != nil {
		return whenErrorCall(err)
	}

	for _, fileInfo := range localFileInfos {
		localFilePath := path.Join(localPath, fileInfo.Name())

		select {
		case <-u.canceled:
			return whenErrorCall(fmt.Errorf("user canceled"))
		default:
		}

		if fileInfo.IsDir() {
			remoteFilePath := path.Join(remotePath, fileInfo.Name())

			err = u.uploadFolder(localFilePath, remoteFilePath, process, nil)
			if nil != err {
				return whenErrorCall(err)
			}
		} else {
			err = u.uploadFile(localFilePath, remotePath, process, nil)
			if nil != err {
				return whenErrorCall(err)
			}
		}
	}

	return whenErrorCall(err)
}

测试用例

package sshutil

import (
	"fmt"
	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
	"sync"
	"testing"
	"time"
)

type uploaderTest struct {
	sshClient  *ssh.Client
	sftpClient *sftp.Client

	uploader *uploader
}

func newUploaderTest() (*uploaderTest, error) {
	var (
		err   error
		dTest = &uploaderTest{}
	)
	config := ssh.ClientConfig{
		User:            "pi",                                      // 用户名
		Auth:            []ssh.AuthMethod{ssh.Password("a123456")}, // 密码
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Timeout:         10 * time.Second,
	}
	dTest.sshClient, err = ssh.Dial("tcp", "192.168.3.2:22", &config) //IP + 端口
	if err != nil {
		fmt.Print(err)
		return nil, err
	}
	if dTest.sftpClient, err = sftp.NewClient(dTest.sshClient); err != nil {
		dTest.destroy()
		return nil, err
	}

	dTest.uploader, err = newUploader(dTest.sftpClient)

	return dTest, err
}

func (d *uploaderTest) destroy() {
	if nil != d.sftpClient {
		d.sftpClient.Close()
		d.sftpClient = nil
	}

	if nil != d.sshClient {
		d.sshClient.Close()
		d.sshClient = nil
	}
}

func TestUploader_Upload(t *testing.T) {
	var dTest, err = newUploaderTest()
	if nil != err {
		fmt.Println("fail to new uploader test!")
		return
	}
	defer dTest.destroy()

	err = dTest.uploader.Upload("./download", "/home/pi/upload/")
	if nil != err {
		fmt.Println(err)
	}
}

func TestUploader_UploadWithCallback(t *testing.T) {
	var dTest, err = newUploaderTest()
	if nil != err {
		fmt.Println("fail to new uploader test!")
		return
	}
	defer dTest.destroy()

	err = dTest.uploader.UploadWithCallback("./download", "/home/pi/upload1/", func(from, to string, num, uploaded uint) {
		fmt.Println(from, to, num, uploaded)
	}, func(err error) {
		fmt.Println("finished!!!")
	}, false)
	if nil != err {
		fmt.Println(err)
	}
	time.Sleep(time.Second)
}

func TestUploader_UploadWithCallbackAsync(t *testing.T) {
	var waiter sync.WaitGroup
	var dTest, err = newUploaderTest()
	if nil != err {
		fmt.Println("fail to new uploader test!")
		return
	}
	defer dTest.destroy()
	waiter.Add(1)
	err = dTest.uploader.UploadWithCallback("./download", "/home/pi/upload2/", func(from, to string, num, uploaded uint) {
		fmt.Println(from, to, num, uploaded)
	}, func(err error) {
		waiter.Done()
		fmt.Println("finished!!!")
	}, true)
	if nil != err {
		fmt.Println(err)
	}
	fmt.Println("waiting finish...")
	waiter.Wait()
	fmt.Println("had finished!")
	time.Sleep(time.Second)
}

代码源

https://gitee.com/grayhsu/ssh_remote_access

其他

参考

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

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

相关文章

多传感器时间同步详解

多传感器时间同步 每个传感器都有自己的时钟源不同传感器具有不同的采样频率另外数据传输、camera曝光等都会产生不可控的延迟 为了有效融合多个传感器的感知数据&#xff0c;必须进行时间同步 在自动驾驶中&#xff0c;需要用到很多传感器的数据&#xff08;Lidar&#xff0…

实时通讯技术 WebRTC 介绍

WebRTC WebRTC&#xff08;Web Real-Time Communication&#xff09;是一个支持网页浏览器进行实时语音对话或视频对话的技术。 历史 2010年5月&#xff0c;Google以6820万美元收购VoIP软件开发商Global IP Solutions的GIPS引擎&#xff0c;并改为名为“WebRTC”。WebRTC使用…

震惊!某省图书馆竟然可以注册后直接访问知网并下载文章?

四川省图书馆 使用说明 1.点击进入https://portal.sclib.org/interlibSSO/main/main.jsp 显示如下&#xff1a; 2.关注四川省图书馆公众号并注册 3.点击馆外登录并使用刚注册的用户名密码登录 显示如下&#xff1a; 4.登录成功后跳转至首页并点击cnki即可正常使用

Elasticsearch概念 使用docker安装Elasticsearch和kibana

目录 一、Elasticsearch概念 倒排索引和正向索引 正向和倒排 二、ES安装 三、安装 kibana 四、IK分词器 下载ES中文分词器 扩展或停用词条 一、Elasticsearch概念 倒排索引和正向索引 正向索引 就像在mysql数据中搜索非主键字段的内容&#xff0c;就需要逐条数据的去查…

数组和指针经典笔试题讲解下

目录 创作不易&#xff0c;如对您帮助&#xff0c;还望一键三连&#xff0c;谢谢&#xff01;&#xff01;&#xff01; 题目一&#xff1a; 题目二&#xff1a; 题目三&#xff1a; 题目四&#xff1a; 题目五&#xff1a; 题目六&#xff1a; 题目七&#xff1a; 创作…

python中如何用matplotlib写柱状图

#代码 import matplotlib.pyplot as plt import numpy as npspecies ("Adelie", "Chinstrap", "Gentoo") penguin_means {Bill Depth: (18.35, 18.43, 14.98),Bill Length: (38.79, 48.83, 47.50),Flipper Length: (189.95, 195.82, 217.19),…

智能外呼文书送达系统,智慧检务解决方案

在全民数字化改革中&#xff0c;司法体制改革不断推进的大背景下&#xff0c;合肥高新技术产业开发区人民检察院的内设机构改革已完成落地&#xff0c;刑事案件审查办理迎来了重大改变&#xff0c;需要检察官对现有办案方式方法做出相应的调整&#xff0c;将主要精力从大量的重…

【AIGC调研系列】Sora级别的国产视频大模型-Vidu

Vidu能够达到Sora级别的标准。Vidu被多个来源认为是国内首个Sora级别的视频大模型[2][3][4]。它采用了团队原创的Diffusion与Transformer融合的架构U-ViT&#xff0c;能够生成长达16秒、分辨率高达1080P的高清视频内容[1][6]。此外&#xff0c;Vidu的一致性、运动幅度都达到了S…

无人机+集群组网+单兵图传:空地一体化组网技术详解

空地一体化组网技术是一种结合了无人机、集群自组网和单兵图传等多种技术的先进通信解决方案。这种技术方案的主要目的是在前线事故现场和后方指挥中心之间建立一个高效、稳定的通信链路&#xff0c;以确保信息的实时传输和指挥的顺畅进行。 首先&#xff0c;前端视频采集部分&…

面试经典150题——求根节点到叶节点数字之和

​ 1. 题目描述 2. 题目分析与解析 2.1 思路一——DFS 理解问题&#xff1a; 首先要理解题目的要求&#xff0c;即对于给定的二叉树&#xff0c;我们需要找出从根节点到所有叶子节点的所有路径&#xff0c;然后将每一条路径上的数字组成一个整数&#xff0c;最后求出这些整数…

JSP在页面用<%=调用声明函数时出现HTTP 500错误

JSP在页面用<%调用声明函数时出现HTTP 500错误 错误描述&#xff1a; Eclipse在编写JSP页面时&#xff0c;在其中采用<%&#xff01;%>方式声明了函数&#xff0c;然后在页面中用<%函数名%>方式调用时&#xff0c;出现HTTP状态500错误&#xff0c;提示为&#…

github Copilot的使用总结

1. 代码建议和补全 GitHub Copilot 的基本使用涉及编写代码时的实时代码建议和补全。一旦你已经安装并配置好 GitHub Copilot 插件&#xff0c;你可以在支持的编辑器&#xff08;如 Visual Studio Code&#xff09;中开始使用 Copilot。以下是一些基本的使用步骤&#xff1a; …

《苍穹外卖》Day10部分知识点记录

一、Spring Task 介绍 Spring Task是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 定位&#xff1a;定时任务框架 作用&#xff1a;定时自动执行某段Java代码 应用场景&#xff1a;只要是需要定时处理的场景都可以使用Spring Task …

飞书API(6):使用 pandas 处理数据并写入 MySQL 数据库

一、引入 上一篇了解了飞书 28 种数据类型通过接口读取到的数据结构&#xff0c;本文开始探讨如何将这些数据写入 MySQL 数据库。这个工作流的起点是从 API 获取到的一个完整的数据&#xff0c;终点是写入 MySQL 数据表&#xff0c;表结构和维格表结构类似。在过程中可以有不同…

重生奇迹mu装备掉落大全

1、骷髅兵&#xff1a; [一般宝]毒戒指(3%HP)石巨人召唤石玛雅雷之项链(1%)。 2、独眼巨人&#xff1a;4冰之戒指(2%)3雷之项链(2%)3毒之戒指天使3毒戒(3%回复)灵魂祝福石巨人石玛雅钻云枪石。 3、幽灵&#xff1a;3雷链(hp3%)守护天使小恶魔&#xff0c;灵魂宝石祝福4冰戒回3…

AI赋能分层模式,解构未来,智领风潮

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;AI赋能分…

【探索Java编程:从入门到入狱】Day3

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

Redis分布式锁 - 基于Jedis和LUA的分布式锁

先基于单机模式&#xff0c;基于Jedis手工造轮子实现自己的分布式锁。 首先看两个命令&#xff1a; Redis 分布式锁机制&#xff0c;主要借助 setnx 和 expire 两个命令完成。 setnx命令: setnx 是 set if not exists 的简写。将 key 的值设为 value &#xff0c;当且仅当…

跨设备自动化协同提效新利器!边缘自动化流程编排工具

痛点剖析 随着企业生产环境的日益复杂化&#xff0c;不同生产设备间的协调性问题尤为凸显。 1、不同设备往往基于各自的技术标准、通信协议和操作系统设计&#xff0c;这使得它们之间的数据交换和指令传递存在显著的障碍。 2、技术上的不兼容性导致设备间难以实现无缝对接和…

Matplotlib是什么?

一、Matplotlib是什么&#xff1f; Matplotlib是一个Python语言的2D绘图库&#xff0c;它非常广泛地用于数据的可视化。以下是一些主要特点&#xff1a; 多功能性&#xff1a;它允许用户创建各种静态、动态或交互式的图表&#xff0c;如线图、散点图、直方图等。跨平台性&…
最新文章