Добавление статистики
Исправление работы с мультипакетами
This commit is contained in:
@@ -32,6 +32,7 @@ import (
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/stats"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||
@@ -401,9 +402,19 @@ func (b *Builder) BuildPackage(
|
||||
|
||||
// We filter so as not to re-build what has already been built at the `installBuildDeps` stage.
|
||||
var filteredDepends []string
|
||||
|
||||
// Создаем набор подпакетов текущего мультипакета для исключения циклических зависимостей
|
||||
currentPackageNames := make(map[string]struct{})
|
||||
for _, pkg := range input.packages {
|
||||
currentPackageNames[pkg] = struct{}{}
|
||||
}
|
||||
|
||||
for _, d := range depends {
|
||||
if _, found := depNames[d]; !found {
|
||||
filteredDepends = append(filteredDepends, d)
|
||||
// Исключаем зависимости, которые являются подпакетами текущего мультипакета
|
||||
if _, isCurrentPackage := currentPackageNames[d]; !isCurrentPackage {
|
||||
filteredDepends = append(filteredDepends, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,6 +539,13 @@ func (b *Builder) InstallALRPackages(
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Отслеживание установки ALR пакетов
|
||||
for _, dep := range res {
|
||||
if stats.ShouldTrackPackage(dep.Name) {
|
||||
stats.TrackInstallation(ctx, dep.Name, "upgrade")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -552,11 +570,13 @@ func (b *Builder) BuildALRDeps(
|
||||
repoDeps = notFound
|
||||
|
||||
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
|
||||
pkgs := cliutils.FlattenPkgs(
|
||||
// Для зависимостей указываем isDependency = true
|
||||
pkgs := cliutils.FlattenPkgsWithContext(
|
||||
ctx,
|
||||
found,
|
||||
"install",
|
||||
input.BuildOpts().Interactive,
|
||||
true,
|
||||
)
|
||||
type item struct {
|
||||
pkg *alrsh.Package
|
||||
@@ -691,6 +711,13 @@ func (i *Builder) InstallPkgs(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Отслеживание установки локальных пакетов
|
||||
for _, dep := range builtDeps {
|
||||
if stats.ShouldTrackPackage(dep.Name) {
|
||||
stats.TrackInstallation(ctx, dep.Name, "install")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(repoDeps) > 0 {
|
||||
@@ -700,6 +727,13 @@ func (i *Builder) InstallPkgs(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Отслеживание установки пакетов из репозитория
|
||||
for _, pkg := range repoDeps {
|
||||
if stats.ShouldTrackPackage(pkg) {
|
||||
stats.TrackInstallation(ctx, pkg, "install")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builtDeps, nil
|
||||
|
@@ -103,22 +103,62 @@ func ShowScript(path, name, style string) error {
|
||||
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
|
||||
// of packages by prompting the user if multiple packages match.
|
||||
func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package {
|
||||
return FlattenPkgsWithContext(ctx, found, verb, interactive, false)
|
||||
}
|
||||
|
||||
// FlattenPkgsWithContext расширенная версия FlattenPkgs с контекстом обработки зависимостей
|
||||
func FlattenPkgsWithContext(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool, isDependency bool) []alrsh.Package {
|
||||
var outPkgs []alrsh.Package
|
||||
for _, pkgs := range found {
|
||||
if len(pkgs) > 1 && interactive {
|
||||
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
||||
if err != nil {
|
||||
slog.Error(gotext.Get("Error prompting for choice of package"))
|
||||
os.Exit(1)
|
||||
if len(pkgs) > 1 {
|
||||
// Проверяем, являются ли пакеты подпакетами одного мультипакета
|
||||
if isMultiPackage(pkgs) && verb == "install" {
|
||||
// Для мультипакетов при установке ВСЕГДА берем все подпакеты без выбора
|
||||
// Это правильное поведение как для прямой установки, так и для зависимостей
|
||||
outPkgs = append(outPkgs, pkgs...)
|
||||
} else if interactive {
|
||||
// Для разных пакетов с одинаковым именем - показываем меню выбора
|
||||
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
||||
if err != nil {
|
||||
slog.Error(gotext.Get("Error prompting for choice of package"))
|
||||
os.Exit(1)
|
||||
}
|
||||
outPkgs = append(outPkgs, choice)
|
||||
} else {
|
||||
// Если не интерактивный режим - берем первый
|
||||
outPkgs = append(outPkgs, pkgs[0])
|
||||
}
|
||||
outPkgs = append(outPkgs, choice)
|
||||
} else if len(pkgs) == 1 || !interactive {
|
||||
} else {
|
||||
// Если только один пакет - берем его
|
||||
outPkgs = append(outPkgs, pkgs[0])
|
||||
}
|
||||
}
|
||||
return outPkgs
|
||||
}
|
||||
|
||||
// isMultiPackage проверяет, являются ли пакеты подпакетами одного мультипакета
|
||||
func isMultiPackage(pkgs []alrsh.Package) bool {
|
||||
if len(pkgs) <= 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Проверяем, что у всех пакетов одинаковый BasePkgName и Repository
|
||||
firstBasePkg := pkgs[0].BasePkgName
|
||||
firstRepo := pkgs[0].Repository
|
||||
|
||||
if firstBasePkg == "" {
|
||||
return false // Не мультипакет
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs[1:] {
|
||||
if pkg.BasePkgName != firstBasePkg || pkg.Repository != firstRepo {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// PkgPrompt asks the user to choose between multiple packages.
|
||||
func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) {
|
||||
if !interactive {
|
||||
|
@@ -60,6 +60,13 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrs
|
||||
return nil, nil, fmt.Errorf("FindPkgs: get by provides: %w", err)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
result, err = rs.db.GetPkgs(ctx, "basepkg_name = ?", pkgName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("FindPkgs: get by basepkg_name: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
result, err = rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
||||
}
|
||||
|
106
internal/stats/tracker.go
Normal file
106
internal/stats/tracker.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// ALR - Any Linux Repository
|
||||
// Copyright (C) 2025 The ALR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type InstallationData struct {
|
||||
PackageName string `json:"packageName"`
|
||||
Version string `json:"version,omitempty"`
|
||||
InstallType string `json:"installType"` // "install" or "upgrade"
|
||||
UserAgent string `json:"userAgent"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
apiEndpoints = []string{
|
||||
"https://alr.plemya-x.ru/api/packages/track-install",
|
||||
"http://localhost:3001/api/packages/track-install",
|
||||
}
|
||||
userAgent = "ALR-CLI/1.0"
|
||||
)
|
||||
|
||||
func generateFingerprint(packageName string) string {
|
||||
hostname, _ := os.Hostname()
|
||||
data := fmt.Sprintf("%s_%s_%s", hostname, packageName, time.Now().Format("2006-01-02"))
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// TrackInstallation отправляет статистику установки пакета
|
||||
func TrackInstallation(ctx context.Context, packageName string, installType string) {
|
||||
// Запускаем в отдельной горутине, чтобы не блокировать основной процесс
|
||||
go func() {
|
||||
data := InstallationData{
|
||||
PackageName: packageName,
|
||||
InstallType: installType,
|
||||
UserAgent: userAgent,
|
||||
Fingerprint: generateFingerprint(packageName),
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return // Тихо игнорируем ошибки - статистика не критична
|
||||
}
|
||||
|
||||
// Пробуем отправить запрос к разным endpoint-ам
|
||||
for _, endpoint := range apiEndpoints {
|
||||
if sendRequest(endpoint, jsonData) {
|
||||
return // Если хотя бы один запрос прошёл успешно, выходим
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func sendRequest(endpoint string, data []byte) bool {
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.StatusCode >= 200 && resp.StatusCode < 300
|
||||
}
|
||||
|
||||
// ShouldTrackPackage проверяет, нужно ли отслеживать установку этого пакета
|
||||
func ShouldTrackPackage(packageName string) bool {
|
||||
// Отслеживаем только alr-bin
|
||||
return strings.Contains(packageName, "alr-bin")
|
||||
}
|
@@ -56,6 +56,31 @@ installPkg() {
|
||||
esac
|
||||
}
|
||||
|
||||
trackInstallation() {
|
||||
# Отправить статистику установки (не критично если не получится)
|
||||
if command -v curl &>/dev/null; then
|
||||
# Генерируем уникальный отпечаток на основе hostname и даты
|
||||
fingerprint=$(echo "$(hostname)_$(date +%Y-%m-%d)" | sha256sum 2>/dev/null | cut -d' ' -f1 || echo "$(hostname)_$(date +%Y-%m-%d)")
|
||||
|
||||
# Пробуем разные домены/порты для отправки статистики
|
||||
for api_url in "https://alr.plemya-x.ru/api/packages/track-install" "http://localhost:3001/api/packages/track-install"; do
|
||||
curl -s -m 5 -X POST "$api_url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "User-Agent: ALR-InstallScript/1.0" \
|
||||
-d "{
|
||||
\"packageName\": \"alr-bin\",
|
||||
\"installType\": \"script\",
|
||||
\"userAgent\": \"ALR-InstallScript/1.0\",
|
||||
\"fingerprint\": \"$fingerprint\"
|
||||
}" >/dev/null 2>&1
|
||||
# Если один запрос удался, не пробуем остальные
|
||||
if [ $? -eq 0 ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
if ! command -v curl &>/dev/null; then
|
||||
error "Этот скрипт требует команду curl. Пожалуйста, установите её и запустите снова."
|
||||
fi
|
||||
@@ -186,6 +211,9 @@ if [ -z "$noPkgMgr" ]; then
|
||||
info "Установка пакета ALR"
|
||||
installPkg "$pkgMgr" "$fname"
|
||||
|
||||
# Отправляем статистику установки
|
||||
trackInstallation
|
||||
|
||||
info "Очистка"
|
||||
rm -f "$fname"
|
||||
trap - EXIT
|
||||
|
Reference in New Issue
Block a user