Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,247 +33,247 @@
cliDownloader = new HuggingFaceCliDownloader(this.downloadOptions, this.logger);
}

public async Task<bool> DownloadAsync(
string modelId,
string fileName,
string destinationPath,
IProgress<DownloadProgress>? progress = null,
CancellationToken cancellationToken = default,
string? revision = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(modelId);
ArgumentException.ThrowIfNullOrWhiteSpace(fileName);
ArgumentException.ThrowIfNullOrWhiteSpace(destinationPath);

string resolvedDestinationPath = ResolveDestinationPath(destinationPath);
// Deterministic temp name (matches ModelDownloaderAdapter) so an interrupted download
// resumes across separate calls/app restarts instead of restarting from byte 0 and
// orphaning a fresh GUID-named partial on every attempt.
string tempPath = $"{resolvedDestinationPath}.partial";

string resolvedRevision = string.IsNullOrWhiteSpace(revision) ? "main" : revision.Trim();
Uri downloadUri = BuildDownloadUri(modelId, resolvedRevision, fileName);
logger.LogInformation($"Downloading model from {downloadUri}");

// Ensure destination directory exists
string? directory = Path.GetDirectoryName(resolvedDestinationPath);
if (!string.IsNullOrWhiteSpace(directory))
{
Directory.CreateDirectory(directory);
}

if (downloadOptions.CliPreference == HuggingFaceCliPreference.Required)
{
// Required CLI mode must not probe HTTP resume state (HEAD/Range).
// Clear any leftover HTTP partial, attempt CLI, then refuse HTTP fallback.
try
{
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
logger.LogWarning(
$"Failed to clear partial download before required Hugging Face CLI attempt: {tempPath}",
ex);
}

if (await cliDownloader.TryDownloadAsync(
modelId,
fileName,
resolvedDestinationPath,
resolvedRevision,
progress,
cancellationToken).ConfigureAwait(false))
{
CleanupStaleTemps(resolvedDestinationPath);
return true;
}

logger.LogError(
$"TRACKDUB_HF_USE_CLI=require: Hugging Face CLI did not download '{modelId}/{fileName}'; refusing HTTP fallback. " +
"To enable HTTP fallback instead of requiring the CLI, set TRACKDUB_HF_USE_CLI=auto or TRACKDUB_HF_USE_CLI=never.");
return false;
}

long existingBytes = await PartialDownloadState.PrepareResumeAsync(
httpClient,
downloadUri,
tempPath,
logger,
cancellationToken).ConfigureAwait(false);

if (existingBytes == 0 &&
await cliDownloader.TryDownloadAsync(
modelId,
fileName,
resolvedDestinationPath,
resolvedRevision,
progress,
cancellationToken).ConfigureAwait(false))
{
CleanupStaleTemps(resolvedDestinationPath);
return true;
}

if (existingBytes == 0 &&
await ParallelRangeDownloader.TryDownloadAsync(
httpClient,
downloadUri,
tempPath,
downloadOptions,
logger,
progress,
cancellationToken).ConfigureAwait(false))
{
File.Move(tempPath, resolvedDestinationPath, overwrite: true);
CleanupStaleTemps(resolvedDestinationPath);
logger.LogInformation($"Parallel download completed: {resolvedDestinationPath}");
return true;
}

string url = downloadUri.ToString();
for (int attempt = 1; attempt <= RetryPolicy.Download.MaxAttempts; attempt++)
{
try
{
if (attempt > 1)
{
existingBytes = await PartialDownloadState.PrepareResumeAsync(
httpClient,
downloadUri,
tempPath,
logger,
cancellationToken).ConfigureAwait(false);
}

using var request = new HttpRequestMessage(HttpMethod.Get, url);
if (existingBytes > 0)
{
request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(existingBytes, null);
}

using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);

bool isResuming = existingBytes > 0 && response.StatusCode == System.Net.HttpStatusCode.PartialContent;
if (existingBytes > 0 && response.StatusCode == System.Net.HttpStatusCode.OK)
{
logger.LogWarning($"Hugging Face ignored resume request; restarting '{url}'.");
existingBytes = 0;
isResuming = false;
}

if (!response.IsSuccessStatusCode)
{
if (existingBytes > 0 && response.StatusCode == System.Net.HttpStatusCode.RequestedRangeNotSatisfiable && attempt < RetryPolicy.Download.MaxAttempts)
{
logger.LogWarning($"Hugging Face partial download was no longer resumable; restarting '{url}'.");
PartialDownloadState.DeleteArtifacts(tempPath);
existingBytes = 0;
await Task.Delay(RetryPolicy.Download.GetDelay(attempt), cancellationToken).ConfigureAwait(false);
continue;
}

logger.LogError($"Download failed with status {response.StatusCode}: {url}");

if (DownloadRetry.ShouldRetryStatus(response.StatusCode) && attempt < RetryPolicy.Download.MaxAttempts)
{
await Task.Delay(RetryPolicy.Download.GetDelay(attempt), cancellationToken).ConfigureAwait(false);
continue;
}

PartialDownloadState.DeleteArtifacts(tempPath);
return false;
}

long? contentLength = ResolveTotalContentLength(response, existingBytes, isResuming);
long totalBytesRead = isResuming ? existingBytes : 0;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var lastReportTime = stopwatch.Elapsed;
long sessionBytesRead = 0;

await using (Stream contentStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false))
await using (var fileStream = new FileStream(tempPath, isResuming ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.None, BufferSize, useAsync: true))
{
byte[] buffer = new byte[BufferSize];
int bytesRead;

while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
while ((bytesRead = await contentStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
sessionBytesRead += bytesRead;

var now = stopwatch.Elapsed;
if (now - lastReportTime >= TimeSpan.FromMilliseconds(250) || totalBytesRead == contentLength)
{
int percentComplete = contentLength is > 0 and var totalBytes
? (int)((totalBytesRead * 100) / totalBytes)
: 0;
double elapsedSeconds = stopwatch.Elapsed.TotalSeconds;
double speed = elapsedSeconds > 0 ? sessionBytesRead / elapsedSeconds : 0;
TimeSpan? eta = null;
if (contentLength is > 0 and var totalBytesForEta && speed > 0)
{
long remainingBytes = totalBytesForEta - totalBytesRead;
eta = TimeSpan.FromSeconds(remainingBytes / speed);
}

progress?.Report(new DownloadProgress(
totalBytesRead,
contentLength,
percentComplete,
$"Downloaded {FormatBytes(totalBytesRead)} of {(contentLength is { } total ? FormatBytes(total) : "unknown")}",
speed,
eta));

PartialDownloadState.RecordCommittedBytes(
tempPath,
totalBytesRead,
contentLength,
downloadUri);

lastReportTime = now;
}
}
}

PartialDownloadState.RecordCommittedBytes(
tempPath,
totalBytesRead,
contentLength,
downloadUri);

File.Move(tempPath, resolvedDestinationPath, overwrite: true);
CleanupStaleTemps(resolvedDestinationPath);
logger.LogInformation($"Download completed: {resolvedDestinationPath} ({FormatBytes(totalBytesRead)})");
return true;
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
logger.LogInformation(
$"Download cancelled; partial file retained at '{tempPath}' for resume.");
throw;
}
catch (Exception ex) when (DownloadRetry.IsTransientException(ex, cancellationToken) && attempt < RetryPolicy.Download.MaxAttempts)
{
logger.LogWarning($"Download interrupted for '{url}'. Retrying attempt {attempt + 1} of {RetryPolicy.Download.MaxAttempts}.", ex);
await Task.Delay(RetryPolicy.Download.GetDelay(attempt), cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
logger.LogError($"Download failed: {resolvedDestinationPath}", ex);
PartialDownloadState.DeleteArtifacts(tempPath);
return false;
}
}

logger.LogError($"Download failed after {RetryPolicy.Download.MaxAttempts} attempts: {url}");
return false;
}

Check notice on line 276 in src/Trackdub.Infrastructure/Licensing/HuggingFaceModelDownloader.cs

View check run for this annotation

codefactor.io / CodeFactor

src/Trackdub.Infrastructure/Licensing/HuggingFaceModelDownloader.cs#L36-L276

Complex Method
private static long? ResolveTotalContentLength(HttpResponseMessage response, long existingBytes, bool isResuming)
{
if (isResuming && response.Content.Headers.ContentRange?.Length is long totalLength)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ sourceUri.Scheme is not ("http" or "https"))
byte[] buffer = new byte[BufferSize];
int bytesRead;
while ((bytesRead = await contentStream
.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
.ReadAsync(buffer, cancellationToken)
.ConfigureAwait(false)) != 0)
Comment on lines 198 to 200

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The CodeFactor bot placed .ReadAsync(buffer, cancellationToken) at column 0, breaking the indentation of the method chain. The call should be indented to align with the surrounding code.

Suggested change
while ((bytesRead = await contentStream
.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
.ReadAsync(buffer, cancellationToken)
.ConfigureAwait(false)) != 0)
while ((bytesRead = await contentStream
.ReadAsync(buffer, cancellationToken)
.ConfigureAwait(false)) != 0)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Trackdub.Infrastructure/Licensing/ModelDownloaderAdapter.cs
Line: 198-200

