Pin nixpkgs to the revision used by itc.componentware: the channel version ships a latexminted that crashes on Python 3.14, which made every listing fail and sent latexmk into an endless error prompt. Also force nonstopmode/halt-on-error so a broken listing can never loop again.
55 lines
2.2 KiB
C#
55 lines
2.2 KiB
C#
/// <summary>
|
|
/// todo: this function can currently cause dataloss. see #10070
|
|
/// </summary>
|
|
/// <returns>success</returns>
|
|
private async Task<bool> RenameCustomerFolderAsync(
|
|
OrgSlug source, OrgSlug destination, OrgId owner, CancellationToken cancellationToken)
|
|
{
|
|
if (source == destination)
|
|
return true;
|
|
|
|
if (!await CanClaimCustomerFolderAsync(destination, owner, cancellationToken))
|
|
return false;
|
|
|
|
var sourcePrefix = source.ToString();
|
|
var destinationPrefix = destination.ToString();
|
|
|
|
var sourceKeys = await ListKeysAsync(sourcePrefix, cancellationToken);
|
|
|
|
// The marker of the customer folder carries the efecte-org-id, so it is copied last and
|
|
// deleted first: the destination only becomes resolvable once it is complete. Everything
|
|
// below the customer folder keeps its relative key, including subfolders Houston does not
|
|
// know, so no document ever ends up somewhere else.
|
|
var documentKeys = sourceKeys.Where(key => key != sourcePrefix).ToList();
|
|
var markerKeys = sourceKeys.Where(key => key == sourcePrefix).ToList();
|
|
var copiedKeys = new List<string>(sourceKeys.Count);
|
|
|
|
try
|
|
{
|
|
foreach (var key in documentKeys.Concat(markerKeys))
|
|
{
|
|
var destinationKey = destinationPrefix + key[sourcePrefix.Length..];
|
|
await CopyObjectAsync(key, destinationKey, cancellationToken);
|
|
copiedKeys.Add(destinationKey);
|
|
}
|
|
}
|
|
catch (AmazonS3Exception exception)
|
|
{
|
|
ExceptionlessClient.Default.SubmitLog(
|
|
$"Could not move customer folder '{source}' to '{destination}': {exception.Message}. "
|
|
+ "The folder stays where it is.", LogLevel.Error);
|
|
|
|
// Leftover copies would block every later attempt, because the destination is only
|
|
// claimed while it is empty. Cleaning them up keeps the move retryable.
|
|
await TryDeleteObjectsAsync(copiedKeys, cancellationToken);
|
|
return false;
|
|
}
|
|
|
|
// From here on the destination is authoritative. A failure while deleting only leaves
|
|
// garbage behind, the customer already sees all of his documents under the new name.
|
|
await TryDeleteObjectsAsync(documentKeys, cancellationToken);
|
|
await TryDeleteObjectsAsync(markerKeys, cancellationToken);
|
|
|
|
return true;
|
|
}
|