From 93e82b43a754b928c484fbe4ab8d818c97250ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=A5=D1=80?= =?UTF-8?q?=D0=B0=D0=BC=D0=BE=D0=B2?= Date: Wed, 10 Jul 2024 13:10:14 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=D1=8B?= =?UTF-8?q?=20=D0=B8=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/dl/dl.go | 106 ++++++++++++++++---------------------------- internal/dl/file.go | 42 +++++++++++++----- 2 files changed, 69 insertions(+), 79 deletions(-) diff --git a/internal/dl/dl.go b/internal/dl/dl.go index 784d056..a2a6781 100644 --- a/internal/dl/dl.go +++ b/internal/dl/dl.go @@ -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 . - */ +* 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 . +*/ -// 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 "" } -// 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 } diff --git a/internal/dl/file.go b/internal/dl/file.go index f3a80f5..a3da657 100644 --- a/internal/dl/file.go +++ b/internal/dl/file.go @@ -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 { @@ -261,4 +279,4 @@ func getFilename(res *http.Response) (name string) { } else { return path.Base(res.Request.URL.Path) } -} +} \ No newline at end of file