Compare commits
No commits in common. "a135dd0d96c976488c8ee14b1fb997c7ef145e0e" and "ef96184dc3010b02246076d6be48807c4d027596" have entirely different histories.
a135dd0d96
...
ef96184dc3
4 changed files with 15 additions and 148 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Application
|
# Application
|
||||||
APP_NAME=WebDrop Bridge
|
APP_NAME=WebDrop Bridge
|
||||||
APP_VERSION=0.8.6
|
APP_VERSION=0.8.5
|
||||||
|
|
||||||
# Web App
|
# Web App
|
||||||
WEBAPP_URL=file:///./webapp/index.html
|
WEBAPP_URL=file:///./webapp/index.html
|
||||||
|
|
|
||||||
|
|
@ -165,123 +165,24 @@ if (Test-Path $manifestOutput) {
|
||||||
$artifactPaths.Add($manifestOutput)
|
$artifactPaths.Add($manifestOutput)
|
||||||
$assetMap = Get-AssetMap -Assets $releaseInfo.assets
|
$assetMap = Get-AssetMap -Assets $releaseInfo.assets
|
||||||
|
|
||||||
$artifactsToUpload = New-Object System.Collections.Generic.List[string]
|
$curlAuth = "$ForgejoUser`:$ForgejoPW"
|
||||||
|
$uploadUrl = "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets"
|
||||||
|
|
||||||
foreach ($artifact in $artifactPaths) {
|
foreach ($artifact in $artifactPaths) {
|
||||||
$assetName = [System.IO.Path]::GetFileName($artifact)
|
$assetName = [System.IO.Path]::GetFileName($artifact)
|
||||||
$extension = [System.IO.Path]::GetExtension($artifact).ToLowerInvariant()
|
if ($assetMap.ContainsKey($assetName)) {
|
||||||
|
$existingAsset = $assetMap[$assetName]
|
||||||
if ($extension -eq ".msi" -and $assetMap.ContainsKey($assetName)) {
|
Invoke-RestMethod -Uri "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets/$($existingAsset.id)" -Method DELETE -Headers $headers -TimeoutSec 30 | Out-Null
|
||||||
$localSize = (Get-Item $artifact).Length
|
Write-Host "[OK] Replaced existing asset $assetName" -ForegroundColor Yellow
|
||||||
$remoteSize = [int64]$assetMap[$assetName].size
|
|
||||||
if ($localSize -eq $remoteSize) {
|
|
||||||
Write-Host "[OK] Skipping already uploaded MSI $assetName ($([math]::Round($localSize / 1MB, 2)) MB)" -ForegroundColor Cyan
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$artifactsToUpload.Add($artifact)
|
$response = curl.exe -s -X POST -u $curlAuth -F "attachment=@$artifact" $uploadUrl
|
||||||
}
|
if ($response -like "*error*" -or $response -like "*404*") {
|
||||||
|
Write-Host "WARNING: Could not upload $artifact : $response" -ForegroundColor Yellow
|
||||||
if ($artifactsToUpload.Count -eq 0) {
|
}
|
||||||
Write-Host "[OK] All release assets already uploaded." -ForegroundColor Green
|
else {
|
||||||
Write-Host "View at: $ForgejoUrl/$Repo/releases/tag/v$Version" -ForegroundColor Cyan
|
Write-Host "[OK] Uploaded $assetName" -ForegroundColor Green
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use Python requests library for more reliable large file uploads
|
|
||||||
$pythonUploadScript = @"
|
|
||||||
import sys
|
|
||||||
import requests
|
|
||||||
from requests.auth import HTTPBasicAuth
|
|
||||||
from pathlib import Path
|
|
||||||
import time
|
|
||||||
|
|
||||||
upload_url = sys.argv[1]
|
|
||||||
artifacts = sys.argv[2:]
|
|
||||||
username = '$ForgejoUser'
|
|
||||||
password = '$ForgejoPW'
|
|
||||||
delete_url_template = '${ForgejoUrl}/api/v1/repos/${Repo}/releases/$releaseId/assets/{}'
|
|
||||||
release_info_url = '${ForgejoUrl}/api/v1/repos/${Repo}/releases/$releaseId'
|
|
||||||
|
|
||||||
session = requests.Session()
|
|
||||||
session.auth = HTTPBasicAuth(username, password)
|
|
||||||
session.headers.update({'Connection': 'close'})
|
|
||||||
|
|
||||||
def upload_with_retry(artifact_path, max_retries=3):
|
|
||||||
asset_name = Path(artifact_path).name
|
|
||||||
|
|
||||||
# Check if asset already exists and delete it
|
|
||||||
try:
|
|
||||||
release_response = session.get(release_info_url, timeout=30)
|
|
||||||
release_response.raise_for_status()
|
|
||||||
for asset in release_response.json().get('assets', []):
|
|
||||||
if asset['name'] == asset_name:
|
|
||||||
delete_resp = session.delete(delete_url_template.format(asset['id']), timeout=30)
|
|
||||||
delete_resp.raise_for_status()
|
|
||||||
print(f'[OK] Replaced existing asset {asset_name}', file=sys.stderr)
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Warning checking existing assets: {e}', file=sys.stderr)
|
|
||||||
|
|
||||||
# Upload file with streaming and retries
|
|
||||||
retryable_status_codes = {429, 502, 503, 504}
|
|
||||||
for attempt in range(max_retries):
|
|
||||||
try:
|
|
||||||
if attempt > 0:
|
|
||||||
print(f' Retry {attempt} of {max_retries}...', file=sys.stderr)
|
|
||||||
time.sleep(min(10, 2 * attempt))
|
|
||||||
|
|
||||||
with open(artifact_path, 'rb') as f:
|
|
||||||
files = {'attachment': (asset_name, f)}
|
|
||||||
response = session.post(
|
|
||||||
upload_url,
|
|
||||||
files=files,
|
|
||||||
timeout=900, # 15 minute timeout
|
|
||||||
stream=False
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code in [200, 201]:
|
|
||||||
print(f'[OK] Uploaded {asset_name}')
|
|
||||||
return True
|
|
||||||
|
|
||||||
if response.status_code in retryable_status_codes:
|
|
||||||
if attempt >= max_retries - 1:
|
|
||||||
print(f'ERROR uploading {asset_name} (HTTP {response.status_code} after {max_retries} retries)')
|
|
||||||
print(response.text)
|
|
||||||
sys.exit(1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f'ERROR uploading {asset_name} (HTTP {response.status_code})')
|
|
||||||
print(response.text)
|
|
||||||
sys.exit(1)
|
|
||||||
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
|
|
||||||
if attempt >= max_retries - 1:
|
|
||||||
print(f'ERROR uploading {asset_name}: Connection failed after {max_retries} retries')
|
|
||||||
print(str(e))
|
|
||||||
sys.exit(1)
|
|
||||||
time.sleep(min(10, 2 * (attempt + 1)))
|
|
||||||
except Exception as e:
|
|
||||||
print(f'ERROR uploading {asset_name}: {e}')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
for artifact_path in artifacts:
|
|
||||||
upload_with_retry(artifact_path)
|
|
||||||
|
|
||||||
print(f'[OK] All files uploaded successfully')
|
|
||||||
"@
|
|
||||||
|
|
||||||
$uploadScriptPath = ([System.IO.Path]::GetTempFileName() -replace 'tmp$', 'py')
|
|
||||||
Set-Content -Path $uploadScriptPath -Value $pythonUploadScript -Encoding UTF8
|
|
||||||
|
|
||||||
try {
|
|
||||||
$uploadUrl = "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets"
|
|
||||||
& $pythonExe $uploadScriptPath $uploadUrl @artifactsToUpload
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
exit 1
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally {
|
|
||||||
Remove-Item $uploadScriptPath -ErrorAction SilentlyContinue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "`n[OK] Release complete!" -ForegroundColor Green
|
Write-Host "`n[OK] Release complete!" -ForegroundColor Green
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""WebDrop Bridge - Qt-based desktop application for intelligent drag-and-drop file handling."""
|
"""WebDrop Bridge - Qt-based desktop application for intelligent drag-and-drop file handling."""
|
||||||
|
|
||||||
__version__ = "0.8.6"
|
__version__ = "0.8.5"
|
||||||
__author__ = "WebDrop Team"
|
__author__ = "WebDrop Team"
|
||||||
__license__ = "MIT"
|
__license__ = "MIT"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -315,40 +315,6 @@ class TestDownloading:
|
||||||
assert result.name == "AgravityBridge-0.0.2-win-x64.msi"
|
assert result.name == "AgravityBridge-0.0.2-win-x64.msi"
|
||||||
mock_download.assert_called_once()
|
mock_download.assert_called_once()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_download_update_falls_back_to_brand_prefix_without_manifest(
|
|
||||||
self, agravity_update_manager, tmp_path
|
|
||||||
):
|
|
||||||
"""Test branded download selection still works when the manifest is unavailable."""
|
|
||||||
release = Release(
|
|
||||||
tag_name="v0.0.2",
|
|
||||||
name="WebDropBridge v0.0.2",
|
|
||||||
version="0.0.2",
|
|
||||||
body="Release notes",
|
|
||||||
assets=[
|
|
||||||
{
|
|
||||||
"name": "WebDropBridge-0.0.2-win-x64.msi",
|
|
||||||
"browser_download_url": "https://example.com/WebDropBridge-0.0.2-win-x64.msi",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "AgravityBridge-0.0.2-win-x64.msi",
|
|
||||||
"browser_download_url": "https://example.com/AgravityBridge-0.0.2-win-x64.msi",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "AgravityBridge-0.0.2-win-x64.msi.sha256",
|
|
||||||
"browser_download_url": "https://example.com/AgravityBridge-0.0.2-win-x64.msi.sha256",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
published_at="2026-01-29T10:00:00Z",
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch.object(UpdateManager, "_download_file", return_value=True) as mock_download:
|
|
||||||
result = await agravity_update_manager.download_update(release, tmp_path)
|
|
||||||
|
|
||||||
assert result is not None
|
|
||||||
assert result.name == "AgravityBridge-0.0.2-win-x64.msi"
|
|
||||||
mock_download.assert_called_once()
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_verify_checksum_uses_release_manifest(self, agravity_update_manager, tmp_path):
|
async def test_verify_checksum_uses_release_manifest(self, agravity_update_manager, tmp_path):
|
||||||
"""Test branded checksum selection from a shared release manifest."""
|
"""Test branded checksum selection from a shared release manifest."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue