[WIP] updater rafactoring, minor improvments

This commit is contained in:
Vladimir Stoilov
2024-09-17 12:36:44 +03:00
parent 83ec18f552
commit 84a15b5fb3
18 changed files with 380 additions and 310 deletions

View File

@@ -3,6 +3,8 @@ package service
import (
"context"
"fmt"
"os"
"path/filepath"
go_runtime "runtime"
"sync/atomic"
"time"
@@ -35,7 +37,6 @@ import (
"github.com/safing/portmaster/service/sync"
"github.com/safing/portmaster/service/ui"
"github.com/safing/portmaster/service/updates"
"github.com/safing/portmaster/service/updates/registry"
"github.com/safing/portmaster/spn/access"
"github.com/safing/portmaster/spn/cabin"
"github.com/safing/portmaster/spn/captain"
@@ -103,31 +104,54 @@ type Instance struct {
CommandLineOperation func() error
}
func getCurrentBinaryFolder() (string, error) {
// Get the path of the currently running executable
exePath, err := os.Executable()
if err != nil {
return "", fmt.Errorf("failed to get executable path: %w", err)
}
// Get the absolute path
absPath, err := filepath.Abs(exePath)
if err != nil {
return "", fmt.Errorf("failed to get absolute path: %w", err)
}
// Get the directory of the executable
installDir := filepath.Dir(absPath)
return installDir, nil
}
// New returns a new Portmaster service instance.
func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
var binaryUpdateIndex registry.UpdateIndex
var intelUpdateIndex registry.UpdateIndex
var binaryUpdateIndex updates.UpdateIndex
var intelUpdateIndex updates.UpdateIndex
if go_runtime.GOOS == "windows" {
binaryUpdateIndex = registry.UpdateIndex{
Directory: "C:/Program Files/Portmaster/binary",
DownloadDirectory: "C:/Program Files/Portmaster/new_binary",
PurgeDirectory: "C:/Program Files/Portmaster/old_binary",
binaryFolder, err := getCurrentBinaryFolder()
if err != nil {
return nil, err
}
binaryUpdateIndex = updates.UpdateIndex{
Directory: binaryFolder, // Default: C:/Program Files/Portmaster/binary
DownloadDirectory: os.ExpandEnv("%ProgramData%/Portmaster/new_binary"),
PurgeDirectory: os.ExpandEnv("%ProgramData%/Portmaster/old_binary"),
Ignore: []string{"databases", "intel", "config.json"},
IndexURLs: []string{"http://192.168.88.11:8000/test-binary.json"},
IndexFile: "bin-index.json",
AutoApply: false,
}
intelUpdateIndex = registry.UpdateIndex{
Directory: "C:/Program Files/Portmaster/intel",
DownloadDirectory: "C:/Program Files/Portmaster/new_intel",
PurgeDirectory: "C:/Program Files/Portmaster/old_intel",
intelUpdateIndex = updates.UpdateIndex{
Directory: os.ExpandEnv("%ProgramData%/Portmaster/intel"),
DownloadDirectory: os.ExpandEnv("%ProgramData%/Portmaster/new_intel"),
PurgeDirectory: os.ExpandEnv("%ProgramData%/Portmaster/old_intel"),
IndexURLs: []string{"http://192.168.88.11:8000/test-intel.json"},
IndexFile: "intel-index.json",
AutoApply: true,
}
} else if go_runtime.GOOS == "linux" {
binaryUpdateIndex = registry.UpdateIndex{
binaryUpdateIndex = updates.UpdateIndex{
Directory: "/usr/lib/portmaster",
DownloadDirectory: "/var/lib/portmaster/new_bin",
PurgeDirectory: "/var/lib/portmaster/old_bin",
@@ -137,7 +161,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
AutoApply: false,
}
intelUpdateIndex = registry.UpdateIndex{
intelUpdateIndex = updates.UpdateIndex{
Directory: "/var/lib/portmaster/intel",
DownloadDirectory: "/var/lib/portmaster/new_intel",
PurgeDirectory: "/var/lib/portmaster/intel_bin",

View File

@@ -14,7 +14,7 @@ import (
"github.com/safing/portmaster/base/database"
"github.com/safing/portmaster/base/database/record"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/service/updates/registry"
"github.com/safing/portmaster/service/updates"
)
const (
@@ -39,9 +39,9 @@ var (
filterListLock sync.RWMutex
// Updater files for tracking upgrades.
baseFile *registry.File
intermediateFile *registry.File
urgentFile *registry.File
baseFile *updates.File
intermediateFile *updates.File
urgentFile *updates.File
filterListsLoaded chan struct{}
)
@@ -77,7 +77,7 @@ func isLoaded() bool {
// processListFile opens the latest version of file and decodes it's DSDL
// content. It calls processEntry for each decoded filterlists entry.
func processListFile(ctx context.Context, filter *scopedBloom, file *registry.File) error {
func processListFile(ctx context.Context, filter *scopedBloom, file *updates.File) error {
f, err := os.Open(file.Path())
if err != nil {
return err

View File

@@ -9,7 +9,7 @@ import (
"github.com/safing/portmaster/base/database"
"github.com/safing/portmaster/base/database/record"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/service/updates/registry"
"github.com/safing/portmaster/service/updates"
"github.com/safing/structures/dsd"
)
@@ -162,7 +162,7 @@ func getListIndexFromCache() (*ListIndexFile, error) {
var (
// listIndexUpdate must only be used by updateListIndex.
listIndexUpdate *registry.File
listIndexUpdate *updates.File
listIndexUpdateLock sync.Mutex
)

View File

@@ -14,7 +14,7 @@ import (
"github.com/safing/portmaster/base/database/query"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/portmaster/service/updates/registry"
"github.com/safing/portmaster/service/updates"
)
var updateInProgress = abool.New()
@@ -174,8 +174,8 @@ func removeAllObsoleteFilterEntries(wc *mgr.WorkerCtx) error {
// getUpgradableFiles returns a slice of filterlists files
// that should be updated. The files MUST be updated and
// processed in the returned order!
func getUpgradableFiles() ([]*registry.File, error) {
var updateOrder []*registry.File
func getUpgradableFiles() ([]*updates.File, error) {
var updateOrder []*updates.File
// cacheDBInUse := isLoaded()
@@ -218,7 +218,7 @@ func getUpgradableFiles() ([]*registry.File, error) {
return resolveUpdateOrder(updateOrder)
}
func resolveUpdateOrder(updateOrder []*registry.File) ([]*registry.File, error) {
func resolveUpdateOrder(updateOrder []*updates.File) ([]*updates.File, error) {
// sort the update order by ascending version
sort.Sort(byAscVersion(updateOrder))
log.Tracef("intel/filterlists: order of updates: %v", updateOrder)
@@ -258,7 +258,7 @@ func resolveUpdateOrder(updateOrder []*registry.File) ([]*registry.File, error)
return updateOrder[startAtIdx:], nil
}
type byAscVersion []*registry.File
type byAscVersion []*updates.File
func (fs byAscVersion) Len() int { return len(fs) }

View File

@@ -9,7 +9,7 @@ import (
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/portmaster/service/updates/registry"
"github.com/safing/portmaster/service/updates"
)
var worker *updateWorker
@@ -27,7 +27,7 @@ const (
type geoIPDB struct {
*maxminddb.Reader
file *registry.File
file *updates.File
}
// updateBroadcaster stores a geoIPDB and provides synchronized
@@ -197,7 +197,7 @@ func getGeoIPDB(resource string) (*geoIPDB, error) {
}, nil
}
func open(resource string) (*registry.File, error) {
func open(resource string) (*updates.File, error) {
f, err := module.instance.IntelUpdates().GetFile(resource)
if err != nil {
return nil, fmt.Errorf("getting file: %w", err)

View File

@@ -16,7 +16,7 @@ import (
"github.com/safing/portmaster/base/api"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/base/utils"
"github.com/safing/portmaster/service/updates/registry"
"github.com/safing/portmaster/service/updates"
)
var (
@@ -93,7 +93,7 @@ func (bs *archiveServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// get file from update system
zipFile, err := module.instance.BinaryUpdates().GetFile(fmt.Sprintf("%s.zip", moduleName))
if err != nil {
if errors.Is(err, registry.ErrNotFound) {
if errors.Is(err, updates.ErrNotFound) {
log.Tracef("ui: requested module %s does not exist", moduleName)
http.Error(w, err.Error(), http.StatusNotFound)
} else {

View File

@@ -1,4 +1,4 @@
package registry
package updates
import (
"archive/zip"
@@ -6,6 +6,7 @@ import (
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
@@ -20,7 +21,7 @@ import (
const MaxUnpackSize = 1 << 30 // 2^30 == 1GB
const current_platform = runtime.GOOS + "_" + runtime.GOARCH
const currentPlatform = runtime.GOOS + "_" + runtime.GOARCH
type Artifact struct {
Filename string `json:"Filename"`
@@ -38,7 +39,73 @@ type Bundle struct {
Artifacts []Artifact `json:"Artifacts"`
}
func (bundle Bundle) downloadAndVerify(dir string) {
func ParseBundle(dir string, indexFile string) (*Bundle, error) {
filepath := fmt.Sprintf("%s/%s", dir, indexFile)
// Check if the file exists.
file, err := os.Open(filepath)
if err != nil {
return nil, fmt.Errorf("failed to open index file: %w", err)
}
defer func() { _ = file.Close() }()
// Read
content, err := io.ReadAll(file)
if err != nil {
return nil, err
}
// Parse
var bundle Bundle
err = json.Unmarshal(content, &bundle)
if err != nil {
return nil, err
}
return &bundle, nil
}
// CopyMatchingFilesFromCurrent check if there the current bundle files has matching files with the new bundle and copies them if they match.
func (bundle Bundle) CopyMatchingFilesFromCurrent(current Bundle, currentDir, newDir string) error {
for _, currentArtifact := range current.Artifacts {
new:
for _, newArtifact := range bundle.Artifacts {
if currentArtifact.Filename == newArtifact.Filename {
if currentArtifact.SHA256 == newArtifact.SHA256 {
// Files match, make sure new dir exists
_ = os.MkdirAll(newDir, defaultDirMode)
// Open the current file
sourceFilePath := fmt.Sprintf("%s/%s", currentDir, newArtifact.Filename)
sourceFile, err := os.Open(sourceFilePath)
if err != nil {
return fmt.Errorf("failed to open %s file: %w", sourceFilePath, err)
}
defer sourceFile.Close()
// Create new file
destFilePath := fmt.Sprintf("%s/%s", newDir, newArtifact.Filename)
destFile, err := os.Create(destFilePath)
if err != nil {
return fmt.Errorf("failed to create %s file: %w", destFilePath, err)
}
defer destFile.Close()
// Copy
_, err = io.Copy(destFile, sourceFile)
if err != nil {
return fmt.Errorf("failed to copy contents: %w", err)
}
// Flush
_ = destFile.Sync()
}
break new
}
}
}
return nil
}
func (bundle Bundle) DownloadAndVerify(dir string) {
client := http.Client{}
for _, artifact := range bundle.Artifacts {
@@ -111,7 +178,7 @@ func checkIfFileIsValid(filename string, artifact Artifact) (bool, error) {
func processArtifact(client *http.Client, artifact Artifact, filePath string) error {
// Skip artifacts not meant for this machine.
if artifact.Platform != "" && artifact.Platform != current_platform {
if artifact.Platform != "" && artifact.Platform != currentPlatform {
return nil
}

View File

@@ -1,4 +1,4 @@
package registry
package updates
import (
"fmt"
@@ -19,7 +19,7 @@ type UpdateIndex struct {
AutoApply bool
}
func (ui *UpdateIndex) downloadIndexFile() (err error) {
func (ui *UpdateIndex) DownloadIndexFile() (err error) {
_ = os.MkdirAll(ui.DownloadDirectory, defaultDirMode)
for _, url := range ui.IndexURLs {
err = ui.downloadIndexFileFromURL(url)

View File

@@ -1,23 +1,38 @@
package updates
import (
"flag"
"errors"
"fmt"
"os"
"time"
semver "github.com/hashicorp/go-version"
"github.com/safing/portmaster/base/api"
"github.com/safing/portmaster/base/config"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/base/notifications"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/portmaster/service/updates/registry"
)
var autoUpdate bool
func init() {
flag.BoolVar(&autoUpdate, "auto-update", false, "auto apply downloaded updates")
type File struct {
id string
path string
}
func (f *File) Identifier() string {
return f.id
}
func (f *File) Path() string {
return f.path
}
func (f *File) Version() string {
return ""
}
var ErrNotFound error = errors.New("file not found")
// Updates provides access to released artifacts.
type Updates struct {
m *mgr.Manager
@@ -29,13 +44,18 @@ type Updates struct {
EventResourcesUpdated *mgr.EventMgr[struct{}]
EventVersionsUpdated *mgr.EventMgr[struct{}]
registry registry.Registry
updateIndex UpdateIndex
bundle *Bundle
updateBundle *Bundle
files map[string]File
instance instance
}
// New returns a new Updates module.
func New(instance instance, name string, index registry.UpdateIndex) (*Updates, error) {
func New(instance instance, name string, index UpdateIndex) (*Updates, error) {
m := mgr.New(name)
module := &Updates{
m: m,
@@ -44,6 +64,8 @@ func New(instance instance, name string, index registry.UpdateIndex) (*Updates,
EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m),
EventVersionsUpdated: mgr.NewEventMgr[struct{}](VersionUpdateEvent, m),
updateIndex: index,
instance: instance,
}
@@ -51,39 +73,87 @@ func New(instance instance, name string, index registry.UpdateIndex) (*Updates,
module.updateCheckWorkerMgr = m.NewWorkerMgr("update checker", module.checkForUpdates, nil)
module.updateCheckWorkerMgr.Repeat(30 * time.Second)
module.upgraderWorkerMgr = m.NewWorkerMgr("upgrader", func(w *mgr.WorkerCtx) error {
err := module.registry.ApplyUpdates()
err := applyUpdates(module.updateIndex, *module.updateBundle)
if err != nil {
// TODO(vladimir): Send notification to UI
log.Errorf("updates: failed to apply updates: %s", err)
} else {
// TODO(vladimir): Prompt user to restart?
module.instance.Restart()
}
return nil
}, nil)
module.registry = registry.New(index)
_ = module.registry.Initialize()
var err error
module.bundle, err = ParseBundle(module.updateIndex.Directory, module.updateIndex.IndexFile)
if err != nil {
return nil, fmt.Errorf("failed to parse binary bundle: %s", err)
}
// Add bundle artifacts to registry.
module.processBundle(module.bundle)
// Remove old files
m.Go("old files cleaner", func(ctx *mgr.WorkerCtx) error {
err := os.RemoveAll(module.updateIndex.PurgeDirectory)
if err != nil {
return fmt.Errorf("failed to delete folder: %w", err)
}
return nil
})
return module, nil
}
func (reg *Updates) processBundle(bundle *Bundle) {
for _, artifact := range bundle.Artifacts {
artifactPath := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, artifact.Filename)
reg.files[artifact.Filename] = File{id: artifact.Filename, path: artifactPath}
}
}
func (u *Updates) checkForUpdates(_ *mgr.WorkerCtx) error {
hasUpdates, err := u.registry.CheckForUpdates()
err := u.updateIndex.DownloadIndexFile()
if err != nil {
log.Errorf("updates: failed to check for updates: %s", err)
return fmt.Errorf("failed to download index file: %s", err)
}
if hasUpdates {
log.Infof("updates: there is updates available")
err = u.registry.DownloadUpdates()
if err != nil {
log.Errorf("updates: failed to download bundle: %s", err)
} else if autoUpdate {
u.ApplyUpdates()
}
} else {
log.Infof("updates: no new updates")
u.EventResourcesUpdated.Submit(struct{}{})
u.updateBundle, err = ParseBundle(u.updateIndex.DownloadDirectory, u.updateIndex.IndexFile)
if err != nil {
return fmt.Errorf("failed parse bundle: %s", err)
}
defer u.EventResourcesUpdated.Submit(struct{}{})
// Compare current and downloaded index version.
currentVersion, err := semver.NewVersion(u.bundle.Version)
downloadVersion, err := semver.NewVersion(u.updateBundle.Version)
if currentVersion.Compare(downloadVersion) <= 0 {
// no updates
log.Info("updates: check complete: no new updates")
return nil
}
log.Infof("updates: check complete: downloading new version: %s %s", u.updateBundle.Name, u.updateBundle.Version)
err = u.DownloadUpdates()
if err != nil {
log.Errorf("updates: failed to download bundle: %s", err)
} else if u.updateIndex.AutoApply {
u.ApplyUpdates()
}
return nil
}
// DownloadUpdates downloads available binary updates.
func (u *Updates) DownloadUpdates() error {
if u.updateBundle == nil {
// CheckForBinaryUpdates needs to be called before this.
return fmt.Errorf("no valid update bundle found")
}
_ = deleteUnfinishedDownloads(u.updateIndex.DownloadDirectory)
err := u.updateBundle.CopyMatchingFilesFromCurrent(*u.bundle, u.updateIndex.Directory, u.updateIndex.DownloadDirectory)
if err != nil {
log.Warningf("updates: error while coping file from current to update: %s", err)
}
u.updateBundle.DownloadAndVerify(u.updateIndex.DownloadDirectory)
return nil
}
@@ -107,8 +177,17 @@ func (u *Updates) Start() error {
return nil
}
func (u *Updates) GetFile(id string) (*registry.File, error) {
return u.registry.GetFile(id)
func (u *Updates) GetFile(id string) (*File, error) {
file, ok := u.files[id]
if ok {
return &file, nil
} else {
log.Errorf("updates: requested file id not found: %s", id)
for _, file := range u.files {
log.Debugf("File: %s", file)
}
return nil, ErrNotFound
}
}
// Stop stops the module.

View File

@@ -1,210 +0,0 @@
package registry
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/safing/portmaster/base/log"
)
var ErrNotFound error = errors.New("file not found")
const (
defaultFileMode = os.FileMode(0o0644)
executableFileMode = os.FileMode(0o0744)
defaultDirMode = os.FileMode(0o0755)
)
type File struct {
id string
path string
}
func (f *File) Identifier() string {
return f.id
}
func (f *File) Path() string {
return f.path
}
func (f *File) Version() string {
return ""
}
type Registry struct {
updateIndex UpdateIndex
bundle *Bundle
updateBundle *Bundle
files map[string]File
}
// New create new Registry.
func New(index UpdateIndex) Registry {
return Registry{
updateIndex: index,
files: make(map[string]File),
}
}
// Initialize parses and initializes currently installed bundles.
func (reg *Registry) Initialize() error {
var err error
// Parse current installed binary bundle.
reg.bundle, err = parseBundle(reg.updateIndex.Directory, reg.updateIndex.IndexFile)
if err != nil {
return fmt.Errorf("failed to parse binary bundle: %w", err)
}
// Add bundle artifacts to registry.
reg.processBundle(reg.bundle)
return nil
}
func (reg *Registry) processBundle(bundle *Bundle) {
for _, artifact := range bundle.Artifacts {
artifactPath := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, artifact.Filename)
reg.files[artifact.Filename] = File{id: artifact.Filename, path: artifactPath}
}
}
// GetFile returns the object of a artifact by id.
func (reg *Registry) GetFile(id string) (*File, error) {
file, ok := reg.files[id]
if ok {
return &file, nil
} else {
log.Errorf("updates: requested file id not found: %s", id)
for _, file := range reg.files {
log.Debugf("File: %s", file)
}
return nil, ErrNotFound
}
}
// CheckForUpdates checks if there is a new binary bundle updates.
func (reg *Registry) CheckForUpdates() (bool, error) {
err := reg.updateIndex.downloadIndexFile()
if err != nil {
return false, err
}
reg.updateBundle, err = parseBundle(reg.updateIndex.DownloadDirectory, reg.updateIndex.IndexFile)
if err != nil {
return false, err
}
// TODO(vladimir): Make a better check.
if reg.bundle.Version != reg.updateBundle.Version {
return true, nil
}
return false, nil
}
// DownloadUpdates downloads available binary updates.
func (reg *Registry) DownloadUpdates() error {
if reg.updateBundle == nil {
// CheckForBinaryUpdates needs to be called before this.
return fmt.Errorf("no valid update bundle found")
}
_ = deleteUnfinishedDownloads(reg.updateIndex.DownloadDirectory)
reg.updateBundle.downloadAndVerify(reg.updateIndex.DownloadDirectory)
return nil
}
// ApplyUpdates removes the current binary folder and replaces it with the downloaded one.
func (reg *Registry) ApplyUpdates() error {
// Create purge dir.
err := os.MkdirAll(reg.updateIndex.PurgeDirectory, defaultDirMode)
if err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
// Read all files in the current version folder.
files, err := os.ReadDir(reg.updateIndex.Directory)
if err != nil {
return err
}
// Move current version files into purge folder.
for _, file := range files {
filepath := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, file.Name())
purgePath := fmt.Sprintf("%s/%s", reg.updateIndex.PurgeDirectory, file.Name())
err := os.Rename(filepath, purgePath)
if err != nil {
return fmt.Errorf("failed to move file %s: %w", filepath, err)
}
}
// Move the new index file
indexFile := fmt.Sprintf("%s/%s", reg.updateIndex.DownloadDirectory, reg.updateIndex.IndexFile)
newIndexFile := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, reg.updateIndex.IndexFile)
err = os.Rename(indexFile, newIndexFile)
if err != nil {
return fmt.Errorf("failed to move index file %s: %w", indexFile, err)
}
// Move downloaded files to the current version folder.
for _, artifact := range reg.bundle.Artifacts {
fromFilepath := fmt.Sprintf("%s/%s", reg.updateIndex.DownloadDirectory, artifact.Filename)
toFilepath := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, artifact.Filename)
err = os.Rename(fromFilepath, toFilepath)
if err != nil {
return fmt.Errorf("failed to move file %s: %w", fromFilepath, err)
}
}
return nil
}
func parseBundle(dir string, indexFile string) (*Bundle, error) {
filepath := fmt.Sprintf("%s/%s", dir, indexFile)
// Check if the file exists.
file, err := os.Open(filepath)
if err != nil {
return nil, fmt.Errorf("failed to open index file: %w", err)
}
defer func() { _ = file.Close() }()
// Read
content, err := io.ReadAll(file)
if err != nil {
return nil, err
}
// Parse
var bundle Bundle
err = json.Unmarshal(content, &bundle)
if err != nil {
return nil, err
}
return &bundle, nil
}
func deleteUnfinishedDownloads(rootDir string) error {
return filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Check if the current file has the specified extension
if !info.IsDir() && strings.HasSuffix(info.Name(), ".download") {
log.Warningf("updates: deleting unfinished: %s\n", path)
err := os.Remove(path)
if err != nil {
return fmt.Errorf("failed to delete file %s: %w", path, err)
}
}
return nil
})
}

View File

@@ -0,0 +1,78 @@
package updates
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/safing/portmaster/base/log"
)
const (
defaultFileMode = os.FileMode(0o0644)
executableFileMode = os.FileMode(0o0744)
defaultDirMode = os.FileMode(0o0755)
)
func applyUpdates(updateIndex UpdateIndex, newBundle Bundle) error {
// Create purge dir.
err := os.MkdirAll(updateIndex.PurgeDirectory, defaultDirMode)
if err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
// Read all files in the current version folder.
files, err := os.ReadDir(updateIndex.Directory)
if err != nil {
return err
}
// Move current version files into purge folder.
for _, file := range files {
filepath := fmt.Sprintf("%s/%s", updateIndex.Directory, file.Name())
purgePath := fmt.Sprintf("%s/%s", updateIndex.PurgeDirectory, file.Name())
err := os.Rename(filepath, purgePath)
if err != nil {
return fmt.Errorf("failed to move file %s: %w", filepath, err)
}
}
// Move the new index file
indexFile := fmt.Sprintf("%s/%s", updateIndex.DownloadDirectory, updateIndex.IndexFile)
newIndexFile := fmt.Sprintf("%s/%s", updateIndex.Directory, updateIndex.IndexFile)
err = os.Rename(indexFile, newIndexFile)
if err != nil {
return fmt.Errorf("failed to move index file %s: %w", indexFile, err)
}
// Move downloaded files to the current version folder.
for _, artifact := range newBundle.Artifacts {
fromFilepath := fmt.Sprintf("%s/%s", updateIndex.DownloadDirectory, artifact.Filename)
toFilepath := fmt.Sprintf("%s/%s", updateIndex.Directory, artifact.Filename)
err = os.Rename(fromFilepath, toFilepath)
if err != nil {
return fmt.Errorf("failed to move file %s: %w", fromFilepath, err)
}
}
return nil
}
func deleteUnfinishedDownloads(rootDir string) error {
return filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Check if the current file has the specified extension
if !info.IsDir() && strings.HasSuffix(info.Name(), ".download") {
log.Warningf("updates: deleting unfinished: %s\n", path)
err := os.Remove(path)
if err != nil {
return fmt.Errorf("failed to delete file %s: %w", path, err)
}
}
return nil
})
}