Compare commits
2 Commits
959298fc2d
...
af439a8e69
| Author | SHA1 | Date | |
|---|---|---|---|
| af439a8e69 | |||
| 50c8ad9823 |
BIN
GitUpdateChecker_go_test.exe
Normal file
BIN
GitUpdateChecker_go_test.exe
Normal file
Binary file not shown.
36
build.bat
36
build.bat
@@ -1,44 +1,30 @@
|
|||||||
@echo off
|
@echo off
|
||||||
echo ========================================
|
echo ========================================
|
||||||
echo Build Git Update Checker (.exe)
|
echo Build Git Update Checker (.exe) - Go
|
||||||
echo ========================================
|
echo ========================================
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
:: Vérifier que Python est installé
|
where go >nul 2>&1
|
||||||
python --version >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo [ERREUR] Python n'est pas installe ou pas dans le PATH.
|
echo [ERREUR] Go n'est pas installe ou pas dans le PATH.
|
||||||
pause
|
pause
|
||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
:: Installer PyInstaller si nécessaire
|
echo [*] Telechargement des dependances...
|
||||||
pip show pyinstaller >nul 2>&1
|
go mod tidy
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo [*] Installation de PyInstaller...
|
echo [ERREUR] go mod tidy a echoue.
|
||||||
pip install pyinstaller
|
pause
|
||||||
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
echo [*] Conversion icon.png -> icon.ico + icon_small.png (32x32)...
|
|
||||||
python -c "from PIL import Image; img = Image.open('icon.png').convert('RGBA'); img.save('icon.ico', format='ICO', sizes=[(256,256),(128,128),(64,64),(32,32),(16,16)]); thumb = img.copy(); thumb.thumbnail((32,32), Image.LANCZOS); thumb.save('icon_small.png')"
|
|
||||||
|
|
||||||
echo [*] Compilation en cours...
|
echo [*] Compilation en cours...
|
||||||
echo.
|
go build -ldflags "-H windowsgui -s -w" -o GitUpdateChecker.exe .
|
||||||
|
|
||||||
pyinstaller --onedir --noconsole --name "GitUpdateChecker" --icon=icon.ico --add-data "icon.png:." --add-data "icon_small.png:." -y git_updater.py
|
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
if exist "dist\GitUpdateChecker\GitUpdateChecker.exe" (
|
if exist "GitUpdateChecker.exe" (
|
||||||
echo [*] Copie de GitUpdateChecker.exe a la racine...
|
echo [OK] GitUpdateChecker.exe cree - exe unique, aucune dependance.
|
||||||
copy /Y "dist\GitUpdateChecker\GitUpdateChecker.exe" "GitUpdateChecker.exe" >nul
|
|
||||||
|
|
||||||
echo [*] Copie de _internal\ a la racine...
|
|
||||||
if exist "_internal" rmdir /s /q "_internal"
|
|
||||||
xcopy /E /I /Q "dist\GitUpdateChecker\_internal" "_internal"
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo [OK] Deploiement pret. Committer GitUpdateChecker.exe + _internal\
|
|
||||||
echo _internal\ ne change que si la version de Python change.
|
|
||||||
) else (
|
) else (
|
||||||
echo [ERREUR] La compilation a echoue.
|
echo [ERREUR] La compilation a echoue.
|
||||||
)
|
)
|
||||||
|
|||||||
80
config.go
Normal file
80
config.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepoConfig struct {
|
||||||
|
Name string
|
||||||
|
URL string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfUpdateConfig struct {
|
||||||
|
URL string
|
||||||
|
ExeName string
|
||||||
|
Branch string
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig() ([]RepoConfig, SelfUpdateConfig, error) {
|
||||||
|
cfgPath := filepath.Join(exeDir(), "config.ini")
|
||||||
|
f, err := os.Open(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, SelfUpdateConfig{}, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var repos []RepoConfig
|
||||||
|
var su SelfUpdateConfig
|
||||||
|
section := ""
|
||||||
|
kv := map[string]string{}
|
||||||
|
|
||||||
|
flush := func() {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(section, "repo:"):
|
||||||
|
name := strings.TrimPrefix(section, "repo:")
|
||||||
|
if kv["url"] != "" && kv["path"] != "" {
|
||||||
|
repos = append(repos, RepoConfig{
|
||||||
|
Name: name,
|
||||||
|
URL: kv["url"],
|
||||||
|
Path: kv["path"],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case section == "self-update":
|
||||||
|
su.URL = strings.TrimRight(kv["url"], "/")
|
||||||
|
su.ExeName = kv["exe_name"]
|
||||||
|
if su.ExeName == "" {
|
||||||
|
su.ExeName = "GitUpdateChecker.exe"
|
||||||
|
}
|
||||||
|
su.Branch = kv["branch"]
|
||||||
|
if su.Branch == "" {
|
||||||
|
su.Branch = "master"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kv = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||||
|
flush()
|
||||||
|
section = strings.ToLower(strings.TrimSpace(line[1 : len(line)-1]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if idx := strings.IndexByte(line, '='); idx > 0 {
|
||||||
|
k := strings.TrimSpace(strings.ToLower(line[:idx]))
|
||||||
|
v := strings.TrimSpace(line[idx+1:])
|
||||||
|
kv[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush()
|
||||||
|
|
||||||
|
return repos, su, scanner.Err()
|
||||||
|
}
|
||||||
134
git.go
Normal file
134
git.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepoResult struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
URL string
|
||||||
|
Pending bool
|
||||||
|
UpToDate bool
|
||||||
|
Offline bool
|
||||||
|
NeedsClone bool
|
||||||
|
Error string
|
||||||
|
NewCommits int
|
||||||
|
LocalChanges int
|
||||||
|
}
|
||||||
|
|
||||||
|
func runGit(args []string, cwd string, timeout time.Duration) (code int, stdout string, stderr string) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
fullArgs := append([]string{"-c", "safe.directory=*"}, args...)
|
||||||
|
cmd := newGitCmd(ctx, fullArgs, cwd)
|
||||||
|
|
||||||
|
var outBuf, errBuf strings.Builder
|
||||||
|
cmd.Stdout = &outBuf
|
||||||
|
cmd.Stderr = &errBuf
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
stdout = strings.TrimSpace(outBuf.String())
|
||||||
|
stderr = strings.TrimSpace(errBuf.String())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
return 1, stdout, "Timeout"
|
||||||
|
}
|
||||||
|
var exitErr *exec.ExitError
|
||||||
|
if errors.As(err, &exitErr) {
|
||||||
|
return exitErr.ExitCode(), stdout, stderr
|
||||||
|
}
|
||||||
|
return -1, stdout, err.Error()
|
||||||
|
}
|
||||||
|
return 0, stdout, stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
func absRepoPath(rel string) string {
|
||||||
|
if filepath.IsAbs(rel) {
|
||||||
|
return rel
|
||||||
|
}
|
||||||
|
return filepath.Join(exeDir(), rel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRepo(cfg RepoConfig) RepoResult {
|
||||||
|
res := RepoResult{Name: cfg.Name, URL: cfg.URL}
|
||||||
|
local := absRepoPath(cfg.Path)
|
||||||
|
res.Path = local
|
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(local, ".git")); os.IsNotExist(err) {
|
||||||
|
res.NeedsClone = true
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
runGit([]string{"remote", "set-url", "origin", cfg.URL}, local, 10*time.Second)
|
||||||
|
|
||||||
|
code, _, stderr := runGit([]string{"fetch", "origin"}, local, 60*time.Second)
|
||||||
|
if code != 0 {
|
||||||
|
for _, kw := range []string{"could not resolve", "connection refused", "unable to connect", "timed out", "the remote end hung up"} {
|
||||||
|
if strings.Contains(strings.ToLower(stderr), kw) {
|
||||||
|
res.Offline = true
|
||||||
|
res.Error = "Hors ligne"
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.Error = "Fetch: " + stderr
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
_, branch, _ := runGit([]string{"rev-parse", "--abbrev-ref", "HEAD"}, local, 5*time.Second)
|
||||||
|
if branch == "" {
|
||||||
|
branch = "master"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, countStr, _ := runGit([]string{"rev-list", "--count", "HEAD..origin/" + branch}, local, 5*time.Second)
|
||||||
|
fmt.Sscanf(countStr, "%d", &res.NewCommits)
|
||||||
|
|
||||||
|
_, status, _ := runGit([]string{"status", "--porcelain"}, local, 5*time.Second)
|
||||||
|
if status != "" {
|
||||||
|
res.LocalChanges = len(strings.Split(strings.TrimSpace(status), "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
res.UpToDate = res.NewCommits == 0 && res.LocalChanges == 0
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func doClone(cfg RepoConfig) error {
|
||||||
|
local := absRepoPath(cfg.Path)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(local), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
code, _, stderr := runGit([]string{"clone", cfg.URL, local}, "", 300*time.Second)
|
||||||
|
if code != 0 {
|
||||||
|
return fmt.Errorf("%s", stderr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doPull(res RepoResult) error {
|
||||||
|
_, branch, _ := runGit([]string{"rev-parse", "--abbrev-ref", "HEAD"}, res.Path, 5*time.Second)
|
||||||
|
if branch == "" {
|
||||||
|
branch = "master"
|
||||||
|
}
|
||||||
|
code, _, stderr := runGit([]string{"pull", "origin", branch}, res.Path, 120*time.Second)
|
||||||
|
if code != 0 {
|
||||||
|
return fmt.Errorf("%s", stderr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doCheckout(res RepoResult) error {
|
||||||
|
code, _, stderr := runGit([]string{"checkout", "--", "."}, res.Path, 30*time.Second)
|
||||||
|
if code != 0 {
|
||||||
|
return fmt.Errorf("%s", stderr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
11
go.mod
Normal file
11
go.mod
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module gitchecker
|
||||||
|
|
||||||
|
go 1.22
|
||||||
|
|
||||||
|
require github.com/lxn/walk v0.0.0-20210112085537-c389da54e794
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||||
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
|
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
|
||||||
|
)
|
||||||
9
go.sum
Normal file
9
go.sum
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw=
|
||||||
|
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||||
|
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||||
|
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||||
|
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
|
||||||
|
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||||
531
gui.go
Normal file
531
gui.go
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lxn/walk"
|
||||||
|
. "github.com/lxn/walk/declarative"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ── Modèle TableView ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
type RepoItem struct {
|
||||||
|
result RepoResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *RepoItem) statusText() string {
|
||||||
|
r := it.result
|
||||||
|
if r.Pending {
|
||||||
|
return "Vérification..."
|
||||||
|
}
|
||||||
|
if r.Error != "" {
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
if r.NeedsClone {
|
||||||
|
return "À cloner"
|
||||||
|
}
|
||||||
|
if r.UpToDate {
|
||||||
|
return "À jour"
|
||||||
|
}
|
||||||
|
msg := ""
|
||||||
|
if r.NewCommits > 0 {
|
||||||
|
msg = fmt.Sprintf("%d commit(s)", r.NewCommits)
|
||||||
|
}
|
||||||
|
if r.LocalChanges > 0 {
|
||||||
|
if msg != "" {
|
||||||
|
msg += ", "
|
||||||
|
}
|
||||||
|
msg += fmt.Sprintf("%d modif. locale(s)", r.LocalChanges)
|
||||||
|
}
|
||||||
|
if msg == "" {
|
||||||
|
return "À jour"
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *RepoItem) textColor() walk.Color {
|
||||||
|
r := it.result
|
||||||
|
if r.Pending {
|
||||||
|
return walk.RGB(120, 120, 120)
|
||||||
|
}
|
||||||
|
if r.Error != "" {
|
||||||
|
return walk.RGB(200, 50, 50)
|
||||||
|
}
|
||||||
|
if r.NeedsClone {
|
||||||
|
return walk.RGB(180, 120, 0)
|
||||||
|
}
|
||||||
|
if r.UpToDate {
|
||||||
|
return walk.RGB(0, 150, 0)
|
||||||
|
}
|
||||||
|
return walk.RGB(180, 120, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RepoModel struct {
|
||||||
|
walk.TableModelBase
|
||||||
|
mu sync.RWMutex
|
||||||
|
items []*RepoItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRepoModel(cfgs []RepoConfig) *RepoModel {
|
||||||
|
m := &RepoModel{}
|
||||||
|
m.items = make([]*RepoItem, len(cfgs))
|
||||||
|
for i, c := range cfgs {
|
||||||
|
m.items[i] = &RepoItem{result: RepoResult{Name: c.Name, Pending: true}}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RepoModel) RowCount() int {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
return len(m.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RepoModel) Value(row, col int) interface{} {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if row >= len(m.items) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
it := m.items[row]
|
||||||
|
if col == 0 {
|
||||||
|
return it.result.Name
|
||||||
|
}
|
||||||
|
return it.statusText()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RepoModel) StyleCell(style *walk.CellStyle) {
|
||||||
|
if style.Col() != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if style.Row() >= len(m.items) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
style.TextColor = m.items[style.Row()].textColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RepoModel) setResult(row int, res RepoResult) {
|
||||||
|
m.mu.Lock()
|
||||||
|
if row < len(m.items) {
|
||||||
|
m.items[row].result = res
|
||||||
|
}
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.PublishRowChanged(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RepoModel) reset(cfgs []RepoConfig) {
|
||||||
|
m.mu.Lock()
|
||||||
|
m.items = make([]*RepoItem, len(cfgs))
|
||||||
|
for i, c := range cfgs {
|
||||||
|
m.items[i] = &RepoItem{result: RepoResult{Name: c.Name, Pending: true}}
|
||||||
|
}
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.PublishRowsReset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RepoModel) getResult(row int) (RepoResult, bool) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if row < 0 || row >= len(m.items) {
|
||||||
|
return RepoResult{}, false
|
||||||
|
}
|
||||||
|
return m.items[row].result, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RepoModel) hasUpdates() bool {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
for _, it := range m.items {
|
||||||
|
r := it.result
|
||||||
|
if !r.Pending && r.Error == "" && !r.Offline && (r.NewCommits > 0 || r.LocalChanges > 0 || r.NeedsClone) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Application ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
mw *walk.MainWindow
|
||||||
|
statusLabel *walk.Label
|
||||||
|
tv *walk.TableView
|
||||||
|
model *RepoModel
|
||||||
|
logEdit *walk.TextEdit
|
||||||
|
btnRefresh *walk.PushButton
|
||||||
|
btnUpdateAll *walk.PushButton
|
||||||
|
btnAction *walk.PushButton
|
||||||
|
|
||||||
|
reposConfig []RepoConfig
|
||||||
|
suConfig SelfUpdateConfig
|
||||||
|
checking atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func runApp() error {
|
||||||
|
app := &App{}
|
||||||
|
var err error
|
||||||
|
app.reposConfig, app.suConfig, err = loadConfig()
|
||||||
|
if err != nil {
|
||||||
|
logWarn("Config: " + err.Error())
|
||||||
|
}
|
||||||
|
app.model = newRepoModel(app.reposConfig)
|
||||||
|
return app.buildAndRun()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) buildAndRun() error {
|
||||||
|
if err := (MainWindow{
|
||||||
|
AssignTo: &a.mw,
|
||||||
|
Title: "Git Update Checker v" + VERSION,
|
||||||
|
MinSize: Size{Width: 650, Height: 400},
|
||||||
|
Size: Size{Width: 820, Height: 600},
|
||||||
|
Layout: VBox{Margins: Margins{Left: 10, Top: 10, Right: 10, Bottom: 10}},
|
||||||
|
Children: []Widget{
|
||||||
|
// En-tête
|
||||||
|
Composite{
|
||||||
|
Layout: HBox{MarginsZero: true},
|
||||||
|
Children: []Widget{
|
||||||
|
Label{
|
||||||
|
Text: "Git Update Checker v" + VERSION,
|
||||||
|
Font: Font{Bold: true, PointSize: 12},
|
||||||
|
},
|
||||||
|
HSpacer{},
|
||||||
|
Label{AssignTo: &a.statusLabel, Text: "Démarrage..."},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Zone principale : table + log
|
||||||
|
VSplitter{
|
||||||
|
Children: []Widget{
|
||||||
|
TableView{
|
||||||
|
AssignTo: &a.tv,
|
||||||
|
AlternatingRowBG: true,
|
||||||
|
ColumnsOrderable: false,
|
||||||
|
Columns: []TableViewColumn{
|
||||||
|
{Title: "Dépôt", Width: 180},
|
||||||
|
{Title: "Statut", Width: 350},
|
||||||
|
},
|
||||||
|
Model: a.model,
|
||||||
|
OnCurrentIndexChanged: a.onSelectionChanged,
|
||||||
|
OnItemActivated: a.doAction,
|
||||||
|
},
|
||||||
|
Composite{
|
||||||
|
Layout: VBox{MarginsZero: true},
|
||||||
|
Children: []Widget{
|
||||||
|
Composite{
|
||||||
|
Layout: HBox{MarginsZero: true},
|
||||||
|
Children: []Widget{
|
||||||
|
Label{Text: "Journal", Font: Font{Bold: true}},
|
||||||
|
HSpacer{},
|
||||||
|
PushButton{
|
||||||
|
Text: "Effacer",
|
||||||
|
MaxSize: Size{Width: 65},
|
||||||
|
OnClicked: func() { a.logEdit.SetText("") },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TextEdit{
|
||||||
|
AssignTo: &a.logEdit,
|
||||||
|
ReadOnly: true,
|
||||||
|
VScroll: true,
|
||||||
|
Font: Font{Family: "Consolas", PointSize: 9},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Boutons
|
||||||
|
Composite{
|
||||||
|
Layout: HBox{MarginsZero: true},
|
||||||
|
Children: []Widget{
|
||||||
|
PushButton{
|
||||||
|
AssignTo: &a.btnRefresh,
|
||||||
|
Text: "Rafraîchir",
|
||||||
|
OnClicked: a.startCheck,
|
||||||
|
},
|
||||||
|
PushButton{
|
||||||
|
AssignTo: &a.btnUpdateAll,
|
||||||
|
Text: "Tout mettre à jour",
|
||||||
|
Enabled: false,
|
||||||
|
OnClicked: a.updateAll,
|
||||||
|
},
|
||||||
|
PushButton{
|
||||||
|
AssignTo: &a.btnAction,
|
||||||
|
Text: "Mettre à jour",
|
||||||
|
Enabled: false,
|
||||||
|
OnClicked: a.doAction,
|
||||||
|
},
|
||||||
|
HSpacer{},
|
||||||
|
PushButton{Text: "config.ini", OnClicked: a.openConfig},
|
||||||
|
PushButton{Text: "Logs", OnClicked: a.openLogs},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}.Create()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icône fenêtre
|
||||||
|
if icoPath := filepath.Join(exeDir(), "icon.ico"); fileExists(icoPath) {
|
||||||
|
if icon, err := walk.NewIconFromFile(icoPath); err == nil {
|
||||||
|
a.mw.SetIcon(icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lancer la vérification au démarrage
|
||||||
|
go func() {
|
||||||
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
a.mw.Synchronize(a.checkSelfUpdateThenRepos)
|
||||||
|
}()
|
||||||
|
|
||||||
|
a.mw.Run()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Vérifications ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func (a *App) checkSelfUpdateThenRepos() {
|
||||||
|
a.setStatus("Vérification auto-update...")
|
||||||
|
go func() {
|
||||||
|
needs, info, err := checkSelfUpdate(a.suConfig)
|
||||||
|
a.mw.Synchronize(func() {
|
||||||
|
if err != nil {
|
||||||
|
logWarn("Auto-update: " + err.Error())
|
||||||
|
}
|
||||||
|
if needs {
|
||||||
|
ans := walk.MsgBox(a.mw,
|
||||||
|
"Mise à jour disponible",
|
||||||
|
info+"\n\nTélécharger maintenant ?",
|
||||||
|
walk.MsgBoxYesNo|walk.MsgBoxIconQuestion,
|
||||||
|
)
|
||||||
|
if ans == walk.DlgCmdYes {
|
||||||
|
a.doSelfUpdate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.startCheck()
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) startCheck() {
|
||||||
|
if !a.checking.CompareAndSwap(false, true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.btnRefresh.SetEnabled(false)
|
||||||
|
a.btnUpdateAll.SetEnabled(false)
|
||||||
|
a.btnAction.SetEnabled(false)
|
||||||
|
a.model.reset(a.reposConfig)
|
||||||
|
a.setStatus(fmt.Sprintf("Vérification 0/%d...", len(a.reposConfig)))
|
||||||
|
logInfo("Vérification des dépôts...")
|
||||||
|
|
||||||
|
done := atomic.Int32{}
|
||||||
|
total := int32(len(a.reposConfig))
|
||||||
|
|
||||||
|
for i, cfg := range a.reposConfig {
|
||||||
|
i, cfg := i, cfg
|
||||||
|
go func() {
|
||||||
|
res := checkRepo(cfg)
|
||||||
|
a.mw.Synchronize(func() {
|
||||||
|
a.model.setResult(i, res)
|
||||||
|
a.appendLog(logLineForResult(res))
|
||||||
|
logInfo(fmt.Sprintf("[%s] %s", res.Name, logLineForResult(res)))
|
||||||
|
|
||||||
|
n := done.Add(1)
|
||||||
|
if int32(n) == total {
|
||||||
|
a.onCheckDone()
|
||||||
|
} else {
|
||||||
|
a.setStatus(fmt.Sprintf("Vérification %d/%d...", n, total))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if total == 0 {
|
||||||
|
a.onCheckDone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) onCheckDone() {
|
||||||
|
a.checking.Store(false)
|
||||||
|
a.btnRefresh.SetEnabled(true)
|
||||||
|
a.btnUpdateAll.SetEnabled(a.model.hasUpdates())
|
||||||
|
a.setStatus(fmt.Sprintf("Dernière vérification : %s", time.Now().Format("15:04:05")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func logLineForResult(r RepoResult) string {
|
||||||
|
if r.Error != "" {
|
||||||
|
return r.Error
|
||||||
|
}
|
||||||
|
if r.NeedsClone {
|
||||||
|
return "À cloner"
|
||||||
|
}
|
||||||
|
if r.UpToDate {
|
||||||
|
return "À jour"
|
||||||
|
}
|
||||||
|
msg := ""
|
||||||
|
if r.NewCommits > 0 {
|
||||||
|
msg += fmt.Sprintf("%d commit(s) disponible(s)", r.NewCommits)
|
||||||
|
}
|
||||||
|
if r.LocalChanges > 0 {
|
||||||
|
if msg != "" {
|
||||||
|
msg += ", "
|
||||||
|
}
|
||||||
|
msg += fmt.Sprintf("%d modif. locale(s)", r.LocalChanges)
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actions dépôt ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func (a *App) onSelectionChanged() {
|
||||||
|
idx := a.tv.CurrentIndex()
|
||||||
|
res, ok := a.model.getResult(idx)
|
||||||
|
if !ok || res.Pending || res.Offline || res.Error != "" {
|
||||||
|
a.btnAction.SetEnabled(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res.NeedsClone {
|
||||||
|
a.btnAction.SetText("Cloner")
|
||||||
|
a.btnAction.SetEnabled(true)
|
||||||
|
} else if res.NewCommits > 0 || res.LocalChanges > 0 {
|
||||||
|
a.btnAction.SetText("Mettre à jour")
|
||||||
|
a.btnAction.SetEnabled(true)
|
||||||
|
} else {
|
||||||
|
a.btnAction.SetEnabled(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) doAction() {
|
||||||
|
idx := a.tv.CurrentIndex()
|
||||||
|
res, ok := a.model.getResult(idx)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg := a.reposConfig[idx]
|
||||||
|
|
||||||
|
a.btnAction.SetEnabled(false)
|
||||||
|
a.appendLog(fmt.Sprintf("[%s] Mise à jour en cours...", res.Name))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
if res.NeedsClone {
|
||||||
|
err = doClone(cfg)
|
||||||
|
} else {
|
||||||
|
if res.LocalChanges > 0 {
|
||||||
|
err = doCheckout(res)
|
||||||
|
}
|
||||||
|
if err == nil && res.NewCommits > 0 {
|
||||||
|
err = doPull(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.mw.Synchronize(func() {
|
||||||
|
if err != nil {
|
||||||
|
walk.MsgBox(a.mw, "Erreur", res.Name+"\n\n"+err.Error(), walk.MsgBoxIconError)
|
||||||
|
logError(fmt.Sprintf("[%s] %v", res.Name, err))
|
||||||
|
} else {
|
||||||
|
a.appendLog(fmt.Sprintf("[%s] Mise à jour OK", res.Name))
|
||||||
|
logInfo(fmt.Sprintf("[%s] Mise à jour OK", res.Name))
|
||||||
|
}
|
||||||
|
a.startCheck()
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) updateAll() {
|
||||||
|
a.btnUpdateAll.SetEnabled(false)
|
||||||
|
a.btnRefresh.SetEnabled(false)
|
||||||
|
pending := atomic.Int32{}
|
||||||
|
|
||||||
|
for i, cfg := range a.reposConfig {
|
||||||
|
res, ok := a.model.getResult(i)
|
||||||
|
if !ok || res.Pending || res.UpToDate || res.Offline || res.Error != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pending.Add(1)
|
||||||
|
cfg, res := cfg, res
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
if res.NeedsClone {
|
||||||
|
err = doClone(cfg)
|
||||||
|
} else {
|
||||||
|
if res.LocalChanges > 0 {
|
||||||
|
err = doCheckout(res)
|
||||||
|
}
|
||||||
|
if err == nil && res.NewCommits > 0 {
|
||||||
|
err = doPull(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.mw.Synchronize(func() {
|
||||||
|
if err != nil {
|
||||||
|
logError(fmt.Sprintf("[%s] %v", res.Name, err))
|
||||||
|
a.appendLog(fmt.Sprintf("[%s] Erreur: %v", res.Name, err))
|
||||||
|
} else {
|
||||||
|
logInfo(fmt.Sprintf("[%s] Mise à jour OK", res.Name))
|
||||||
|
a.appendLog(fmt.Sprintf("[%s] Mise à jour OK", res.Name))
|
||||||
|
}
|
||||||
|
if pending.Add(-1) == 0 {
|
||||||
|
a.startCheck()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if pending.Load() == 0 {
|
||||||
|
a.startCheck()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Auto-update programme ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func (a *App) doSelfUpdate() {
|
||||||
|
a.setStatus("Téléchargement de la mise à jour...")
|
||||||
|
go func() {
|
||||||
|
err := doSelfUpdate(a.suConfig)
|
||||||
|
a.mw.Synchronize(func() {
|
||||||
|
if err != nil {
|
||||||
|
walk.MsgBox(a.mw, "Erreur", "Mise à jour échouée :\n"+err.Error(), walk.MsgBoxIconError)
|
||||||
|
logError("Auto-update: " + err.Error())
|
||||||
|
a.startCheck()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logInfo("Auto-update: mise à jour appliquée, redémarrage...")
|
||||||
|
walk.MsgBox(a.mw, "Mise à jour", "Mise à jour installée.\nLe programme va redémarrer.", walk.MsgBoxIconInformation)
|
||||||
|
exePath, _ := os.Executable()
|
||||||
|
relaunchAfterUpdate(exePath)
|
||||||
|
a.mw.Close()
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Utilitaires GUI ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func (a *App) setStatus(text string) {
|
||||||
|
a.statusLabel.SetText(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) appendLog(line string) {
|
||||||
|
ts := time.Now().Format("15:04:05")
|
||||||
|
current := a.logEdit.Text()
|
||||||
|
if current != "" {
|
||||||
|
current += "\r\n"
|
||||||
|
}
|
||||||
|
a.logEdit.SetText(current + "[" + ts + "] " + line)
|
||||||
|
// Scroller en bas
|
||||||
|
a.logEdit.SendMessage(0x0115 /*WM_VSCROLL*/, 7 /*SB_BOTTOM*/, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) openConfig() {
|
||||||
|
p := filepath.Join(exeDir(), "config.ini")
|
||||||
|
exec.Command("notepad", p).Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) openLogs() {
|
||||||
|
p := filepath.Join(exeDir(), "log")
|
||||||
|
exec.Command("explorer", p).Start()
|
||||||
|
}
|
||||||
44
logger.go
Normal file
44
logger.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logFile *os.File
|
||||||
|
|
||||||
|
func initLogger() {
|
||||||
|
dir := filepath.Join(exeDir(), "log")
|
||||||
|
os.MkdirAll(dir, 0755)
|
||||||
|
|
||||||
|
// Nettoyage logs > 30 jours
|
||||||
|
entries, _ := os.ReadDir(dir)
|
||||||
|
cutoff := time.Now().AddDate(0, 0, -30)
|
||||||
|
for _, e := range entries {
|
||||||
|
if !e.IsDir() {
|
||||||
|
if info, err := e.Info(); err == nil && info.ModTime().Before(cutoff) {
|
||||||
|
os.Remove(filepath.Join(dir, e.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logPath := filepath.Join(dir, time.Now().Format("2006-01-02")+".log")
|
||||||
|
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logFile = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func logMsg(level, msg string) {
|
||||||
|
line := fmt.Sprintf("[%s] %-5s %s\n", time.Now().Format("15:04:05"), level, msg)
|
||||||
|
if logFile != nil {
|
||||||
|
logFile.WriteString(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logInfo(msg string) { logMsg("INFO", msg) }
|
||||||
|
func logWarn(msg string) { logMsg("WARN", msg) }
|
||||||
|
func logError(msg string) { logMsg("ERROR", msg) }
|
||||||
36
main.go
Normal file
36
main.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/lxn/walk"
|
||||||
|
)
|
||||||
|
|
||||||
|
const VERSION = "0.7.0"
|
||||||
|
|
||||||
|
func exeDir() string {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return filepath.Dir(exe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(p string) bool {
|
||||||
|
_, err := os.Stat(p)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
initLogger()
|
||||||
|
logInfo(fmt.Sprintf("=== Demarrage Git Update Checker v%s ===", VERSION))
|
||||||
|
|
||||||
|
if err := runApp(); err != nil {
|
||||||
|
walk.MsgBox(nil, "Erreur fatale", err.Error(), walk.MsgBoxIconError)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
40
platform_windows.go
Normal file
40
platform_windows.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createNoWindow = 0x08000000
|
||||||
|
|
||||||
|
// newGitCmd crée une commande git sans fenêtre console.
|
||||||
|
func newGitCmd(ctx context.Context, args []string, cwd string) *exec.Cmd {
|
||||||
|
cmd := exec.CommandContext(ctx, "git", args...)
|
||||||
|
if cwd != "" {
|
||||||
|
cmd.Dir = cwd
|
||||||
|
}
|
||||||
|
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||||
|
CreationFlags: createNoWindow,
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// relaunchAfterUpdate crée un batch qui attend 1s, relance l'exe et nettoie le .old.
|
||||||
|
func relaunchAfterUpdate(exePath string) {
|
||||||
|
oldPath := exePath + ".old"
|
||||||
|
batPath := exePath + "_update.bat"
|
||||||
|
content := fmt.Sprintf(
|
||||||
|
"@echo off\r\ntimeout /t 1 /nobreak >nul\r\nstart \"\" \"%s\"\r\ndel \"%s\" 2>nul\r\ndel \"%%~f0\"\r\n",
|
||||||
|
exePath, oldPath,
|
||||||
|
)
|
||||||
|
os.WriteFile(batPath, []byte(content), 0644)
|
||||||
|
cmd := exec.Command("cmd", "/c", batPath)
|
||||||
|
cmd.SysProcAttr = &windows.SysProcAttr{CreationFlags: createNoWindow}
|
||||||
|
cmd.Start()
|
||||||
|
}
|
||||||
95
selfupdate.go
Normal file
95
selfupdate.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseVersion(v string) [3]int {
|
||||||
|
var t [3]int
|
||||||
|
for i, p := range strings.SplitN(strings.TrimSpace(v), ".", 3) {
|
||||||
|
fmt.Sscanf(p, "%d", &t[i])
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func versionGreater(remote, local string) bool {
|
||||||
|
r, l := parseVersion(remote), parseVersion(local)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
if r[i] > l[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if r[i] < l[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSelfUpdate(cfg SelfUpdateConfig) (needsUpdate bool, info string, err error) {
|
||||||
|
if cfg.URL == "" {
|
||||||
|
return false, "", nil
|
||||||
|
}
|
||||||
|
url := fmt.Sprintf("%s/raw/branch/%s/version.txt", cfg.URL, cfg.Branch)
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
data, _ := io.ReadAll(resp.Body)
|
||||||
|
remote := strings.TrimSpace(string(data))
|
||||||
|
if !versionGreater(remote, VERSION) {
|
||||||
|
return false, "", nil
|
||||||
|
}
|
||||||
|
return true, fmt.Sprintf("Version actuelle : %s\nVersion disponible : %s", VERSION, remote), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doSelfUpdate(cfg SelfUpdateConfig) error {
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newPath := exePath + ".new"
|
||||||
|
oldPath := exePath + ".old"
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/raw/branch/%s/%s", cfg.URL, cfg.Branch, cfg.ExeName)
|
||||||
|
client := &http.Client{Timeout: 120 * time.Second}
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
f, err := os.Create(newPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, copyErr := io.Copy(f, resp.Body)
|
||||||
|
f.Close()
|
||||||
|
if copyErr != nil {
|
||||||
|
os.Remove(newPath)
|
||||||
|
return copyErr
|
||||||
|
}
|
||||||
|
if n < 1000 {
|
||||||
|
os.Remove(newPath)
|
||||||
|
return fmt.Errorf("fichier telecharge invalide (%d octets)", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer le Mark of the Web (Zone.Identifier)
|
||||||
|
os.Remove(newPath + ":Zone.Identifier")
|
||||||
|
|
||||||
|
if err := os.Rename(exePath, oldPath); err != nil {
|
||||||
|
os.Remove(newPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Rename(newPath, exePath); err != nil {
|
||||||
|
os.Rename(oldPath, exePath) // restaurer
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user