fix: harden media download flow

This commit is contained in:
nimbold
2026-06-08 01:06:00 +03:30
parent 2e5b4ae7c3
commit 4e48f1e42c
36 changed files with 576 additions and 530 deletions
+6 -4
View File
@@ -4,8 +4,10 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
APP_NAME="Firelink"
CONFIGURATION="${CONFIGURATION:-release}"
MARKETING_VERSION="${MARKETING_VERSION:-0.1.0}"
BUILD_NUMBER="${BUILD_NUMBER:-1}"
DEFAULT_MARKETING_VERSION="$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || true)"
DEFAULT_BUILD_NUMBER="$(git rev-list --count HEAD 2>/dev/null || true)"
MARKETING_VERSION="${MARKETING_VERSION:-${DEFAULT_MARKETING_VERSION:-0.1.0}}"
BUILD_NUMBER="${BUILD_NUMBER:-${DEFAULT_BUILD_NUMBER:-1}}"
APP_DIR="$ROOT_DIR/build/$APP_NAME.app"
CONTENTS_DIR="$APP_DIR/Contents"
MACOS_DIR="$CONTENTS_DIR/MacOS"
@@ -35,12 +37,12 @@ ARIA2C_PATH=$(which aria2c || true)
if [[ -n "$ARIA2C_PATH" && -x "$ARIA2C_PATH" ]]; then
echo "Bundling aria2c from $ARIA2C_PATH..."
cp "$ARIA2C_PATH" "$RESOURCES_DIR/aria2c"
if ! command -v dylibbundler &> /dev/null; then
echo "Installing dylibbundler..."
brew install dylibbundler
fi
FRAMEWORKS_DIR="$CONTENTS_DIR/Frameworks"
mkdir -p "$FRAMEWORKS_DIR"
dylibbundler -od -b -x "$RESOURCES_DIR/aria2c" -d "$FRAMEWORKS_DIR" -p "@executable_path/../Frameworks/"
-23
View File
@@ -1,23 +0,0 @@
import re
with open("Sources/Firelink/DownloadTable.swift", "r") as f:
content = f.read()
# Remove doubleClickableCell block
content = re.sub(r' private func doubleClickableCell.*? }\n\n', '', content, flags=re.DOTALL)
# Remove doubleClickableCell wrappers from TableColumn
content = re.sub(r'doubleClickableCell\(for: item\) \{\n\s*(.*?)\n\s*\}', r'\1', content, flags=re.DOTALL)
# Add simultaneousGesture to Table
table_end = content.find(' .environment(\\.defaultMinListRowHeight, settings.listRowDensity.minRowHeight)')
gesture = ''' .simultaneousGesture(TapGesture(count: 2).onEnded {
if let id = selection.first, let item = controller.downloads.first(where: { $0.id == id }) {
performPrimaryAction(for: item)
}
})
'''
content = content[:table_end] + gesture + content[table_end:]
with open("Sources/Firelink/DownloadTable.swift", "w") as f:
f.write(content)
-62
View File
@@ -1,62 +0,0 @@
import re
with open("Sources/Firelink/DownloadTable.swift", "r") as f:
content = f.read()
# Fix size to use bytesText if sizeBytes is nil or 0
old_size = ''' TableColumn("Size", value: \\.sortableSize) { item in
Text(ByteFormatter.string(item.sizeBytes))
.monospacedDigit()
.lineLimit(1)
.truncationMode(.tail)
}'''
new_size = ''' TableColumn("Size", value: \\.sortableSize) { item in
if let size = item.sizeBytes, size > 0 {
Text(ByteFormatter.string(size))
.monospacedDigit()
.lineLimit(1)
.truncationMode(.tail)
} else if item.bytesText != "-" && !item.bytesText.isEmpty {
Text(item.bytesText)
.monospacedDigit()
.lineLimit(1)
.truncationMode(.tail)
} else {
Text("Unknown")
.monospacedDigit()
.lineLimit(1)
.truncationMode(.tail)
}
}'''
content = content.replace(old_size, new_size)
# Add allowsHitTesting(false) to text to fix single click interception
old_text = ''' Text(item.fileName)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)'''
new_text = ''' Text(item.fileName)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)
.allowsHitTesting(false)'''
content = content.replace(old_text, new_text)
# Change performPrimaryAction to use id
old_action = 'openWindow(value: item.id)'
new_action = 'openWindow(id: "download-properties", value: item.id)'
content = content.replace(old_action, new_action)
with open("Sources/Firelink/DownloadTable.swift", "w") as f:
f.write(content)
with open("Sources/Firelink/FirelinkApp.swift", "r") as f:
app_content = f.read()
app_content = app_content.replace(
'WindowGroup("Download Properties", for: UUID.self)',
'WindowGroup("Download Properties", id: "download-properties", for: UUID.self)'
)
with open("Sources/Firelink/FirelinkApp.swift", "w") as f:
f.write(app_content)
-86
View File
@@ -1,86 +0,0 @@
with open("Sources/Firelink/FirelinkApp.swift", "r") as f:
app_content = f.read()
app_content = app_content.replace(
'WindowGroup("Download Properties", id: "download-properties", for: String.self) { $downloadIDString in\n if let idString = downloadIDString, let downloadID = UUID(uuidString: idString) {',
'WindowGroup("Download Properties", id: "download-properties", for: UUID.self) { $downloadID in\n if let downloadID {'
)
with open("Sources/Firelink/FirelinkApp.swift", "w") as f:
f.write(app_content)
with open("Sources/Firelink/DownloadTable.swift", "r") as f:
table_content = f.read()
# Fix openWindow in performPrimaryAction
table_content = table_content.replace(
'openWindow(id: "download-properties", value: item.id.uuidString)',
'openWindow(id: "download-properties", value: item.id)'
)
# Fix openWindow in rowContextMenu
table_content = table_content.replace(
'openWindow(value: target.id)',
'openWindow(id: "download-properties", value: target.id)'
)
# Remove simultaneousGesture from Table
import re
table_content = re.sub(
r'\s*\.simultaneousGesture\(TapGesture\(count: 2\)\.onEnded \{\s*if let id = selection\.first, let item = controller\.downloads\.first\(where: \{ \$0\.id == id \}\) \{\s*performPrimaryAction\(for: item\)\s*\}\s*\}\)',
'',
table_content
)
# Replace the File Name TableColumn to use doubleClickableCell
old_column = ''' TableColumn("File Name", value: \\.fileName) { item in
HStack(alignment: .top, spacing: 8) {
Image(systemName: item.category.symbolName)
.font(.title3)
.foregroundStyle(categoryColor(for: item.category))
.frame(width: 22)
Text(item.fileName)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)
.allowsHitTesting(false)
.draggable(item.id.uuidString)
}
}'''
new_column = ''' TableColumn("File Name", value: \\.fileName) { item in
doubleClickableCell(for: item) {
HStack(alignment: .top, spacing: 8) {
Image(systemName: item.category.symbolName)
.font(.title3)
.foregroundStyle(categoryColor(for: item.category))
.frame(width: 22)
Text(item.fileName)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)
.allowsHitTesting(false)
}
.draggable(item.id.uuidString)
}
}'''
table_content = table_content.replace(old_column, new_column)
# Add doubleClickableCell helper
helper = ''' private func performPrimaryAction(for item: DownloadItem) {'''
helper_new = ''' private func doubleClickableCell<Content: View>(for item: DownloadItem, @ViewBuilder content: () -> Content) -> some View {
content()
.contentShape(Rectangle())
.simultaneousGesture(TapGesture(count: 2).onEnded {
performPrimaryAction(for: item)
})
}
private func performPrimaryAction(for item: DownloadItem) {'''
table_content = table_content.replace(helper, helper_new)
with open("Sources/Firelink/DownloadTable.swift", "w") as f:
f.write(table_content)
-21
View File
@@ -1,21 +0,0 @@
with open("Sources/Firelink/FirelinkApp.swift", "r") as f:
app_content = f.read()
old_group = ''' WindowGroup("Download Properties", id: "download-properties", for: UUID.self) { $downloadID in
if let downloadID {'''
new_group = ''' WindowGroup("Download Properties", id: "download-properties", for: String.self) { $downloadIDString in
if let idString = downloadIDString, let downloadID = UUID(uuidString: idString) {'''
app_content = app_content.replace(old_group, new_group)
with open("Sources/Firelink/FirelinkApp.swift", "w") as f:
f.write(app_content)
with open("Sources/Firelink/DownloadTable.swift", "r") as f:
table_content = f.read()
old_open1 = 'openWindow(id: "download-properties", value: item.id)'
new_open1 = 'openWindow(id: "download-properties", value: item.id.uuidString)'
table_content = table_content.replace(old_open1, new_open1)
with open("Sources/Firelink/DownloadTable.swift", "w") as f:
f.write(table_content)
-45
View File
@@ -1,45 +0,0 @@
with open("Sources/Firelink/DownloadTable.swift", "r") as f:
content = f.read()
old_column = ''' TableColumn("File Name", value: \\.fileName) { item in
doubleClickableCell(for: item) {
HStack(alignment: .top, spacing: 8) {
Image(systemName: item.category.symbolName)
.font(.title3)
.foregroundStyle(categoryColor(for: item.category))
.frame(width: 22)
Text(item.fileName)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)
.allowsHitTesting(false)
}
.draggable(item.id.uuidString)
}
}'''
new_column = ''' TableColumn("File Name", value: \\.fileName) { item in
HStack(alignment: .top, spacing: 8) {
Image(systemName: item.category.symbolName)
.font(.title3)
.foregroundStyle(categoryColor(for: item.category))
.frame(width: 22)
Text(item.fileName)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)
.allowsHitTesting(false)
}
.draggable(item.id.uuidString)
}'''
content = content.replace(old_column, new_column)
import re
# Remove doubleClickableCell function block
pattern = r'\s*private func doubleClickableCell<Content: View>\(for item: DownloadItem, @ViewBuilder content: \(\) -> Content\) -> some View \{\s*content\(\)\s*\.contentShape\(Rectangle\(\)\)\s*\.simultaneousGesture\(TapGesture\(count: 2\)\.onEnded \{\s*performPrimaryAction\(for: item\)\s*\}\)\s*\}'
content = re.sub(pattern, '', content)
with open("Sources/Firelink/DownloadTable.swift", "w") as f:
f.write(content)
+1
View File
@@ -5,4 +5,5 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
swift build
git diff --check
python3 -m json.tool Extensions/Firefox/manifest.json >/dev/null