Comment:
The CodeFactor bot placed `.ReadAsync(buffer, cancellationToken)` at column 0, breaking the indentation of the method chain. The call should be indented to align with the surrounding code.

```suggestion
                    while ((bytesRead = await contentStream
                               .ReadAsync(buffer, cancellationToken)
                               .ConfigureAwait(false)) != 0)
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor Fix in Codex Fix in Claude Code Fix in Devin

{
await fileStream
Expand Down
2 changes: 1 addition & 1 deletion src/Trackdub.Infrastructure/Updates/UpdateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ private async Task<bool> DownloadFileAsync(
int bytesRead;

while ((bytesRead = await contentStream
.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
.ReadAsync(buffer, cancellationToken)
.ConfigureAwait(false)) != 0)
Comment on lines 299 to 301

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Same column-0 indentation defect introduced by the bot — .ReadAsync(buffer, cancellationToken) should be indented to align with the method chain.

Suggested change
while ((bytesRead = await contentStream
.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
.ReadAsync(buffer, cancellationToken)
.ConfigureAwait(false)) != 0)
while ((bytesRead = await contentStream
.ReadAsync(buffer, cancellationToken)
.ConfigureAwait(false)) != 0)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Trackdub.Infrastructure/Updates/UpdateService.cs
Line: 299-301

Comment:
Same column-0 indentation defect introduced by the bot — `.ReadAsync(buffer, cancellationToken)` should be indented to align with the method chain.

```suggestion
            while ((bytesRead = await contentStream
                .ReadAsync(buffer, cancellationToken)
                .ConfigureAwait(false)) != 0)
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor Fix in Codex Fix in Claude Code Fix in Devin

{
await fileStream
Expand Down
4 changes: 2 additions & 2 deletions src/Trackdub.Media/Extraction/Pcm16WaveClipExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public async Task<AudioClipExtractionResult> ExtractAsync(
await using (FileStream sourceStream = new FileStream(sourceWavePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true))
{
byte[] headerBuffer = new byte[4096];
int headerBytesRead = await sourceStream.ReadAsync(headerBuffer, 0, headerBuffer.Length, cancellationToken).ConfigureAwait(false);
int headerBytesRead = await sourceStream.ReadAsync(headerBuffer, cancellationToken).ConfigureAwait(false);
if (headerBytesRead < 44)
{
throw new InvalidOperationException("Source wave file is too small to contain a valid header.");
Expand Down Expand Up @@ -248,7 +248,7 @@ private static async Task CopySliceAsync(
while (remainingBytes > 0)
{
int bytesToRead = Math.Min(buffer.Length, remainingBytes);
int bytesRead = await source.ReadAsync(buffer, 0, bytesToRead, cancellationToken).ConfigureAwait(false);
int bytesRead = await source.ReadAsync(buffer.AsMemory(0, bytesToRead), cancellationToken).ConfigureAwait(false);
if (bytesRead == 0)
{
throw new InvalidOperationException("Unexpected end of wave file while reading clip data.");
Expand Down
Loading