mirror of
https://gitea.plemya-x.ru/Plemya-x/ALR.git
synced 2025-01-10 17:26:45 +00:00
Небольшие переводы и комментарии
This commit is contained in:
parent
47d1d97496
commit
93e82b43a7
2 changed files with 69 additions and 79 deletions
|
@ -1,23 +1,23 @@
|
|||
/*
|
||||
* ALR - Any Linux Repository
|
||||
* Copyright (C) 2024 Евгений Храмов
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
* ALR - Any Linux Repository
|
||||
* Copyright (C) 2024 Евгений Храмов
|
||||
*
|
||||
* 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 dl contains abstractions for downloadingfiles and directories
|
||||
// from various sources.
|
||||
// Пакет dl содержит абстракции для загрузки файлов и каталогов
|
||||
// из различных источников.
|
||||
package dl
|
||||
|
||||
import (
|
||||
|
@ -43,31 +43,32 @@ import (
|
|||
"plemya-x.ru/alr/pkg/loggerctx"
|
||||
)
|
||||
|
||||
// Константа для имени файла манифеста кэша
|
||||
const manifestFileName = ".alr_cache_manifest"
|
||||
|
||||
// ErrChecksumMismatch occurs when the checksum of a downloaded file
|
||||
// does not match the expected checksum provided in the Options struct.
|
||||
// Объявление ошибок для несоответствия контрольной суммы и отсутствия алгоритма хеширования
|
||||
var (
|
||||
ErrChecksumMismatch = errors.New("dl: checksums did not match")
|
||||
ErrNoSuchHashAlgo = errors.New("dl: invalid hashing algorithm")
|
||||
)
|
||||
|
||||
// Downloaders contains all the downloaders in the order in which
|
||||
// they should be checked
|
||||
// Массив доступных загрузчиков в порядке их проверки
|
||||
var Downloaders = []Downloader{
|
||||
GitDownloader{},
|
||||
TorrentDownloader{},
|
||||
FileDownloader{},
|
||||
}
|
||||
|
||||
// Type represents the type of download (file or directory)
|
||||
// Тип данных, представляющий тип загрузки (файл или каталог)
|
||||
type Type uint8
|
||||
|
||||
// Объявление констант для типов загрузки
|
||||
const (
|
||||
TypeFile Type = iota
|
||||
TypeDir
|
||||
)
|
||||
|
||||
// Метод для получения строки, представляющей тип загрузки
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case TypeFile:
|
||||
|
@ -78,8 +79,7 @@ func (t Type) String() string {
|
|||
return "<unknown>"
|
||||
}
|
||||
|
||||
// Options contains the options for downloading
|
||||
// files and directories
|
||||
// Структура Options содержит параметры для загрузки файлов и каталогов
|
||||
type Options struct {
|
||||
Hash []byte
|
||||
HashAlgorithm string
|
||||
|
@ -92,6 +92,7 @@ type Options struct {
|
|||
LocalDir string
|
||||
}
|
||||
|
||||
// Метод для создания нового хеша на основе указанного алгоритма хеширования
|
||||
func (opts Options) NewHash() (hash.Hash, error) {
|
||||
switch opts.HashAlgorithm {
|
||||
case "", "sha256":
|
||||
|
@ -119,49 +120,26 @@ func (opts Options) NewHash() (hash.Hash, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Manifest holds information about the type and name
|
||||
// of a downloaded file or directory. It is stored inside
|
||||
// each cache directory for later use.
|
||||
// Структура Manifest хранит информацию о типе и имени загруженного файла или каталога
|
||||
type Manifest struct {
|
||||
Type Type
|
||||
Name string
|
||||
}
|
||||
|
||||
// Интерфейс Downloader для реализации различных загрузчиков
|
||||
type Downloader interface {
|
||||
// Name returns the name of the downloader
|
||||
Name() string
|
||||
// MatchURL checks if the given URL matches
|
||||
// the downloader.
|
||||
MatchURL(string) bool
|
||||
// Download downloads the object at the URL
|
||||
// provided in the options, to the destination
|
||||
// given in the options. It returns a type,
|
||||
// a name for the downloaded object (this may be empty),
|
||||
// and an error.
|
||||
Download(Options) (Type, string, error)
|
||||
}
|
||||
|
||||
// UpdatingDownloader extends the Downloader interface
|
||||
// with an Update method for protocols such as git, which
|
||||
// allow for incremental updates without changing the URL.
|
||||
// Интерфейс UpdatingDownloader расширяет Downloader методом Update
|
||||
type UpdatingDownloader interface {
|
||||
Downloader
|
||||
// Update checks for and performs any
|
||||
// available updates for the object
|
||||
// described in the options. It returns
|
||||
// true if an update was performed, or
|
||||
// false if no update was required.
|
||||
Update(Options) (bool, error)
|
||||
}
|
||||
|
||||
// Download downloads a file or directory using the specified options.
|
||||
// It first gets the appropriate downloader for the URL, then checks
|
||||
// if caching is enabled. If caching is enabled, it attempts to get
|
||||
// the cache directory for the URL and update it if necessary.
|
||||
// If the source is found in the cache, it links it to the destination
|
||||
// using hard links. If the source is not found in the cache,
|
||||
// it downloads the source to a new cache directory and links it
|
||||
// to the destination.
|
||||
// Функция Download загружает файл или каталог с использованием указанных параметров
|
||||
func Download(ctx context.Context, opts Options) (err error) {
|
||||
log := loggerctx.From(ctx)
|
||||
normalized, err := normalizeURL(opts.URL)
|
||||
|
@ -216,9 +194,6 @@ func Download(ctx context.Context, opts Options) (err error) {
|
|||
return nil
|
||||
}
|
||||
} else {
|
||||
// If we cannot read the manifest,
|
||||
// this cache entry is invalid and
|
||||
// the source must be re-downloaded.
|
||||
err = os.RemoveAll(cacheDir)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -256,7 +231,7 @@ func Download(ctx context.Context, opts Options) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// writeManifest writes the manifest to the specified cache directory.
|
||||
// Функция writeManifest записывает манифест в указанный каталог кэша
|
||||
func writeManifest(cacheDir string, m Manifest) error {
|
||||
fl, err := os.Create(filepath.Join(cacheDir, manifestFileName))
|
||||
if err != nil {
|
||||
|
@ -266,7 +241,7 @@ func writeManifest(cacheDir string, m Manifest) error {
|
|||
return msgpack.NewEncoder(fl).Encode(m)
|
||||
}
|
||||
|
||||
// getManifest reads the manifest from the specified cache directory.
|
||||
// Функция getManifest считывает манифест из указанного каталога кэша
|
||||
func getManifest(cacheDir string) (m Manifest, err error) {
|
||||
fl, err := os.Open(filepath.Join(cacheDir, manifestFileName))
|
||||
if err != nil {
|
||||
|
@ -278,7 +253,7 @@ func getManifest(cacheDir string) (m Manifest, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// handleCache links the cache directory or a file within it to the destination
|
||||
// Функция handleCache создает жесткие ссылки для файлов из каталога кэша в каталог назначения
|
||||
func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
|
||||
switch t {
|
||||
case TypeFile:
|
||||
|
@ -313,12 +288,7 @@ func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
// linkDir recursively walks through a directory, creating
|
||||
// hard links for each file from the src directory to the
|
||||
// dest directory. If it encounters a directory, it will
|
||||
// create a directory with the same name and permissions
|
||||
// in the dest directory, because hard links cannot be
|
||||
// created for directories.
|
||||
// Функция linkDir рекурсивно создает жесткие ссылки для файлов из каталога src в каталог dest
|
||||
func linkDir(src, dest string) error {
|
||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
|
@ -329,6 +299,8 @@ func linkDir(src, dest string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
rel, err := filepath.Rel(src, path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -343,6 +315,7 @@ func linkDir(src, dest string) error {
|
|||
})
|
||||
}
|
||||
|
||||
// Функция getDownloader возвращает загрузчик, соответствующий URL
|
||||
func getDownloader(u string) Downloader {
|
||||
for _, d := range Downloaders {
|
||||
if d.MatchURL(u) {
|
||||
|
@ -352,8 +325,7 @@ func getDownloader(u string) Downloader {
|
|||
return nil
|
||||
}
|
||||
|
||||
// normalizeURL normalizes a URL string, so that insignificant
|
||||
// differences don't change the hash.
|
||||
// Функция normalizeURL нормализует строку URL, чтобы незначительные различия не изменяли хеш
|
||||
func normalizeURL(u string) (string, error) {
|
||||
const normalizationFlags = purell.FlagRemoveTrailingSlash |
|
||||
purell.FlagRemoveDefaultPort |
|
||||
|
@ -373,7 +345,7 @@ func normalizeURL(u string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
// Fix magnet URLs after normalization
|
||||
// Исправление URL-адресов magnet после нормализации
|
||||
u = strings.Replace(u, "magnet://", "magnet:", 1)
|
||||
return u, nil
|
||||
}
|
||||
|
|
|
@ -36,40 +36,47 @@ import (
|
|||
"plemya-x.ru/alr/internal/shutils/handlers"
|
||||
)
|
||||
|
||||
// FileDownloader downloads files using HTTP
|
||||
// FileDownloader загружает файлы с использованием HTTP
|
||||
type FileDownloader struct{}
|
||||
|
||||
// Name always returns "file"
|
||||
// Name всегда возвращает "file"
|
||||
func (FileDownloader) Name() string {
|
||||
return "file"
|
||||
}
|
||||
|
||||
// MatchURL always returns true, as FileDownloader
|
||||
// is used as a fallback if nothing else matches
|
||||
// MatchURL всегда возвращает true, так как FileDownloader
|
||||
// используется как резерв, если ничего другого не соответствует
|
||||
func (FileDownloader) MatchURL(string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Download downloads a file using HTTP. If the file is
|
||||
// compressed using a supported format, it will be extracted
|
||||
// Download загружает файл с использованием HTTP. Если файл
|
||||
// сжат в поддерживаемом формате, он будет распакован
|
||||
func (FileDownloader) Download(opts Options) (Type, string, error) {
|
||||
// Разбор URL
|
||||
u, err := url.Parse(opts.URL)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
// Получение параметров запроса
|
||||
query := u.Query()
|
||||
|
||||
// Получение имени файла из параметров запроса
|
||||
name := query.Get("~name")
|
||||
query.Del("~name")
|
||||
|
||||
// Получение параметра архивации
|
||||
archive := query.Get("~archive")
|
||||
query.Del("~archive")
|
||||
|
||||
// Кодирование измененных параметров запроса обратно в URL
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
var r io.ReadCloser
|
||||
var size int64
|
||||
|
||||
// Проверка схемы URL на "local"
|
||||
if u.Scheme == "local" {
|
||||
localFl, err := os.Open(filepath.Join(opts.LocalDir, u.Path))
|
||||
if err != nil {
|
||||
|
@ -85,6 +92,7 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
|
|||
}
|
||||
r = localFl
|
||||
} else {
|
||||
// Выполнение HTTP GET запроса
|
||||
res, err := http.Get(u.String())
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
|
@ -107,6 +115,7 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
|
|||
defer fl.Close()
|
||||
|
||||
var bar io.WriteCloser
|
||||
// Настройка индикатора прогресса
|
||||
if opts.Progress != nil {
|
||||
bar = progressbar.NewOptions64(
|
||||
size,
|
||||
|
@ -134,18 +143,21 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
|
|||
}
|
||||
|
||||
var w io.Writer
|
||||
// Настройка MultiWriter для записи в файл, хеш и индикатор прогресса
|
||||
if opts.Hash != nil {
|
||||
w = io.MultiWriter(fl, h, bar)
|
||||
} else {
|
||||
w = io.MultiWriter(fl, bar)
|
||||
}
|
||||
|
||||
// Копирование содержимого из источника в файл назначения
|
||||
_, err = io.Copy(w, r)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
r.Close()
|
||||
|
||||
// Проверка контрольной суммы
|
||||
if opts.Hash != nil {
|
||||
sum := h.Sum(nil)
|
||||
if !bytes.Equal(sum, opts.Hash) {
|
||||
|
@ -153,6 +165,7 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Проверка необходимости постобработки
|
||||
if opts.PostprocDisabled {
|
||||
return TypeFile, name, nil
|
||||
}
|
||||
|
@ -162,6 +175,7 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
|
|||
return 0, "", err
|
||||
}
|
||||
|
||||
// Идентификация формата архива
|
||||
format, ar, err := archiver.Identify(name, fl)
|
||||
if err == archiver.ErrNoMatch {
|
||||
return TypeFile, name, nil
|
||||
|
@ -169,21 +183,25 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
|
|||
return 0, "", err
|
||||
}
|
||||
|
||||
// Распаковка архива
|
||||
err = extractFile(ar, format, name, opts)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
// Удаление исходного архива
|
||||
err = os.Remove(path)
|
||||
return TypeDir, "", err
|
||||
}
|
||||
|
||||
// extractFile extracts an archive or decompresses a file
|
||||
// extractFile извлекает архив или распаковывает файл
|
||||
func extractFile(r io.Reader, format archiver.Format, name string, opts Options) (err error) {
|
||||
fname := format.Name()
|
||||
|
||||
// Проверка типа формата архива
|
||||
switch format := format.(type) {
|
||||
case archiver.Extractor:
|
||||
// Извлечение файлов из архива
|
||||
err = format.Extract(context.Background(), r, nil, func(ctx context.Context, f archiver.File) error {
|
||||
fr, err := f.Open()
|
||||
if err != nil {
|
||||
|
@ -224,6 +242,7 @@ func extractFile(r io.Reader, format archiver.Format, name string, opts Options)
|
|||
return err
|
||||
}
|
||||
case archiver.Decompressor:
|
||||
// Распаковка сжатого файла
|
||||
rc, err := format.OpenReader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -247,10 +266,9 @@ func extractFile(r io.Reader, format archiver.Format, name string, opts Options)
|
|||
return nil
|
||||
}
|
||||
|
||||
// getFilename attempts to parse the Content-Disposition
|
||||
// HTTP response header and extract a filename. If the
|
||||
// header does not exist, it will use the last element
|
||||
// of the path.
|
||||
// getFilename пытается разобрать заголовок Content-Disposition
|
||||
// HTTP-ответа и извлечь имя файла. Если заголовок отсутствует,
|
||||
// используется последний элемент пути.
|
||||
func getFilename(res *http.Response) (name string) {
|
||||
_, params, err := mime.ParseMediaType(res.Header.Get("Content-Disposition"))
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue