Golang实现一个批量自动化执行树莓派指令的软件(3)下载

ops/2025/3/21 0:32:11/

简介

话接上篇 Golang实现一个批量自动化执行树莓派指令的软件(2)指令, 这次实现文件的下载。

环境描述

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

实现

接口定义

type IDownloader interface {/*Download 下载的同步接口, 会堵塞执行from : 下载的路径to   : 保存的路径*/Download(from, to string) error/*Download 下载的同步/异步接口from : 下载的路径to   : 保存的路径processCallback : 进度回调函数,每次下载文件的时候被调用, 返回当前下载进度信息from : 当前下载的文件路径to   : 当前文件保存路径downloadNumber : 下载的文件总数downloaded     : 已下载的文件数finishedCallback : 完成下载时调用background : 表示是同步执行还是异步执行*/DownloadWithCallback(from, to string,processCallback func(from, to string, downloadNumber, downloaded uint),finishedCallback func(err error), background bool) error
}

接口实现

package sshutilimport ("fmt""github.com/pkg/sftp""os""path""time"
)type downloader struct {client       *sftp.ClientdownloadSize uintdownNumber   uintdownloaded   uintstarted  boolcanceled chan struct{}
}func newDownloader(client *sftp.Client) (*downloader, error) {return &downloader{client: client, canceled: make(chan struct{})}, nil
}func (d *downloader) Download(from, to string) error {return d.download(from, to, nil, nil)
}func (d *downloader) DownloadWithCallback(from, to string,processCallback func(from, to string, downloadNumber, downloaded uint),finishedCallback func(err error), background bool) error {if !background {return d.download(from, to, processCallback, finishedCallback)} else {go d.download(from, to, processCallback, finishedCallback)}return nil
}func (d *downloader) Cancel() error {if d.started {select {case d.canceled <- struct{}{}:case <-time.After(time.Second * 2): // 取消时间过长,取消失败return fmt.Errorf("time out waiting for cancel")}}return nil
}func (d *downloader) Destroy() error {err := d.Cancel()close(d.canceled)return err
}func (d *downloader) downloadFolderCount(remotePath string) (needDownload, size uint, err error) {var c, s uintinfos, _ := d.client.ReadDir(remotePath)for _, info := range infos {if info.IsDir() {c, s, err = d.downloadFolderCount(path.Join(remotePath, info.Name()))if nil != err {return}needDownload += csize += scontinue}size += uint(info.Size())needDownload += 1}err = nilreturn
}func (d *downloader) downloadFileCount(remotePath string) (needDownload, size uint, err error) {info, err := d.client.Stat(remotePath)if nil != err {return 0, 0, err}if info.IsDir() {return d.downloadFolderCount(remotePath)}return 1, uint(info.Size()), nil
}func (d *downloader) download(remotePath, localPath string,processCallback func(from, to string, downloadNumber, downloaded uint),finishedCallback func(err error)) (err error) {whenErrorCall := func(e error) error {if nil != finishedCallback {go finishedCallback(e)}return e}d.started = truedefer func() {d.started = false}()d.downNumber, d.downloadSize, err = d.downloadFileCount(remotePath)if nil != err {return whenErrorCall(err)}err = os.MkdirAll(localPath, 0777)if nil != err {if !os.IsExist(err) {return whenErrorCall(err)}}info, err := d.client.Stat(remotePath)if nil != err {return whenErrorCall(err)}if info.IsDir() {return d.downloadFolder(remotePath, localPath, processCallback, finishedCallback)}return d.downloadFile(remotePath, localPath, processCallback, finishedCallback)
}func (d *downloader) downloadFile(remotePath, localPath string,processCallback func(from, to string, downloadNumber, downloaded uint),finishedCallback func(err error)) (err error) {var (srcFile       *sftp.FiledstFile       *os.Fileinfo          os.FileInfolocalFileName = path.Join(localPath, path.Base(remotePath)))whenErrorCall := func(e error) error {if nil != finishedCallback {go finishedCallback(e)}return e}info, err = d.client.Stat(remotePath)if nil != err {return whenErrorCall(err)}/*这里是解决在下载0KB的文件时,sftp.Open接口会一直堵塞, 所以判定0KB文件直接创建就好, 有兴趣这里可以进行简化*/if 0 >= info.Size() {dstFile, err = os.OpenFile(localFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)if nil != err {return whenErrorCall(err)}dstFile.Close()return whenErrorCall(err)}srcFile, err = d.client.Open(remotePath)if err != nil {return whenErrorCall(err)}defer srcFile.Close()dstFile, err = os.OpenFile(localFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)if err != nil {return whenErrorCall(err)}defer dstFile.Close()if _, err = srcFile.WriteTo(dstFile); err != nil {return whenErrorCall(err)}select {case <-d.canceled:return whenErrorCall(fmt.Errorf("user canceled"))default:}d.downloaded += 1if nil != processCallback {go processCallback(remotePath, localFileName, d.downNumber, d.downloaded)}return whenErrorCall(err)
}func (d *downloader) downloadFolder(remotePath, localPath string,processCallback func(from, to string, downloadNumber, downloaded uint),finishedCallback func(err error)) (err error) {whenErrorCall := func(e error) error {if nil != finishedCallback {go finishedCallback(e)}return e}err = os.MkdirAll(localPath, 0777)if nil != err {return whenErrorCall(err)}infos, err := d.client.ReadDir(remotePath)for _, info := range infos {remoteFilePath := path.Join(remotePath, info.Name())if info.IsDir() {localFilePath := path.Join(localPath, info.Name())err = d.downloadFolder(remoteFilePath, localFilePath, processCallback, nil)if nil != err {return whenErrorCall(err)}} else {err = d.downloadFile(remoteFilePath, localPath, processCallback, nil)if nil != err {return err}}}return whenErrorCall(err)
}

测试用例

package sshutilimport ("fmt""github.com/pkg/sftp""golang.org/x/crypto/ssh""sync""testing""time"
)type downloaderTest struct {sshClient  *ssh.ClientsftpClient *sftp.Clientdownloader *downloader
}func newDownloadTest() (*downloaderTest, error) {var (err   errordTest = &downloaderTest{})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.downloader, err = newDownloader(dTest.sftpClient)return dTest, err
}func (d *downloaderTest) destroy() {if nil != d.sftpClient {d.sftpClient.Close()d.sftpClient = nil}if nil != d.sshClient {d.sshClient.Close()d.sshClient = nil}
}func TestDownloader_Download(t *testing.T) {var dTest, err = newDownloadTest()if nil != err {fmt.Println("fail to new download test!")return}defer dTest.destroy()err = dTest.downloader.Download("/home/pi/", "./download")if nil != err {fmt.Println(err)}
}func TestDownloader_DownloadWithCallback(t *testing.T) {var dTest, err = newDownloadTest()if nil != err {fmt.Println("fail to new download test!")return}defer dTest.destroy()err = dTest.downloader.DownloadWithCallback("/home/pi/", "./download1", func(from, to string, downloadNumber, downloaded uint) {fmt.Println(from, to, downloadNumber, downloaded)}, func(err error) {fmt.Println("finished!!!")}, false)if nil != err {fmt.Println(err)}fmt.Println("sleping...")time.Sleep(time.Second * 1) // process 在download内部是异步调用, 所以这里延时使process内部的打印有时间执行完
}func TestDownloader_DownloadWithCallbackAsync(t *testing.T) {var waiter sync.WaitGroupvar dTest, err = newDownloadTest()if nil != err {fmt.Println("fail to new download test!")return}defer dTest.destroy()waiter.Add(1)err = dTest.downloader.DownloadWithCallback("/home/pi/", "./download2/", func(from, to string, downloadNumber, downloaded uint) {fmt.Println(from, to, downloadNumber, downloaded)}, func(err error) {fmt.Println("finished!!!")waiter.Done()}, true)if nil != err {fmt.Println(err)}fmt.Println("waiting....")waiter.Wait()fmt.Println("done!!!")time.Sleep(time.Second * 1) // process 在download内部是异步调用, 所以这里延时使process内部的打印有时间执行完
}

代码源

https://gitee.com/grayhsu/ssh_remote_access

其他

参考


http://www.ppmy.cn/ops/15025.html

相关文章

【 Vue 路由 跳转 路由守卫 】

Vue Router replace 编程式导航缓存路由组件 路由跳转的replace方法 作用:控制路由跳转时操作浏览器历史记录的模式浏览器的历史记录有两种写入方式&#xff1a;push 和 replacereplace是替换当前记录&#xff0c;路由跳转时候默认为push方式 replace 标签写法 &#xff1a; &…

【每日一题】2007. 从双倍数组中还原原数组-2024.4.18

题目&#xff1a; 2007. 从双倍数组中还原原数组 一个整数数组 original 可以转变成一个 双倍 数组 changed &#xff0c;转变方式为将 original 中每个元素 值乘以 2 加入数组中&#xff0c;然后将所有元素 随机打乱 。 给你一个数组 changed &#xff0c;如果 change 是 双…

Linux常用命令总结(四):文件权限及相关命令介绍

1. 文件属性信息解读 1. 文件类型和权限的表示 0首位表示类型。在Linux中第一个字符代表这个文件是目录、文件或链接文件 符号对应文件类型-代表文件dd 代表目录l链接文档(link file)&#xff1b; 1-3位确定属主&#xff08;该文件的所有者&#xff09;拥有该文件的权限。 4-6…

处理突发事件—中断

中断是计算机操作系统中用来处理突发事件的一种机制&#xff0c;它允许CPU在执行正常程序流程时暂时停下来&#xff0c;去处理更紧急的任务&#xff0c;处理完成后再恢复原来的任务。中断是现代计算机系统中不可或缺的组成部分&#xff0c;它提高了系统的效率和响应性。 中断的…

ABAP Visual Code 新建sap系统连接

本文主要介绍如何新建SAP前端系统链接 前提是你已经都扩展完了 1.点击SAP fiori--》点击新建 2.选择 abap on premise 3.输入如下信息 4.这里介绍下URL 如何获取 SMICM-->点击service 明细里面可以看到你的host name 和 port 当然你也可以随便找一个你的odata 服务看下ur…

数据库--Sqlite3

1、思维导图 2sqlite3在linux中是实现数据的增删&#xff0c;改 #include<myhead.h> int main(int argc, const char *argv[]) { //1、定义一个数据库句柄指针 sqlite3* ppDb NULL; //2、创建或打开数据库 if(sqlite3_open("./mydb…

yolov8缺陷检测改进步骤

yolov8改进步骤 1.看视频:parse 2.修改fitness()函数 位置&#xff1a;ultralytics/utils/metrics.py 检索fitness(self) def fitness(self):"""Model fitness as a weighted combination of metrics."""w [0.0, 1.0, 0.0, 0.0] # weights f…

PS常用的快捷键

一、文件操作类快捷键 操作WindowsmacOS新建文件CtrlNCommand N打开文件CtrlOCommand O保存文件CtrlSCommand S另存为ShiftCtrlSShiftCommand S关闭文件CtrlWCommand W 二、编辑类快捷键 操作WindowsmacOS撤销CtrlZCommand Z重做CtrlShiftZCommand ShiftZ剪切CtrlXComm…