Updater update
This commit is contained in:
parent
3330006c3f
commit
26ac96bdda
@ -28,6 +28,6 @@ func main() {
|
|||||||
femaUpdater := updater.NewUpdater(cfg, binaryData)
|
femaUpdater := updater.NewUpdater(cfg, binaryData)
|
||||||
|
|
||||||
// Create and show updater window
|
// Create and show updater window
|
||||||
updaterWindow := ui.NewUpdaterWindow(myApp, cfg, femaUpdater.Update)
|
updaterWindow := ui.NewUpdaterWindow(myApp, cfg, femaUpdater)
|
||||||
updaterWindow.ShowAndRun()
|
updaterWindow.ShowAndRun()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"ip": "192.168.111.111",
|
"ip": "62.217.183.220",
|
||||||
"port": "22",
|
"port": "10000",
|
||||||
"login": "root",
|
"login": "root",
|
||||||
"password": "orangepi"
|
"password": "orangepi",
|
||||||
|
"downloadUrl": "https://s3.ru1.storage.beget.cloud/e4b29bca179c-sparkguard/build"
|
||||||
}
|
}
|
@ -1,10 +1,14 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/KRBL/FemaInstaller/internal/updater"
|
||||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/config"
|
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,32 +18,107 @@ type UpdaterWindow struct {
|
|||||||
ConfigDisplay *widget.Label
|
ConfigDisplay *widget.Label
|
||||||
UpdateButton *widget.Button
|
UpdateButton *widget.Button
|
||||||
StatusLabel *widget.Label
|
StatusLabel *widget.Label
|
||||||
|
ProgressBar *widget.ProgressBar
|
||||||
|
StageLabel *widget.Label
|
||||||
|
TimeRemainingLabel *widget.Label
|
||||||
|
UpdateMethodRadio *widget.RadioGroup
|
||||||
|
Updater *updater.Updater
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUpdaterWindow creates a new window for the updater application
|
// NewUpdaterWindow creates a new window for the updater application
|
||||||
func NewUpdaterWindow(app fyne.App, config *config.UpdaterConfig, updateHandler func() error) *UpdaterWindow {
|
func NewUpdaterWindow(app fyne.App, config *config.UpdaterConfig, femaUpdater *updater.Updater) *UpdaterWindow {
|
||||||
window := app.NewWindow("Обновление ПО Фема")
|
window := app.NewWindow("Обновление ПО Фема")
|
||||||
|
|
||||||
|
// Create update method radio
|
||||||
|
updateMethodRadio := widget.NewRadioGroup(
|
||||||
|
[]string{"Использовать встроенную версию", "Загрузить с сервера по URL"},
|
||||||
|
func(selected string) {
|
||||||
|
if selected == "Использовать встроенную версию" {
|
||||||
|
femaUpdater.SetUpdateMethod(updater.UpdateMethodEmbedded)
|
||||||
|
} else {
|
||||||
|
femaUpdater.SetUpdateMethod(updater.UpdateMethodDirectDownload)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// Default to embedded method
|
||||||
|
updateMethodRadio.SetSelected("Использовать встроенную версию")
|
||||||
|
|
||||||
// Create updater window
|
// Create updater window
|
||||||
updaterWindow := &UpdaterWindow{
|
updaterWindow := &UpdaterWindow{
|
||||||
Window: window,
|
Window: window,
|
||||||
ConfigDisplay: widget.NewLabel(config.String()),
|
ConfigDisplay: widget.NewLabel(config.String()),
|
||||||
StatusLabel: widget.NewLabel(""),
|
StatusLabel: widget.NewLabel(""),
|
||||||
|
ProgressBar: widget.NewProgressBar(),
|
||||||
|
StageLabel: widget.NewLabel(""),
|
||||||
|
TimeRemainingLabel: widget.NewLabel(""),
|
||||||
|
UpdateMethodRadio: updateMethodRadio,
|
||||||
|
Updater: femaUpdater,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide progress elements initially
|
||||||
|
updaterWindow.ProgressBar.Hide()
|
||||||
|
updaterWindow.StageLabel.Hide()
|
||||||
|
updaterWindow.TimeRemainingLabel.Hide()
|
||||||
|
|
||||||
// Create update button
|
// Create update button
|
||||||
updaterWindow.UpdateButton = widget.NewButton("Обновить ПО", func() {
|
updaterWindow.UpdateButton = widget.NewButton("Обновить ПО", func() {
|
||||||
updaterWindow.StatusLabel.SetText("Начало обновления...")
|
updaterWindow.StatusLabel.SetText("Начало обновления...")
|
||||||
updaterWindow.UpdateButton.Disable()
|
updaterWindow.UpdateButton.Disable()
|
||||||
|
updaterWindow.UpdateMethodRadio.Disable()
|
||||||
|
|
||||||
|
// Show progress elements
|
||||||
|
updaterWindow.ProgressBar.Show()
|
||||||
|
updaterWindow.StageLabel.Show()
|
||||||
|
updaterWindow.TimeRemainingLabel.Show()
|
||||||
|
|
||||||
|
// Reset progress
|
||||||
|
updaterWindow.ProgressBar.SetValue(0)
|
||||||
|
updaterWindow.StageLabel.SetText("Подготовка...")
|
||||||
|
updaterWindow.TimeRemainingLabel.SetText("")
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := updateHandler()
|
// Create progress callback
|
||||||
|
progressCallback := func(progress updater.ProgressInfo) {
|
||||||
|
// Update UI from the main thread
|
||||||
|
window.Canvas().Refresh(updaterWindow.ProgressBar)
|
||||||
|
|
||||||
|
updaterWindow.ProgressBar.SetValue(progress.Percentage / 100)
|
||||||
|
updaterWindow.StageLabel.SetText(progress.Stage)
|
||||||
|
|
||||||
|
// Format time remaining
|
||||||
|
if progress.EstimatedTimeRemaining > 0 {
|
||||||
|
minutes := int(progress.EstimatedTimeRemaining.Minutes())
|
||||||
|
seconds := int(progress.EstimatedTimeRemaining.Seconds()) % 60
|
||||||
|
|
||||||
|
if minutes > 0 {
|
||||||
|
updaterWindow.TimeRemainingLabel.SetText(
|
||||||
|
fmt.Sprintf("Осталось примерно: %d мин %d сек", minutes, seconds))
|
||||||
|
} else {
|
||||||
|
updaterWindow.TimeRemainingLabel.SetText(
|
||||||
|
fmt.Sprintf("Осталось примерно: %d сек", seconds))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updaterWindow.TimeRemainingLabel.SetText("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := femaUpdater.Update(progressCallback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
updaterWindow.StatusLabel.SetText("Ошибка обновления: " + err.Error())
|
updaterWindow.StatusLabel.SetText("Ошибка обновления: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
updaterWindow.StatusLabel.SetText("Обновление успешно завершено!")
|
updaterWindow.StatusLabel.SetText("Обновление успешно завершено!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait a moment to show 100% completion
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// Hide progress elements after completion
|
||||||
|
updaterWindow.ProgressBar.Hide()
|
||||||
|
updaterWindow.StageLabel.Hide()
|
||||||
|
updaterWindow.TimeRemainingLabel.Hide()
|
||||||
|
|
||||||
updaterWindow.UpdateButton.Enable()
|
updaterWindow.UpdateButton.Enable()
|
||||||
|
updaterWindow.UpdateMethodRadio.Enable()
|
||||||
}()
|
}()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -52,6 +131,17 @@ func NewUpdaterWindow(app fyne.App, config *config.UpdaterConfig, updateHandler
|
|||||||
configTitle := widget.NewLabel("Текущая конфигурация:")
|
configTitle := widget.NewLabel("Текущая конфигурация:")
|
||||||
configTitle.TextStyle = fyne.TextStyle{Bold: true}
|
configTitle.TextStyle = fyne.TextStyle{Bold: true}
|
||||||
|
|
||||||
|
// Create progress section
|
||||||
|
progressSection := container.NewVBox(
|
||||||
|
updaterWindow.StageLabel,
|
||||||
|
updaterWindow.ProgressBar,
|
||||||
|
updaterWindow.TimeRemainingLabel,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create update method section title
|
||||||
|
updateMethodTitle := widget.NewLabel("Метод обновления:")
|
||||||
|
updateMethodTitle.TextStyle = fyne.TextStyle{Bold: true}
|
||||||
|
|
||||||
// Create layout
|
// Create layout
|
||||||
content := container.NewVBox(
|
content := container.NewVBox(
|
||||||
title,
|
title,
|
||||||
@ -59,13 +149,17 @@ func NewUpdaterWindow(app fyne.App, config *config.UpdaterConfig, updateHandler
|
|||||||
configTitle,
|
configTitle,
|
||||||
updaterWindow.ConfigDisplay,
|
updaterWindow.ConfigDisplay,
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
|
updateMethodTitle,
|
||||||
|
updaterWindow.UpdateMethodRadio,
|
||||||
|
widget.NewSeparator(),
|
||||||
updaterWindow.UpdateButton,
|
updaterWindow.UpdateButton,
|
||||||
updaterWindow.StatusLabel,
|
updaterWindow.StatusLabel,
|
||||||
|
progressSection,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set window content and size
|
// Set window content and size
|
||||||
window.SetContent(content)
|
window.SetContent(content)
|
||||||
window.Resize(fyne.NewSize(400, 300))
|
window.Resize(fyne.NewSize(500, 400))
|
||||||
|
|
||||||
return updaterWindow
|
return updaterWindow
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,39 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.unprism.ru/KRBL/FemaInstaller/internal/ssh"
|
"gitea.unprism.ru/KRBL/FemaInstaller/internal/ssh"
|
||||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/config"
|
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/config"
|
||||||
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/fileutils"
|
"gitea.unprism.ru/KRBL/FemaInstaller/pkg/fileutils"
|
||||||
|
gossh "golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProgressInfo contains information about the update progress
|
||||||
|
type ProgressInfo struct {
|
||||||
|
Stage string
|
||||||
|
Percentage float64
|
||||||
|
EstimatedTimeRemaining time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProgressCallback is a function that reports update progress
|
||||||
|
type ProgressCallback func(progress ProgressInfo)
|
||||||
|
|
||||||
|
// UpdateMethod defines the method used for updating
|
||||||
|
type UpdateMethod int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UpdateMethodEmbedded uses the binary embedded in the program
|
||||||
|
UpdateMethodEmbedded UpdateMethod = iota
|
||||||
|
// UpdateMethodDirectDownload uses wget to download directly on the device
|
||||||
|
UpdateMethodDirectDownload
|
||||||
)
|
)
|
||||||
|
|
||||||
// Updater handles the software update process
|
// Updater handles the software update process
|
||||||
type Updater struct {
|
type Updater struct {
|
||||||
Config *config.UpdaterConfig
|
Config *config.UpdaterConfig
|
||||||
BinaryData []byte
|
BinaryData []byte
|
||||||
|
UpdateMethod UpdateMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUpdater creates a new updater with the provided configuration and binary data
|
// NewUpdater creates a new updater with the provided configuration and binary data
|
||||||
@ -21,17 +44,28 @@ func NewUpdater(config *config.UpdaterConfig, binaryData []byte) *Updater {
|
|||||||
return &Updater{
|
return &Updater{
|
||||||
Config: config,
|
Config: config,
|
||||||
BinaryData: binaryData,
|
BinaryData: binaryData,
|
||||||
|
UpdateMethod: UpdateMethodEmbedded, // Default to embedded method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetUpdateMethod sets the update method to use
|
||||||
|
func (u *Updater) SetUpdateMethod(method UpdateMethod) {
|
||||||
|
u.UpdateMethod = method
|
||||||
|
}
|
||||||
|
|
||||||
// Update performs the software update process
|
// Update performs the software update process
|
||||||
func (u *Updater) Update() error {
|
func (u *Updater) Update(progressCallback ProgressCallback) error {
|
||||||
// Save binary to temporary file
|
// If no callback is provided, use a no-op callback
|
||||||
tempFile := "update_binary"
|
if progressCallback == nil {
|
||||||
if err := os.WriteFile(tempFile, u.BinaryData, 0644); err != nil {
|
progressCallback = func(progress ProgressInfo) {}
|
||||||
return fmt.Errorf("не удалось сохранить временный файл: %w", err)
|
|
||||||
}
|
}
|
||||||
defer os.Remove(tempFile) // Clean up temporary file
|
|
||||||
|
// Report initial progress
|
||||||
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Подготовка",
|
||||||
|
Percentage: 0,
|
||||||
|
EstimatedTimeRemaining: 0,
|
||||||
|
})
|
||||||
|
|
||||||
// Create SSH client configuration
|
// Create SSH client configuration
|
||||||
clientConfig := ssh.NewClientConfig(
|
clientConfig := ssh.NewClientConfig(
|
||||||
@ -42,12 +76,44 @@ func (u *Updater) Update() error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Connect to SSH server
|
// Connect to SSH server
|
||||||
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Подключение к серверу",
|
||||||
|
Percentage: 10,
|
||||||
|
EstimatedTimeRemaining: 0,
|
||||||
|
})
|
||||||
|
|
||||||
sshClient, err := ssh.CreateSSHClient(clientConfig)
|
sshClient, err := ssh.CreateSSHClient(clientConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ошибка подключения SSH: %w", err)
|
return fmt.Errorf("ошибка подключения SSH: %w", err)
|
||||||
}
|
}
|
||||||
defer sshClient.Close()
|
defer sshClient.Close()
|
||||||
|
|
||||||
|
// Choose update method
|
||||||
|
switch u.UpdateMethod {
|
||||||
|
case UpdateMethodEmbedded:
|
||||||
|
return u.updateWithEmbeddedBinary(sshClient, clientConfig, progressCallback)
|
||||||
|
case UpdateMethodDirectDownload:
|
||||||
|
return u.updateWithDirectDownload(sshClient, progressCallback)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("неизвестный метод обновления")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateWithEmbeddedBinary updates using the binary embedded in the program
|
||||||
|
func (u *Updater) updateWithEmbeddedBinary(sshClient *gossh.Client, clientConfig *ssh.ClientConfig, progressCallback ProgressCallback) error {
|
||||||
|
// Save binary to temporary file
|
||||||
|
tempFile := "update_binary"
|
||||||
|
if err := os.WriteFile(tempFile, u.BinaryData, 0644); err != nil {
|
||||||
|
return fmt.Errorf("не удалось сохранить временный файл: %w", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tempFile) // Clean up temporary file
|
||||||
|
|
||||||
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Создание SFTP-соединения",
|
||||||
|
Percentage: 20,
|
||||||
|
EstimatedTimeRemaining: 0,
|
||||||
|
})
|
||||||
|
|
||||||
// Create SFTP client
|
// Create SFTP client
|
||||||
sftpClient, err := ssh.CreateSFTPClient(clientConfig)
|
sftpClient, err := ssh.CreateSFTPClient(clientConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -55,12 +121,70 @@ func (u *Updater) Update() error {
|
|||||||
}
|
}
|
||||||
defer sftpClient.Close()
|
defer sftpClient.Close()
|
||||||
|
|
||||||
// Upload binary
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Загрузка файла",
|
||||||
|
Percentage: 30,
|
||||||
|
EstimatedTimeRemaining: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Upload binary with progress reporting
|
||||||
remotePath := "/root/fema/build.new"
|
remotePath := "/root/fema/build.new"
|
||||||
if err = fileutils.UploadFile(sftpClient, tempFile, remotePath); err != nil {
|
|
||||||
|
// Create a file upload progress callback
|
||||||
|
uploadProgressCallback := func(percentage float64, estimatedTimeRemaining time.Duration) {
|
||||||
|
// Map the upload progress (0-100%) to the overall progress (30-80%)
|
||||||
|
overallPercentage := 30 + (percentage * 0.5)
|
||||||
|
|
||||||
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Загрузка файла",
|
||||||
|
Percentage: overallPercentage,
|
||||||
|
EstimatedTimeRemaining: estimatedTimeRemaining,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fileutils.UploadFileWithProgress(sftpClient, tempFile, remotePath, uploadProgressCallback); err != nil {
|
||||||
return fmt.Errorf("ошибка загрузки файла: %w", err)
|
return fmt.Errorf("ошибка загрузки файла: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return u.finalizeUpdate(sshClient, progressCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateWithDirectDownload updates by downloading directly on the device using wget
|
||||||
|
func (u *Updater) updateWithDirectDownload(sshClient *gossh.Client, progressCallback ProgressCallback) error {
|
||||||
|
if u.Config.DownloadURL == "" {
|
||||||
|
return fmt.Errorf("URL загрузки не указан в конфигурации")
|
||||||
|
}
|
||||||
|
|
||||||
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Загрузка файла на устройство",
|
||||||
|
Percentage: 30,
|
||||||
|
EstimatedTimeRemaining: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Download file directly on the device using wget
|
||||||
|
downloadCmd := fmt.Sprintf("wget -O /root/fema/build.new %s", u.Config.DownloadURL)
|
||||||
|
|
||||||
|
if err := ssh.ExecuteCommand(sshClient, downloadCmd); err != nil {
|
||||||
|
return fmt.Errorf("ошибка загрузки файла на устройство: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Загрузка файла на устройство",
|
||||||
|
Percentage: 80,
|
||||||
|
EstimatedTimeRemaining: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
return u.finalizeUpdate(sshClient, progressCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalizeUpdate applies the update by moving files and restarting the service
|
||||||
|
func (u *Updater) finalizeUpdate(sshClient *gossh.Client, progressCallback ProgressCallback) error {
|
||||||
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Применение обновления",
|
||||||
|
Percentage: 80,
|
||||||
|
EstimatedTimeRemaining: 0,
|
||||||
|
})
|
||||||
|
|
||||||
// Execute update commands
|
// Execute update commands
|
||||||
commands := []string{
|
commands := []string{
|
||||||
"mv -f /root/fema/build.new /root/fema/build",
|
"mv -f /root/fema/build.new /root/fema/build",
|
||||||
@ -68,12 +192,28 @@ func (u *Updater) Update() error {
|
|||||||
"systemctl restart fema.service",
|
"systemctl restart fema.service",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cmd := range commands {
|
for i, cmd := range commands {
|
||||||
|
// Calculate progress for each command (80-100%)
|
||||||
|
cmdProgress := 80 + float64(i+1)*20/float64(len(commands))
|
||||||
|
|
||||||
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Применение обновления",
|
||||||
|
Percentage: cmdProgress,
|
||||||
|
EstimatedTimeRemaining: 0,
|
||||||
|
})
|
||||||
|
|
||||||
if err := ssh.ExecuteCommand(sshClient, cmd); err != nil {
|
if err := ssh.ExecuteCommand(sshClient, cmd); err != nil {
|
||||||
return fmt.Errorf("ошибка выполнения команды '%s': %w", cmd, err)
|
return fmt.Errorf("ошибка выполнения команды '%s': %w", cmd, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Report completion
|
||||||
|
progressCallback(ProgressInfo{
|
||||||
|
Stage: "Завершено",
|
||||||
|
Percentage: 100,
|
||||||
|
EstimatedTimeRemaining: 0,
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ type UpdaterConfig struct {
|
|||||||
Port string `json:"port"`
|
Port string `json:"port"`
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
DownloadURL string `json:"downloadUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUpdaterConfig creates a new updater configuration with default values
|
// NewUpdaterConfig creates a new updater configuration with default values
|
||||||
@ -21,6 +22,7 @@ func NewUpdaterConfig() *UpdaterConfig {
|
|||||||
Port: "22",
|
Port: "22",
|
||||||
Login: "root",
|
Login: "root",
|
||||||
Password: "",
|
Password: "",
|
||||||
|
DownloadURL: "http://example.com/fema/build",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,5 +71,5 @@ func SaveUpdaterConfig(config *UpdaterConfig, filePath string) error {
|
|||||||
|
|
||||||
// String returns a string representation of the updater configuration
|
// String returns a string representation of the updater configuration
|
||||||
func (c *UpdaterConfig) String() string {
|
func (c *UpdaterConfig) String() string {
|
||||||
return fmt.Sprintf("IP: %s\nПорт: %s\nЛогин: %s\nПароль: %s", c.IP, c.Port, c.Login, c.Password)
|
return fmt.Sprintf("IP: %s\nПорт: %s\nЛогин: %s\nПароль: %s\nURL загрузки: %s", c.IP, c.Port, c.Login, c.Password, c.DownloadURL)
|
||||||
}
|
}
|
@ -4,12 +4,21 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ProgressCallback is a function that reports upload progress
|
||||||
|
type ProgressCallback func(percentage float64, estimatedTimeRemaining time.Duration)
|
||||||
|
|
||||||
// UploadFile uploads a local file to a remote server using SFTP
|
// UploadFile uploads a local file to a remote server using SFTP
|
||||||
func UploadFile(client *sftp.Client, localPath, remotePath string) error {
|
func UploadFile(client *sftp.Client, localPath, remotePath string) error {
|
||||||
|
return UploadFileWithProgress(client, localPath, remotePath, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadFileWithProgress uploads a local file to a remote server using SFTP and reports progress
|
||||||
|
func UploadFileWithProgress(client *sftp.Client, localPath, remotePath string, progressCallback ProgressCallback) error {
|
||||||
// Open the local file
|
// Open the local file
|
||||||
localFile, err := os.Open(localPath)
|
localFile, err := os.Open(localPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -17,6 +26,13 @@ func UploadFile(client *sftp.Client, localPath, remotePath string) error {
|
|||||||
}
|
}
|
||||||
defer localFile.Close()
|
defer localFile.Close()
|
||||||
|
|
||||||
|
// Get file size
|
||||||
|
fileInfo, err := localFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get file info: %w", err)
|
||||||
|
}
|
||||||
|
fileSize := fileInfo.Size()
|
||||||
|
|
||||||
// Create the remote file
|
// Create the remote file
|
||||||
remoteFile, err := client.Create(remotePath)
|
remoteFile, err := client.Create(remotePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -24,10 +40,54 @@ func UploadFile(client *sftp.Client, localPath, remotePath string) error {
|
|||||||
}
|
}
|
||||||
defer remoteFile.Close()
|
defer remoteFile.Close()
|
||||||
|
|
||||||
// Copy from the local file to the remote file
|
// If no progress callback is provided, just copy the file
|
||||||
|
if progressCallback == nil {
|
||||||
if _, err = io.Copy(remoteFile, localFile); err != nil {
|
if _, err = io.Copy(remoteFile, localFile); err != nil {
|
||||||
return fmt.Errorf("failed to upload file: %w", err)
|
return fmt.Errorf("failed to upload file: %w", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy with progress reporting
|
||||||
|
buffer := make([]byte, 32*1024) // 32KB buffer
|
||||||
|
var totalBytesWritten int64
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
bytesRead, readErr := localFile.Read(buffer)
|
||||||
|
if bytesRead > 0 {
|
||||||
|
bytesWritten, writeErr := remoteFile.Write(buffer[:bytesRead])
|
||||||
|
if writeErr != nil {
|
||||||
|
return fmt.Errorf("failed to write to remote file: %w", writeErr)
|
||||||
|
}
|
||||||
|
totalBytesWritten += int64(bytesWritten)
|
||||||
|
|
||||||
|
// Calculate progress percentage
|
||||||
|
percentage := float64(totalBytesWritten) / float64(fileSize) * 100
|
||||||
|
|
||||||
|
// Calculate estimated time remaining
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
|
var estimatedTimeRemaining time.Duration
|
||||||
|
if totalBytesWritten > 0 {
|
||||||
|
bytesPerSecond := float64(totalBytesWritten) / elapsed.Seconds()
|
||||||
|
if bytesPerSecond > 0 {
|
||||||
|
remainingBytes := fileSize - totalBytesWritten
|
||||||
|
estimatedSeconds := float64(remainingBytes) / bytesPerSecond
|
||||||
|
estimatedTimeRemaining = time.Duration(estimatedSeconds * float64(time.Second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report progress
|
||||||
|
progressCallback(percentage, estimatedTimeRemaining)
|
||||||
|
}
|
||||||
|
|
||||||
|
if readErr != nil {
|
||||||
|
if readErr == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to read from local file: %w", readErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user