mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-27 19:17:13 +00:00
fix: resolve queue rendering and sorting desyncs
- Ensure `initDB` sorts active items by `queuePosition` before dispatching to preserve expected backend execution order on startup. - Update `resumeDownload` to explicitly move items to the end of the queue visually by assigning a new max `queuePosition`, synchronizing frontend representation with backend queue behavior. - Change `queuePosition` calculations to use `Math.max` via `reduce` rather than array `.length`, preventing duplicate queue position assignments upon deletions and avoiding call stack limit exceptions for massive queues. - Modify `DownloadTable.tsx` sorting to enforce active downloads appearing at the top of the queue view, deferring to `queuePosition` only for pending items. - Exclude active items from `moveInQueue` logic and UI to prevent visual corruption and silent queue scrambling.
This commit is contained in:
@@ -5,6 +5,7 @@ import { useDownloadProgressStore } from '../store/downloadStore';
|
|||||||
import { Play, Pause, MoreVertical, Clock, ArrowUp, ArrowDown } from 'lucide-react';
|
import { Play, Pause, MoreVertical, Clock, ArrowUp, ArrowDown } from 'lucide-react';
|
||||||
import type { DownloadItem as DownloadItemType } from '../bindings/DownloadItem';
|
import type { DownloadItem as DownloadItemType } from '../bindings/DownloadItem';
|
||||||
import { canPauseDownload, canStartDownload, startActionLabel } from '../utils/downloadActions';
|
import { canPauseDownload, canStartDownload, startActionLabel } from '../utils/downloadActions';
|
||||||
|
import { isActiveDownloadStatus } from '../utils/downloads';
|
||||||
|
|
||||||
interface DownloadItemProps {
|
interface DownloadItemProps {
|
||||||
downloadId: string;
|
downloadId: string;
|
||||||
@@ -37,7 +38,8 @@ export const DownloadItem = React.memo<DownloadItemProps>(({
|
|||||||
return state.downloads
|
return state.downloads
|
||||||
.filter(candidate =>
|
.filter(candidate =>
|
||||||
candidate.queueId === queueId &&
|
candidate.queueId === queueId &&
|
||||||
candidate.status !== 'completed'
|
candidate.status !== 'completed' &&
|
||||||
|
!(isActiveDownloadStatus(candidate.status) && candidate.status !== 'queued')
|
||||||
)
|
)
|
||||||
.sort((left, right) => (left.queuePosition ?? 0) - (right.queuePosition ?? 0))
|
.sort((left, right) => (left.queuePosition ?? 0) - (right.queuePosition ?? 0))
|
||||||
.map(candidate => candidate.id);
|
.map(candidate => candidate.id);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
canStartDownload,
|
canStartDownload,
|
||||||
startActionLabel
|
startActionLabel
|
||||||
} from '../utils/downloadActions';
|
} from '../utils/downloadActions';
|
||||||
|
import { isActiveDownloadStatus } from '../utils/downloads';
|
||||||
|
|
||||||
interface DownloadTableProps {
|
interface DownloadTableProps {
|
||||||
filter: SidebarFilter;
|
filter: SidebarFilter;
|
||||||
@@ -186,7 +187,15 @@ export const DownloadTable: React.FC<DownloadTableProps> = ({ filter }) => {
|
|||||||
// Sort by queue position when viewing a specific queue so the visual
|
// Sort by queue position when viewing a specific queue so the visual
|
||||||
// order matches the queue order and move-up/down buttons reflect reality.
|
// order matches the queue order and move-up/down buttons reflect reality.
|
||||||
const sortedDownloads = filter.startsWith('queue:')
|
const sortedDownloads = filter.startsWith('queue:')
|
||||||
? [...filteredDownloads].sort((left, right) => (left.queuePosition ?? 0) - (right.queuePosition ?? 0))
|
? [...filteredDownloads].sort((left, right) => {
|
||||||
|
const leftActive = isActiveDownloadStatus(left.status) && left.status !== 'queued';
|
||||||
|
const rightActive = isActiveDownloadStatus(right.status) && right.status !== 'queued';
|
||||||
|
|
||||||
|
if (leftActive && !rightActive) return -1;
|
||||||
|
if (!leftActive && rightActive) return 1;
|
||||||
|
|
||||||
|
return (left.queuePosition ?? 0) - (right.queuePosition ?? 0);
|
||||||
|
})
|
||||||
: filteredDownloads;
|
: filteredDownloads;
|
||||||
const handleItemClick = (e: React.MouseEvent, item: DownloadItem) => {
|
const handleItemClick = (e: React.MouseEvent, item: DownloadItem) => {
|
||||||
if (e.detail === 2) {
|
if (e.detail === 2) {
|
||||||
|
|||||||
@@ -227,7 +227,11 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
|||||||
if (!item) return;
|
if (!item) return;
|
||||||
const queueId = item.queueId || MAIN_QUEUE_ID;
|
const queueId = item.queueId || MAIN_QUEUE_ID;
|
||||||
const queueItems = get().downloads
|
const queueItems = get().downloads
|
||||||
.filter(download => (download.queueId || MAIN_QUEUE_ID) === queueId && download.status !== 'completed')
|
.filter(download =>
|
||||||
|
(download.queueId || MAIN_QUEUE_ID) === queueId &&
|
||||||
|
download.status !== 'completed' &&
|
||||||
|
!(isActiveDownloadStatus(download.status) && download.status !== 'queued')
|
||||||
|
)
|
||||||
.sort((left, right) => (left.queuePosition ?? 0) - (right.queuePosition ?? 0));
|
.sort((left, right) => (left.queuePosition ?? 0) - (right.queuePosition ?? 0));
|
||||||
const index = queueItems.findIndex(download => download.id === id);
|
const index = queueItems.findIndex(download => download.id === id);
|
||||||
const target = direction === 'up' ? index - 1 : index + 1;
|
const target = direction === 'up' ? index - 1 : index + 1;
|
||||||
@@ -324,9 +328,11 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
|||||||
const settings = useSettingsStore.getState();
|
const settings = useSettingsStore.getState();
|
||||||
const destPath = await effectiveDestinationForItem(item, settings);
|
const destPath = await effectiveDestinationForItem(item, settings);
|
||||||
const queueId = action.type === 'add-to-queue' ? action.queueId : MAIN_QUEUE_ID;
|
const queueId = action.type === 'add-to-queue' ? action.queueId : MAIN_QUEUE_ID;
|
||||||
const queuePosition = get().downloads.filter(download =>
|
const queueItems = get().downloads.filter(download =>
|
||||||
(download.queueId || MAIN_QUEUE_ID) === queueId && download.status !== 'completed'
|
(download.queueId || MAIN_QUEUE_ID) === queueId
|
||||||
).length;
|
);
|
||||||
|
const maxPos = queueItems.reduce((max, d) => Math.max(max, d.queuePosition ?? 0), -1);
|
||||||
|
const queuePosition = maxPos + 1;
|
||||||
const ownedItem: DownloadItem = {
|
const ownedItem: DownloadItem = {
|
||||||
...item,
|
...item,
|
||||||
destination: destPath,
|
destination: destPath,
|
||||||
@@ -484,7 +490,17 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const prevStatus = targetItem.status;
|
const prevStatus = targetItem.status;
|
||||||
get().updateDownload(id, { status: 'queued', speed: '-', eta: '-' });
|
const queueItems = get().downloads.filter(d =>
|
||||||
|
(d.queueId || MAIN_QUEUE_ID) === (targetItem.queueId || MAIN_QUEUE_ID)
|
||||||
|
);
|
||||||
|
const maxPos = queueItems.reduce((max, d) => Math.max(max, d.queuePosition ?? 0), -1);
|
||||||
|
|
||||||
|
get().updateDownload(id, {
|
||||||
|
status: 'queued',
|
||||||
|
speed: '-',
|
||||||
|
eta: '-',
|
||||||
|
queuePosition: maxPos + 1
|
||||||
|
});
|
||||||
|
|
||||||
const resumedExisting = await invoke('resume_download', { id }).catch(() => false);
|
const resumedExisting = await invoke('resume_download', { id }).catch(() => false);
|
||||||
|
|
||||||
@@ -608,11 +624,12 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
|||||||
get().unregisterBackendIds([item.id]);
|
get().unregisterBackendIds([item.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextPosition = get().downloads.filter(item =>
|
const queueItems = get().downloads.filter(item =>
|
||||||
!selectedIds.has(item.id) &&
|
!selectedIds.has(item.id) &&
|
||||||
(item.queueId || MAIN_QUEUE_ID) === queueId &&
|
(item.queueId || MAIN_QUEUE_ID) === queueId
|
||||||
item.status !== 'completed'
|
);
|
||||||
).length;
|
const maxPos = queueItems.reduce((max, d) => Math.max(max, d.queuePosition ?? 0), -1);
|
||||||
|
const nextPosition = maxPos + 1;
|
||||||
set(state => ({
|
set(state => ({
|
||||||
downloads: state.downloads.map(item =>
|
downloads: state.downloads.map(item =>
|
||||||
selectedIds.has(item.id) && item.status !== 'completed'
|
selectedIds.has(item.id) && item.status !== 'completed'
|
||||||
@@ -693,7 +710,9 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Auto resume downloads that were active or queued
|
// Auto resume downloads that were active or queued
|
||||||
const active = get().downloads.filter(d => d.status === 'queued');
|
const active = get().downloads
|
||||||
|
.filter(d => d.status === 'queued')
|
||||||
|
.sort((a, b) => (a.queuePosition ?? 0) - (b.queuePosition ?? 0));
|
||||||
if (active.length > 0) {
|
if (active.length > 0) {
|
||||||
try {
|
try {
|
||||||
const settings = useSettingsStore.getState();
|
const settings = useSettingsStore.getState();
|
||||||
|
|||||||
Reference in New Issue
Block a user