0
0
Fork 0
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:
Евгений Храмов 2024-07-10 13:10:14 +03:00
parent 47d1d97496
commit 93e82b43a7
2 changed files with 69 additions and 79 deletions

View file

@ -1,23 +1,23 @@
/* /*
* ALR - Any Linux Repository * ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов * Copyright (C) 2024 Евгений Храмов
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// Package dl contains abstractions for downloadingfiles and directories // Пакет dl содержит абстракции для загрузки файлов и каталогов
// from various sources. // из различных источников.
package dl package dl
import ( import (
@ -43,31 +43,32 @@ import (
"plemya-x.ru/alr/pkg/loggerctx" "plemya-x.ru/alr/pkg/loggerctx"
) )
// Константа для имени файла манифеста кэша
const manifestFileName = ".alr_cache_manifest" 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 ( var (
ErrChecksumMismatch = errors.New("dl: checksums did not match") ErrChecksumMismatch = errors.New("dl: checksums did not match")
ErrNoSuchHashAlgo = errors.New("dl: invalid hashing algorithm") ErrNoSuchHashAlgo = errors.New("dl: invalid hashing algorithm")
) )
// Downloaders contains all the downloaders in the order in which // Массив доступных загрузчиков в порядке их проверки
// they should be checked
var Downloaders = []Downloader{ var Downloaders = []Downloader{
GitDownloader{}, GitDownloader{},
TorrentDownloader{}, TorrentDownloader{},
FileDownloader{}, FileDownloader{},
} }
// Type represents the type of download (file or directory) // Тип данных, представляющий тип загрузки (файл или каталог)
type Type uint8 type Type uint8
// Объявление констант для типов загрузки
const ( const (
TypeFile Type = iota TypeFile Type = iota
TypeDir TypeDir
) )
// Метод для получения строки, представляющей тип загрузки
func (t Type) String() string { func (t Type) String() string {
switch t { switch t {
case TypeFile: case TypeFile:
@ -78,8 +79,7 @@ func (t Type) String() string {
return "<unknown>" return "<unknown>"
} }
// Options contains the options for downloading // Структура Options содержит параметры для загрузки файлов и каталогов
// files and directories
type Options struct { type Options struct {
Hash []byte Hash []byte
HashAlgorithm string HashAlgorithm string
@ -92,6 +92,7 @@ type Options struct {
LocalDir string LocalDir string
} }
// Метод для создания нового хеша на основе указанного алгоритма хеширования
func (opts Options) NewHash() (hash.Hash, error) { func (opts Options) NewHash() (hash.Hash, error) {
switch opts.HashAlgorithm { switch opts.HashAlgorithm {
case "", "sha256": case "", "sha256":
@ -119,49 +120,26 @@ func (opts Options) NewHash() (hash.Hash, error) {
} }
} }
// Manifest holds information about the type and name // Структура Manifest хранит информацию о типе и имени загруженного файла или каталога
// of a downloaded file or directory. It is stored inside
// each cache directory for later use.
type Manifest struct { type Manifest struct {
Type Type Type Type
Name string Name string
} }
// Интерфейс Downloader для реализации различных загрузчиков
type Downloader interface { type Downloader interface {
// Name returns the name of the downloader
Name() string Name() string
// MatchURL checks if the given URL matches
// the downloader.
MatchURL(string) bool 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) Download(Options) (Type, string, error)
} }
// UpdatingDownloader extends the Downloader interface // Интерфейс UpdatingDownloader расширяет Downloader методом Update
// with an Update method for protocols such as git, which
// allow for incremental updates without changing the URL.
type UpdatingDownloader interface { type UpdatingDownloader interface {
Downloader 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) Update(Options) (bool, error)
} }
// Download downloads a file or directory using the specified options. // Функция Download загружает файл или каталог с использованием указанных параметров
// 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.
func Download(ctx context.Context, opts Options) (err error) { func Download(ctx context.Context, opts Options) (err error) {
log := loggerctx.From(ctx) log := loggerctx.From(ctx)
normalized, err := normalizeURL(opts.URL) normalized, err := normalizeURL(opts.URL)
@ -216,9 +194,6 @@ func Download(ctx context.Context, opts Options) (err error) {
return nil return nil
} }
} else { } else {
// If we cannot read the manifest,
// this cache entry is invalid and
// the source must be re-downloaded.
err = os.RemoveAll(cacheDir) err = os.RemoveAll(cacheDir)
if err != nil { if err != nil {
return err return err
@ -256,7 +231,7 @@ func Download(ctx context.Context, opts Options) (err error) {
return err return err
} }
// writeManifest writes the manifest to the specified cache directory. // Функция writeManifest записывает манифест в указанный каталог кэша
func writeManifest(cacheDir string, m Manifest) error { func writeManifest(cacheDir string, m Manifest) error {
fl, err := os.Create(filepath.Join(cacheDir, manifestFileName)) fl, err := os.Create(filepath.Join(cacheDir, manifestFileName))
if err != nil { if err != nil {
@ -266,7 +241,7 @@ func writeManifest(cacheDir string, m Manifest) error {
return msgpack.NewEncoder(fl).Encode(m) return msgpack.NewEncoder(fl).Encode(m)
} }
// getManifest reads the manifest from the specified cache directory. // Функция getManifest считывает манифест из указанного каталога кэша
func getManifest(cacheDir string) (m Manifest, err error) { func getManifest(cacheDir string) (m Manifest, err error) {
fl, err := os.Open(filepath.Join(cacheDir, manifestFileName)) fl, err := os.Open(filepath.Join(cacheDir, manifestFileName))
if err != nil { if err != nil {
@ -278,7 +253,7 @@ func getManifest(cacheDir string) (m Manifest, err error) {
return 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) { func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
switch t { switch t {
case TypeFile: case TypeFile:
@ -313,12 +288,7 @@ func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
return false, nil return false, nil
} }
// linkDir recursively walks through a directory, creating // Функция linkDir рекурсивно создает жесткие ссылки для файлов из каталога src в каталог dest
// 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.
func linkDir(src, dest string) error { func linkDir(src, dest string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
@ -329,6 +299,8 @@ func linkDir(src, dest string) error {
return nil return nil
} }
rel, err := filepath.Rel(src, path) rel, err := filepath.Rel(src, path)
if err != nil { if err != nil {
return err return err
@ -343,6 +315,7 @@ func linkDir(src, dest string) error {
}) })
} }
// Функция getDownloader возвращает загрузчик, соответствующий URL
func getDownloader(u string) Downloader { func getDownloader(u string) Downloader {
for _, d := range Downloaders { for _, d := range Downloaders {
if d.MatchURL(u) { if d.MatchURL(u) {
@ -352,8 +325,7 @@ func getDownloader(u string) Downloader {
return nil return nil
} }
// normalizeURL normalizes a URL string, so that insignificant // Функция normalizeURL нормализует строку URL, чтобы незначительные различия не изменяли хеш
// differences don't change the hash.
func normalizeURL(u string) (string, error) { func normalizeURL(u string) (string, error) {
const normalizationFlags = purell.FlagRemoveTrailingSlash | const normalizationFlags = purell.FlagRemoveTrailingSlash |
purell.FlagRemoveDefaultPort | purell.FlagRemoveDefaultPort |
@ -373,7 +345,7 @@ func normalizeURL(u string) (string, error) {
return "", err return "", err
} }
// Fix magnet URLs after normalization // Исправление URL-адресов magnet после нормализации
u = strings.Replace(u, "magnet://", "magnet:", 1) u = strings.Replace(u, "magnet://", "magnet:", 1)
return u, nil return u, nil
} }

View file

@ -36,40 +36,47 @@ import (
"plemya-x.ru/alr/internal/shutils/handlers" "plemya-x.ru/alr/internal/shutils/handlers"
) )
// FileDownloader downloads files using HTTP // FileDownloader загружает файлы с использованием HTTP
type FileDownloader struct{} type FileDownloader struct{}
// Name always returns "file" // Name всегда возвращает "file"
func (FileDownloader) Name() string { func (FileDownloader) Name() string {
return "file" return "file"
} }
// MatchURL always returns true, as FileDownloader // MatchURL всегда возвращает true, так как FileDownloader
// is used as a fallback if nothing else matches // используется как резерв, если ничего другого не соответствует
func (FileDownloader) MatchURL(string) bool { func (FileDownloader) MatchURL(string) bool {
return true return true
} }
// Download downloads a file using HTTP. If the file is // Download загружает файл с использованием HTTP. Если файл
// compressed using a supported format, it will be extracted // сжат в поддерживаемом формате, он будет распакован
func (FileDownloader) Download(opts Options) (Type, string, error) { func (FileDownloader) Download(opts Options) (Type, string, error) {
// Разбор URL
u, err := url.Parse(opts.URL) u, err := url.Parse(opts.URL)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
// Получение параметров запроса
query := u.Query() query := u.Query()
// Получение имени файла из параметров запроса
name := query.Get("~name") name := query.Get("~name")
query.Del("~name") query.Del("~name")
// Получение параметра архивации
archive := query.Get("~archive") archive := query.Get("~archive")
query.Del("~archive") query.Del("~archive")
// Кодирование измененных параметров запроса обратно в URL
u.RawQuery = query.Encode() u.RawQuery = query.Encode()
var r io.ReadCloser var r io.ReadCloser
var size int64 var size int64
// Проверка схемы URL на "local"
if u.Scheme == "local" { if u.Scheme == "local" {
localFl, err := os.Open(filepath.Join(opts.LocalDir, u.Path)) localFl, err := os.Open(filepath.Join(opts.LocalDir, u.Path))
if err != nil { if err != nil {
@ -85,6 +92,7 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
} }
r = localFl r = localFl
} else { } else {
// Выполнение HTTP GET запроса
res, err := http.Get(u.String()) res, err := http.Get(u.String())
if err != nil { if err != nil {
return 0, "", err return 0, "", err
@ -107,6 +115,7 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
defer fl.Close() defer fl.Close()
var bar io.WriteCloser var bar io.WriteCloser
// Настройка индикатора прогресса
if opts.Progress != nil { if opts.Progress != nil {
bar = progressbar.NewOptions64( bar = progressbar.NewOptions64(
size, size,
@ -134,18 +143,21 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
} }
var w io.Writer var w io.Writer
// Настройка MultiWriter для записи в файл, хеш и индикатор прогресса
if opts.Hash != nil { if opts.Hash != nil {
w = io.MultiWriter(fl, h, bar) w = io.MultiWriter(fl, h, bar)
} else { } else {
w = io.MultiWriter(fl, bar) w = io.MultiWriter(fl, bar)
} }
// Копирование содержимого из источника в файл назначения
_, err = io.Copy(w, r) _, err = io.Copy(w, r)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
r.Close() r.Close()
// Проверка контрольной суммы
if opts.Hash != nil { if opts.Hash != nil {
sum := h.Sum(nil) sum := h.Sum(nil)
if !bytes.Equal(sum, opts.Hash) { if !bytes.Equal(sum, opts.Hash) {
@ -153,6 +165,7 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
} }
} }
// Проверка необходимости постобработки
if opts.PostprocDisabled { if opts.PostprocDisabled {
return TypeFile, name, nil return TypeFile, name, nil
} }
@ -162,6 +175,7 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
return 0, "", err return 0, "", err
} }
// Идентификация формата архива
format, ar, err := archiver.Identify(name, fl) format, ar, err := archiver.Identify(name, fl)
if err == archiver.ErrNoMatch { if err == archiver.ErrNoMatch {
return TypeFile, name, nil return TypeFile, name, nil
@ -169,21 +183,25 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
return 0, "", err return 0, "", err
} }
// Распаковка архива
err = extractFile(ar, format, name, opts) err = extractFile(ar, format, name, opts)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
// Удаление исходного архива
err = os.Remove(path) err = os.Remove(path)
return TypeDir, "", err 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) { func extractFile(r io.Reader, format archiver.Format, name string, opts Options) (err error) {
fname := format.Name() fname := format.Name()
// Проверка типа формата архива
switch format := format.(type) { switch format := format.(type) {
case archiver.Extractor: case archiver.Extractor:
// Извлечение файлов из архива
err = format.Extract(context.Background(), r, nil, func(ctx context.Context, f archiver.File) error { err = format.Extract(context.Background(), r, nil, func(ctx context.Context, f archiver.File) error {
fr, err := f.Open() fr, err := f.Open()
if err != nil { if err != nil {
@ -224,6 +242,7 @@ func extractFile(r io.Reader, format archiver.Format, name string, opts Options)
return err return err
} }
case archiver.Decompressor: case archiver.Decompressor:
// Распаковка сжатого файла
rc, err := format.OpenReader(r) rc, err := format.OpenReader(r)
if err != nil { if err != nil {
return err return err
@ -247,10 +266,9 @@ func extractFile(r io.Reader, format archiver.Format, name string, opts Options)
return nil return nil
} }
// getFilename attempts to parse the Content-Disposition // getFilename пытается разобрать заголовок Content-Disposition
// HTTP response header and extract a filename. If the // HTTP-ответа и извлечь имя файла. Если заголовок отсутствует,
// header does not exist, it will use the last element // используется последний элемент пути.
// of the path.
func getFilename(res *http.Response) (name string) { func getFilename(res *http.Response) (name string) {
_, params, err := mime.ParseMediaType(res.Header.Get("Content-Disposition")) _, params, err := mime.ParseMediaType(res.Header.Get("Content-Disposition"))
if err != nil { if err != nil {