utils: downloads: make extraction optional, i18n: init Hindi

Thanks Abitha V.K for the hindi translation
This commit is contained in:
hayzamjs
2025-11-20 02:37:18 +05:30
parent c989b5f748
commit aa7364fe62
8 changed files with 133 additions and 63 deletions
-1
View File
@@ -66,7 +66,6 @@ We also need to enable some services in order to run Sylve, you can drop these i
sysrc ntpd_enable="YES" # Optional
sysrc ntpd_sync_on_start="YES" # Optional
sysrc zfs_enable="YES"
sysrc linux_enable="YES" # Optional
sysrc libvirtd_enable="YES"
sysrc dnsmasq_enable="YES"
sysrc rpcbind_enable="YES"
+3
View File
@@ -92,6 +92,7 @@ func SetupDataPath() error {
filepath.Join(dataPath, "downloads"),
filepath.Join(dataPath, "downloads", "torrents"),
filepath.Join(dataPath, "downloads", "http"),
filepath.Join(dataPath, "downloads", "path"),
filepath.Join(dataPath, "downloads", "extracted"),
}
@@ -122,6 +123,8 @@ func GetDownloadsPath(dType string) string {
return filepath.Join(ParsedConfig.DataPath, "downloads", "torrents", "torrent.db")
case "http":
return filepath.Join(ParsedConfig.DataPath, "downloads", "http")
case "path":
return filepath.Join(ParsedConfig.DataPath, "downloads", "path")
case "extracted":
return filepath.Join(ParsedConfig.DataPath, "downloads", "extracted")
}
+9 -1
View File
@@ -19,6 +19,14 @@ const (
DownloadStatusFailed DownloadStatus = "failed"
)
type DownloadType string
const (
DownloadTypeHTTP DownloadType = "http"
DownloadTypeTorrent DownloadType = "torrent"
DownloadTypePath DownloadType = "path"
)
type DownloadedFile struct {
ID int `json:"id" gorm:"primaryKey"`
DownloadID int `json:"downloadId" gorm:"not null"`
@@ -32,7 +40,7 @@ type Downloads struct {
UUID string `json:"uuid" gorm:"unique;not null"`
Path string `json:"path" gorm:"unique;not null"`
Name string `json:"name" gorm:"not null"`
Type string `json:"type" gorm:"not null"`
Type DownloadType `json:"type" gorm:"not null"`
URL string `json:"url" gorm:"unique;not null"`
Progress int `json:"progress" gorm:"not null"`
Size int64 `json:"size" gorm:"not null"`
+12 -4
View File
@@ -25,9 +25,10 @@ import (
)
type DownloadFileRequest struct {
URL string `json:"url" binding:"required"`
Filename *string `json:"filename"`
IgnoreTLS *bool `json:"ignoreTLS"`
URL string `json:"url" binding:"required"`
Filename *string `json:"filename"`
IgnoreTLS *bool `json:"ignoreTLS"`
AutomaticExtraction *bool `json:"automaticExtraction"`
}
type BulkDeleteDownloadRequest struct {
@@ -108,7 +109,14 @@ func DownloadFile(utilitiesService *utilities.Service) gin.HandlerFunc {
insecureOkay = false
}
if err := utilitiesService.DownloadFile(request.URL, fileName, insecureOkay); err != nil {
var automaticExtraction bool
if request.AutomaticExtraction != nil && *request.AutomaticExtraction {
automaticExtraction = true
} else {
automaticExtraction = false
}
if err := utilitiesService.DownloadFile(request.URL, fileName, insecureOkay, automaticExtraction); err != nil {
c.JSON(http.StatusInternalServerError, internal.APIResponse[any]{
Status: "error",
Message: "failed_to_download_file",
@@ -11,7 +11,7 @@ package utilitiesServiceInterfaces
import utilitiesModels "github.com/alchemillahq/sylve/internal/db/models/utilities"
type UtilitiesServiceInterface interface {
DownloadFile(url string, optFilename string, insecureOkay bool) error
DownloadFile(url string, optFilename string, insecureOkay bool, automaticExtraction bool) error
ListDownloads() ([]utilitiesModels.Downloads, error)
GetMagnetDownloadAndFile(uuid, name string) (*utilitiesModels.Downloads, *utilitiesModels.DownloadedFile, error)
SyncDownloadProgress() error
+48 -32
View File
@@ -98,7 +98,7 @@ func (s *Service) GetFilePathById(uuid string, id int) (string, error) {
return "", fmt.Errorf("unsupported_download_type")
}
func (s *Service) DownloadFile(url string, optFilename string, insecureOkay bool) error {
func (s *Service) DownloadFile(url string, optFilename string, insecureOkay bool, automaticExtraction bool) error {
var existing utilitiesModels.Downloads
if s.DB.Where("url = ?", url).First(&existing).RowsAffected > 0 {
@@ -120,15 +120,16 @@ func (s *Service) DownloadFile(url string, optFilename string, insecureOkay bool
}
download := utilitiesModels.Downloads{
URL: url,
UUID: t.ID(),
Path: t.Dir(),
Type: "torrent",
Name: t.Name(),
Size: 0,
Progress: 0,
Files: []utilitiesModels.DownloadedFile{},
Status: utilitiesModels.DownloadStatusPending,
URL: url,
UUID: t.ID(),
Path: t.Dir(),
Type: utilitiesModels.DownloadTypeTorrent,
Name: t.Name(),
Size: 0,
Progress: 0,
Files: []utilitiesModels.DownloadedFile{},
Status: utilitiesModels.DownloadStatusPending,
AutomaticExtraction: false,
}
if err := s.DB.Create(&download).Error; err != nil {
@@ -180,7 +181,7 @@ func (s *Service) DownloadFile(url string, optFilename string, insecureOkay bool
URL: url,
UUID: uuid,
Path: filePath,
Type: "http",
Type: utilitiesModels.DownloadTypeHTTP,
Name: filename,
Size: size,
Progress: 100,
@@ -199,15 +200,16 @@ func (s *Service) DownloadFile(url string, optFilename string, insecureOkay bool
}
download := utilitiesModels.Downloads{
URL: url,
UUID: uuid,
Path: filePath,
Type: "http",
Name: filename,
Size: 0,
Progress: 0,
Files: []utilitiesModels.DownloadedFile{},
Status: utilitiesModels.DownloadStatusPending,
URL: url,
UUID: uuid,
Path: filePath,
Type: utilitiesModels.DownloadTypeHTTP,
Name: filename,
Size: 0,
Progress: 0,
Files: []utilitiesModels.DownloadedFile{},
Status: utilitiesModels.DownloadStatusPending,
AutomaticExtraction: automaticExtraction,
}
if err := s.DB.Create(&download).Error; err != nil {
@@ -268,15 +270,16 @@ func (s *Service) DownloadFile(url string, optFilename string, insecureOkay bool
logger.L.Info().Msgf("Copied file %s to %s (%d bytes)", url, destPath, size)
download := utilitiesModels.Downloads{
URL: url,
UUID: utils.GenerateDeterministicUUID(url),
Path: destPath,
Type: "http",
Name: filename,
Size: size,
Progress: 100,
Files: []utilitiesModels.DownloadedFile{},
Status: utilitiesModels.DownloadStatusDone,
URL: url,
UUID: utils.GenerateDeterministicUUID(url),
Path: destPath,
Type: utilitiesModels.DownloadTypePath,
Name: filename,
Size: size,
Progress: 100,
Files: []utilitiesModels.DownloadedFile{},
Status: utilitiesModels.DownloadStatusDone,
AutomaticExtraction: false,
}
if err := s.DB.Create(&download).Error; err != nil {
@@ -352,6 +355,10 @@ func (s *Service) postProcessOne(id uint) error {
return nil
}
if !d.AutomaticExtraction {
return s.finishDownload(&d, "", "")
}
// Prepare extract dir
extractsPath := filepath.Join(config.GetDownloadsPath("extracted"), d.UUID)
if err := utils.ResetDir(extractsPath); err != nil {
@@ -459,9 +466,9 @@ func (s *Service) SyncDownloadProgress() error {
for _, d := range downloads {
switch d.Type {
case "torrent":
case utilitiesModels.DownloadTypeTorrent:
s.syncTorrent(&d)
case "http":
case utilitiesModels.DownloadTypeHTTP:
s.syncHTTP(&d)
default:
logger.L.Warn().Msgf("Unknown download type: %s", d.Type)
@@ -592,7 +599,16 @@ func (s *Service) DeleteDownload(id int) error {
}
}
err := utils.DeleteFile(path.Join(config.GetDownloadsPath(download.Type), download.Name))
var dType string
if download.Type == utilitiesModels.DownloadTypeHTTP {
dType = "http"
} else if download.Type == utilitiesModels.DownloadTypePath {
dType = "path"
} else if download.Type == utilitiesModels.DownloadTypeTorrent {
dType = "torrent"
}
err := utils.DeleteFile(path.Join(config.GetDownloadsPath(dType), download.Name))
if err != nil {
logger.L.Debug().Msgf("Failed to delete HTTP download file: %v", err)
return err
+4 -2
View File
@@ -9,12 +9,14 @@ export async function getDownloads(): Promise<Download[]> {
export async function startDownload(
url: string,
filename?: string,
ignoreTLS?: boolean
ignoreTLS?: boolean,
automaticExtraction?: boolean
): Promise<APIResponse> {
return await apiRequest('/utilities/downloads', APIResponseSchema, 'POST', {
url,
filename,
ignoreTLS
ignoreTLS,
automaticExtraction
});
}
@@ -24,7 +24,7 @@
isValidFileName
} from '$lib/utils/string';
import { generateTableData } from '$lib/utils/utilities/downloader';
import { useQueries } from '@sveltestack/svelte-query';
import { useQueries, useQueryClient } from '@sveltestack/svelte-query';
import { toast } from 'svelte-sonner';
import isMagnet from 'validator/lib/isMagnetURI';
@@ -33,13 +33,15 @@
}
let { data }: { data: Data } = $props();
const queryClient = useQueryClient();
const results = useQueries([
{
queryKey: ['downloads'],
queryKey: 'downloads',
queryFn: async () => {
return await getDownloads();
},
refetchInterval: 1000,
refetchInterval: false,
keepPreviousData: true,
initialData: data.downloads,
onSuccess: (data: Download[]) => {
@@ -48,15 +50,28 @@
}
]);
let modalState = $state({
let reload = $state(false);
$effect(() => {
if (reload) {
queryClient.invalidateQueries('downloads');
reload = false;
}
});
let options = {
isOpen: false,
isDelete: false,
isBulkDelete: false,
title: '',
url: '',
name: '',
ignoreTLS: false
});
ignoreTLS: false,
automaticExtraction: false,
loading: false
};
let modalState = $state(options);
let downloads = $derived($results[0].data as Download[]);
let tableData = $derived(generateTableData(downloads));
@@ -132,14 +147,18 @@
return;
}
modalState.loading = true;
const result = await startDownload(
modalState.url,
modalState.name || undefined,
modalState.ignoreTLS
modalState.ignoreTLS,
modalState.automaticExtraction
);
if (result) {
modalState.isOpen = false;
modalState.url = '';
modalState = options;
reload = true;
toast.success('Download started', { position: 'bottom-center' });
} else {
toast.error('Download failed', { position: 'bottom-center' });
@@ -280,9 +299,11 @@
<CustomValueInput
label={'Magnet / HTTP URL / Path'}
placeholder="magnet:?xt=urn:btih:7d5210a711291d7181d6e074ce5ebd56f3fedd60&dn=debian-12.10.0-amd64-netinst.iso&xl=663748608&tr=http%3A%2F%2Fbttracker.debian.org%3A6969%2Fannounce"
placeholder="magnet:?xt=urn:btih:7d5210a711291d7181d6e074ce5ebd56f3fedd60"
bind:value={modalState.url}
classes="flex-1 space-y-1"
type="textarea"
textAreaClasses="h-24 w-full"
/>
{#if modalState.url && isDownloadURL(modalState.url)}
@@ -294,17 +315,30 @@
classes="flex-1 space-y-1 mt-2"
/>
<CustomCheckbox
label="Ignore TLS Errors"
bind:checked={modalState.ignoreTLS}
classes="flex items-center gap-2"
/>
<div class="mt-2 flex flex-row gap-2">
<CustomCheckbox
label="Ignore TLS Errors"
bind:checked={modalState.ignoreTLS}
classes="flex items-center gap-2"
/>
<CustomCheckbox
label="Extract Automatically"
bind:checked={modalState.automaticExtraction}
classes="flex items-center gap-2"
/>
</div>
</div>
{/if}
<Dialog.Footer class="flex justify-end">
<div class="flex w-full items-center justify-end gap-2 py-2">
<Button onclick={newDownload} type="submit" size="sm">Download</Button>
<Button onclick={newDownload} type="submit" size="sm">
{#if modalState.loading}
<span class="icon-[mdi--loading] h-4 w-4 animate-spin"></span>
{:else}
<span>Download</span>
{/if}
</Button>
</div>
</Dialog.Footer>
</Dialog.Content>
@@ -325,9 +359,9 @@
onConfirm: async () => {
const id = activeRows ? activeRows[0]?.id : null;
const result = await deleteDownload(id as number);
reload = true;
if (isAPIResponse(result) && result.status === 'success') {
modalState.isDelete = false;
modalState.title = '';
modalState = options;
activeRows = null;
} else {
handleAPIError(result as APIResponse);
@@ -335,7 +369,7 @@
}
},
onCancel: () => {
modalState.isDelete = false;
modalState = options;
modalState.title = '';
}
}}
@@ -348,9 +382,9 @@
onConfirm: async () => {
const ids = activeRows ? activeRows.map((row) => row.id) : [];
const result = await bulkDeleteDownloads(ids as number[]);
reload = true;
if (isAPIResponse(result) && result.status === 'success') {
modalState.isBulkDelete = false;
modalState.title = '';
modalState = options;
activeRows = null;
} else {
handleAPIError(result as APIResponse);
@@ -358,7 +392,7 @@
}
},
onCancel: () => {
modalState.isBulkDelete = false;
modalState = options;
modalState.title = '';
}
}}