polish for failed retry mechanic and consistent sub naming

This commit is contained in:
2026-02-04 15:02:50 -05:00
parent 2190a9c16b
commit 055808ea6d
6 changed files with 82 additions and 21 deletions
+2 -1
View File
@@ -17,7 +17,8 @@
"WebFetch(domain:raw.githubusercontent.com)", "WebFetch(domain:raw.githubusercontent.com)",
"Bash(python -m py_compile:*)", "Bash(python -m py_compile:*)",
"Bash(powershell:*)", "Bash(powershell:*)",
"Bash(python:*)" "Bash(python:*)",
"Bash(xargs basename:*)"
] ]
} }
} }
+34 -5
View File
@@ -29,8 +29,10 @@ async def list_downloads():
async with current_app.db_engine.connect() as conn: async with current_app.db_engine.connect() as conn:
session = AsyncSession(bind=conn) session = AsyncSession(bind=conn)
# Build query # Build query - eager load source and subscription for labeling
query = select(Download) query = select(Download).options(
selectinload(Download.source).selectinload(Source.subscription)
)
count_query = select(func.count()).select_from(Download) count_query = select(func.count()).select_from(Download)
filters = [] filters = []
@@ -58,8 +60,20 @@ async def list_downloads():
result = await session.execute(query) result = await session.execute(query)
downloads = result.scalars().all() downloads = result.scalars().all()
# Include source info with each download
items = []
for d in downloads:
item = d.to_dict()
if d.source:
item["subscription_name"] = d.source.subscription.name if d.source.subscription else None
item["platform"] = d.source.platform
else:
item["subscription_name"] = None
item["platform"] = None
items.append(item)
return jsonify({ return jsonify({
"items": [d.to_dict() for d in downloads], "items": items,
"total": total, "total": total,
"page": page, "page": page,
"per_page": per_page, "per_page": per_page,
@@ -147,8 +161,11 @@ async def recent_activity():
session = AsyncSession(bind=conn) session = AsyncSession(bind=conn)
# Get failed downloads OR completed with files (noteworthy activity) # Get failed downloads OR completed with files (noteworthy activity)
# Eager load source and subscription for labeling
from sqlalchemy import or_ from sqlalchemy import or_
query = select(Download).where( query = select(Download).options(
selectinload(Download.source).selectinload(Source.subscription)
).where(
and_( and_(
Download.created_at >= cutoff, Download.created_at >= cutoff,
or_( or_(
@@ -164,8 +181,20 @@ async def recent_activity():
result = await session.execute(query) result = await session.execute(query)
downloads = result.scalars().all() downloads = result.scalars().all()
# Include source info with each download
items = []
for d in downloads:
item = d.to_dict()
if d.source:
item["subscription_name"] = d.source.subscription.name if d.source.subscription else None
item["platform"] = d.source.platform
else:
item["subscription_name"] = None
item["platform"] = None
items.append(item)
return jsonify({ return jsonify({
"items": [d.to_dict() for d in downloads], "items": items,
"count": len(downloads), "count": len(downloads),
}) })
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "GallerySubscriber", "name": "GallerySubscriber",
"version": "1.1.0", "version": "1.1.1",
"description": "Export cookies from supported platforms to GallerySubscriber for automated downloads", "description": "Export cookies from supported platforms to GallerySubscriber for automated downloads",
"browser_specific_settings": { "browser_specific_settings": {
+44 -13
View File
@@ -173,7 +173,7 @@
</v-icon> </v-icon>
</template> </template>
<v-list-item-title class="text-body-2"> <v-list-item-title class="text-body-2">
{{ truncate(dl.url, 30) }} {{ getDownloadLabel(dl) }}
</v-list-item-title> </v-list-item-title>
<v-list-item-subtitle> <v-list-item-subtitle>
<v-chip size="x-small" :color="dl.status === 'running' ? 'info' : 'warning'" variant="tonal"> <v-chip size="x-small" :color="dl.status === 'running' ? 'info' : 'warning'" variant="tonal">
@@ -211,7 +211,7 @@
</v-icon> </v-icon>
</template> </template>
<v-list-item-title> <v-list-item-title>
{{ truncate(download.url, 40) }} {{ getDownloadLabel(download) }}
</v-list-item-title> </v-list-item-title>
<v-list-item-subtitle> <v-list-item-subtitle>
{{ formatRelativeTime(download.created_at) }} {{ formatRelativeTime(download.created_at) }}
@@ -255,20 +255,18 @@
<v-table v-if="sourcesWithErrors.length"> <v-table v-if="sourcesWithErrors.length">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Source</th>
<th>Platform</th> <th>Last Error</th>
<th>Error Count</th>
<th>Last Check</th> <th>Last Check</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="source in sourcesWithErrors" :key="source.id"> <tr v-for="source in sourcesWithErrors" :key="source.id">
<td>{{ source.subscription_name || source.subscription?.name || 'Unknown' }}</td> <td>{{ getSourceLabel(source) }}</td>
<td class="text-capitalize">{{ source.platform }}</td>
<td> <td>
<v-chip color="error" size="small"> <v-chip color="error" size="small">
{{ source.error_count }} errors {{ source.last_error_type || 'failed' }}
</v-chip> </v-chip>
</td> </td>
<td>{{ formatDate(source.last_check) }}</td> <td>{{ formatDate(source.last_check) }}</td>
@@ -318,7 +316,7 @@ const credentialsStore = useCredentialsStore()
const notifications = useNotificationStore() const notifications = useNotificationStore()
// Supported platforms for credential status // Supported platforms for credential status
const supportedPlatforms = ['patreon', 'subscribestar', 'hentaifoundry', 'discord'] const supportedPlatforms = ['patreon', 'subscribestar', 'hentaifoundry', 'discord', 'pixiv', 'deviantart']
// State // State
const checkingAll = ref(false) const checkingAll = ref(false)
@@ -335,9 +333,27 @@ let refreshInterval = null
// Computed // Computed
const stats = computed(() => downloadsStore.stats) const stats = computed(() => downloadsStore.stats)
const recentActivity = computed(() => downloadsStore.recentActivity) const recentActivity = computed(() => downloadsStore.recentActivity)
const sourcesWithErrors = computed(() =>
sourcesStore.sources.filter(s => s.error_count > 0).slice(0, 5) // Sources with recent failures - derived from recent activity
) // Only shows sources where a recent download failed (not cumulative error_count)
const sourcesWithErrors = computed(() => {
const failedDownloads = recentActivity.value.filter(d => d.status === 'failed')
// Group by source_id and keep unique sources with their most recent failure
const sourceMap = new Map()
for (const dl of failedDownloads) {
if (dl.source_id && !sourceMap.has(dl.source_id)) {
sourceMap.set(dl.source_id, {
id: dl.source_id,
subscription_name: dl.subscription_name,
platform: dl.platform,
last_error_type: dl.error_type,
last_check: dl.created_at,
})
}
}
return Array.from(sourceMap.values()).slice(0, 5)
})
const failedCount = computed(() => stats.value?.failed || 0) const failedCount = computed(() => stats.value?.failed || 0)
const activeDownloadsCount = computed(() => const activeDownloadsCount = computed(() =>
(stats.value?.queued || 0) + (stats.value?.running || 0) (stats.value?.queued || 0) + (stats.value?.running || 0)
@@ -565,10 +581,25 @@ function truncate(str, length) {
return str.substring(0, length) + '...' return str.substring(0, length) + '...'
} }
function getDownloadLabel(download) {
if (download.subscription_name && download.platform) {
return `${download.subscription_name}:${download.platform}`
}
// Fallback to truncated URL if source info not available
return truncate(download.url, 35)
}
function getSourceLabel(source) {
const name = source.subscription_name || source.subscription?.name || 'Unknown'
return `${name}:${source.platform}`
}
async function retrySource(source) { async function retrySource(source) {
try { try {
await sourcesStore.triggerCheck(source.id) await sourcesStore.triggerCheck(source.id)
notifications.success(`Queued check for ${source.subscription_name || source.platform}`) notifications.success(`Queued check for ${getSourceLabel(source)}`)
// Refresh recent activity after retrying
await downloadsStore.fetchRecentActivity({ limit: 10 })
} catch (error) { } catch (error) {
notifications.error(`Failed to queue check: ${error.message}`) notifications.error(`Failed to queue check: ${error.message}`)
} }
+1 -1
View File
@@ -1,6 +1,6 @@
# GallerySubscriber - Project Summary # GallerySubscriber - Project Summary
**Updated:** 2026-02-04 (Fixed Pixiv authentication - requires OAuth refresh token) **Updated:** 2026-02-04 (Pixiv OAuth in extension, improved failed/naming display)
## Overview ## Overview