b1dc57324e
Two bugs reported after v1.1.2: 1. False-negative "נכשלו" toast even though the file was on disk. Root cause from the log: Polly's 15s timeout on /NetworkStorage/action/upload fires while the server is still writing larger PDFs, so the client sees TimeoutRejectedException → Outcome.Failed, while the server quietly finishes the write. The outcome is now decided by listing the target folder, not by the upload response. If the upload exception fires, the listing is retried up to 4 times with 2s delays to let the server finish. Saved if the file appears, Failed only if it never does. 2. The filename TextBox in the attachments list exposed the extension (.pdf / .docx). Users have to delete it manually when renaming and risk dropping it. AttachmentRowViewModel now splits FileName into BaseName (editable) and Extension (frozen at construction). The XAML binds the TextBox to BaseName and shows the Extension as a muted adjacent chip. The on-disk filename = BaseName + Extension always. Bump to 1.1.3.0 for the patch release. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
72 lines
2.5 KiB
C#
72 lines
2.5 KiB
C#
using System.IO;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using MarcusLaw.OutlookAddin.Core.Models;
|
|
|
|
namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|
{
|
|
public sealed partial class AttachmentRowViewModel : ObservableObject
|
|
{
|
|
public EspoAttachment Data { get; }
|
|
|
|
[ObservableProperty]
|
|
private bool isSelected = true;
|
|
|
|
/// <summary>
|
|
/// Frozen at construction from the original filename. The user
|
|
/// edits only the base name; the extension always tags along.
|
|
/// </summary>
|
|
public string Extension { get; }
|
|
|
|
/// <summary>
|
|
/// Editable base name without the extension. Setting this rewrites
|
|
/// <see cref="EspoAttachment.Name"/> as <basename><Extension>.
|
|
/// </summary>
|
|
public string BaseName
|
|
{
|
|
get => Path.GetFileNameWithoutExtension(Data.Name) ?? string.Empty;
|
|
set
|
|
{
|
|
var newBase = (value ?? string.Empty).Trim();
|
|
var newFull = newBase + Extension;
|
|
if (Data.Name == newFull) return;
|
|
Data.Name = newFull;
|
|
OnPropertyChanged();
|
|
OnPropertyChanged(nameof(FileName));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Full filename including extension — what actually goes to the
|
|
/// server. Read-only mirror of <see cref="EspoAttachment.Name"/>.
|
|
/// </summary>
|
|
public string FileName => Data.Name;
|
|
|
|
public string SizeDisplay { get; }
|
|
|
|
public AttachmentRowViewModel(EspoAttachment data)
|
|
{
|
|
Data = data;
|
|
Extension = Path.GetExtension(data.Name) ?? string.Empty;
|
|
SizeDisplay = ComputeSize(data.ContentBase64);
|
|
}
|
|
|
|
private static string ComputeSize(string base64)
|
|
{
|
|
if (string.IsNullOrEmpty(base64)) return string.Empty;
|
|
// Base64 expands binary 4-for-3, ignoring padding chars.
|
|
var padding = 0;
|
|
if (base64.Length > 0 && base64[base64.Length - 1] == '=') padding++;
|
|
if (base64.Length > 1 && base64[base64.Length - 2] == '=') padding++;
|
|
var bytes = (long)((base64.Length * 3) / 4) - padding;
|
|
return FormatBytes(bytes);
|
|
}
|
|
|
|
private static string FormatBytes(long bytes)
|
|
{
|
|
if (bytes < 1024) return bytes + " B";
|
|
if (bytes < 1024 * 1024) return (bytes / 1024.0).ToString("F1") + " KB";
|
|
return (bytes / (1024.0 * 1024.0)).ToString("F1") + " MB";
|
|
}
|
|
}
|
|
}
|