forked from arduino/arduino-cli
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathinstall.go
139 lines (120 loc) · 4.27 KB
/
install.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].
package resources
import (
"context"
"errors"
"os"
"github.com/arduino/arduino-cli/internal/i18n"
paths "github.com/arduino/go-paths-helper"
"github.com/codeclysm/extract/v4"
"go.bug.st/cleanup"
)
type IntegrityCheckMode int
const (
IntegrityCheckFull IntegrityCheckMode = iota
IntegrityCheckNone
)
// Install installs the resource in three steps:
// - the archive is unpacked in a temporary subdir of tempPath
// - there should be only one root dir in the unpacked content
// - the only root dir is moved/renamed to/as the destination directory
// Note that tempPath and destDir must be on the same filesystem partition
// otherwise the last step will fail.
func (release *DownloadResource) Install(downloadDir, tempPath, destDir *paths.Path, checks IntegrityCheckMode) error {
if checks != IntegrityCheckNone {
// Check the integrity of the package
if ok, err := release.TestLocalArchiveIntegrity(downloadDir); err != nil {
return errors.New(i18n.Tr("testing local archive integrity: %s", err))
} else if !ok {
return errors.New(i18n.Tr("checking local archive integrity"))
}
}
// Create a temporary dir to extract package
if err := tempPath.MkdirAll(); err != nil {
return errors.New(i18n.Tr("creating temp dir for extraction: %s", err))
}
tempDir, err := tempPath.MkTempDir("package-")
if err != nil {
return errors.New(i18n.Tr("creating temp dir for extraction: %s", err))
}
defer tempDir.RemoveAll()
// Obtain the archive path and open it
archivePath, err := release.ArchivePath(downloadDir)
if err != nil {
return errors.New(i18n.Tr("getting archive path: %s", err))
}
file, err := os.Open(archivePath.String())
if err != nil {
return errors.New(i18n.Tr("opening archive file: %s", err))
}
defer file.Close()
// Extract into temp directory
ctx, cancel := cleanup.InterruptableContext(context.Background())
defer cancel()
if err := extract.Archive(ctx, file, tempDir.String(), nil); err != nil {
return errors.New(i18n.Tr("extracting archive: %s", err))
}
// Check package content and find package root dir
root, err := findPackageRoot(tempDir)
if err != nil {
return errors.New(i18n.Tr("searching package root dir: %s", err))
}
// Ensure container dir exists
destDirParent := destDir.Parent()
if err := destDirParent.MkdirAll(); err != nil {
return err
}
defer func() {
if empty, err := IsDirEmpty(destDirParent); err == nil && empty {
destDirParent.RemoveAll()
}
}()
// If the destination dir already exists remove it
if destDir.IsDir() {
destDir.RemoveAll()
}
// Move/rename the extracted root directory in the destination directory
if err := root.Rename(destDir); err != nil {
// Copy the extracted root directory to the destination directory, if move failed
if err := root.CopyDirTo(destDir); err != nil {
return errors.New(i18n.Tr("moving extracted archive to destination dir: %s", err))
}
}
return nil
}
// IsDirEmpty returns true if the directory specified by path is empty.
func IsDirEmpty(path *paths.Path) (bool, error) {
files, err := path.ReadDir()
if err != nil {
return false, err
}
return len(files) == 0, nil
}
func findPackageRoot(parent *paths.Path) (*paths.Path, error) {
files, err := parent.ReadDir()
if err != nil {
return nil, errors.New(i18n.Tr("reading package root dir: %s", err))
}
files.FilterDirs()
files.FilterOutPrefix("__MACOSX")
if len(files) == 0 {
return nil, errors.New(i18n.Tr("files in archive must be placed in a subdirectory"))
}
if len(files) > 1 {
return nil, errors.New(i18n.Tr("no unique root dir in archive, found '%[1]s' and '%[2]s'", files[0], files[1]))
}
return files[0], nil
}