Um salvamento que morre no meio do caminho, seja por uma reinicialização forçada, um processo encerrado, ou um disco que enche no meio da escrita, tradicionalmente significava uma coisa para um formato construído em torno de escritas no próprio lugar: quaisquer bytes que chegaram ao disco antes da interrupção são o que você recupera, e uma pasta de trabalho truncada não abre mais. O HotXLS fecha esse modo de falha com um caminho de salvamento à prova de falha usado para cada arquivo XLSX, ODS e XLS clássico que ele escreve. Cada chamada SaveAs escreve o arquivo novo completo em um arquivo temporário criado ao lado do destino, depois o confirma com uma única renomeação atômica MoveFileExW da API do Windows, de modo que um salvamento interrompido só pode falhar em produzir o arquivo novo, nunca danifica o que você já tinha. A mesma disciplina de "primeiro faça a etapa, depois troque" roda uniformemente pelos dois motores de salvamento do HotXLS, o escritor BIFF8 por trás do XLS clássico e o escritor OOXML por trás de XLSX e ODS, e é um padrão que vale a pena emprestar para qualquer arquivo que seu próprio código Delphi sobrescreva diretamente, planilhas ou não
O que acontece se o salvamento de uma pasta de trabalho for interrompido no meio do caminho?
A resposta direta é que depende inteiramente de como o escritor toca o arquivo de destino, e a implementação comum, abrir o arquivo alvo e transmitir conteúdo novo diretamente para dentro dele, funciona bem enquanto nada dá errado. No instante em que algo dá errado — uma queda, um processo morto à força, um compartilhamento de rede que cai no meio da escrita — o arquivo no disco fica em qualquer estado intermediário que o escritor tinha alcançado: um diretório central ZIP que nunca foi anexado para XLSX ou ODS, ou um stream BIFF faltando registros que um leitor espera para XLS clássico. O Excel não conserta isso graciosamente, e nenhum outro consumidor que espere um arquivo completo também não, de modo que o resultado prático é uma pasta de trabalho que abriu bem ontem e se recusa a abrir hoje
Como o HotXLS coloca cada salvamento em etapas atrás de uma troca atômica
O HotXLS nunca abre o arquivo de destino para escrita diretamente, para nenhum dos três formatos que salva. A sequência tem a mesma forma sempre: construir a saída completa em algum lugar que não é o arquivo que o usuário já tem no disco, e só mover isso para o lugar depois que essa construção tiver totalmente sucedido. Concretamente, SaveAs cria um arquivo temporário vazio na mesma pasta do caminho de destino, escreve toda a nova pasta de trabalho nesse arquivo temporário, e só depois que essa escrita retorna sem erro é que confirma o arquivo temporário sobre o destino com uma única renomeação. Nada disso exige uma propriedade para ativar; é simplesmente o que SaveAs faz para um caminho de arquivo comum, em toda chamada
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Report');
Sheet.Cells[1, 1].Value := 'Nothing special to enable here';
// If this call is interrupted, monthly-report.xlsx on disk stays
// either the old version, complete, or the new version, complete
if Book.SaveAs('monthly-report.xlsx', xlsxOpenXMLWorkbook) <> 1 then
raise Exception.Create('Save failed, see Book.LastDiagnostic');
finally
Book.Free;
end;
end;
A mesma disciplina se aplica ao escritor XLS clássico, não apenas ao OOXML, e os dois arquivos temporários até compartilham uma convenção de nomenclatura: ambos chamam a API GetTempFileNameW do Windows com o prefixo hxl, de modo que um salvamento interrompido antes da limpeza pode deixar para trás um arquivo perdido com um nome como hxl4C2A.tmp ao lado de sua pasta de trabalho. Esse arquivo não é corrupção, é evidência de que o mecanismo funcionou exatamente como projetado: a escrita incompleta parou ali, e sua pasta de trabalho real nunca foi aberta para escrita, para começo de conversa. Ver um desses depois de uma queda é seguro de excluir e não há nada para investigar
Por que colocar o arquivo temporário em etapa ao lado da pasta de trabalho em vez de em %TEMP%?
A resposta curta é que a renomeação do MoveFileExW só é atômica quando a origem e o destino ficam no mesmo volume, e a forma mais certa de garantir isso sem pedir a quem chama para configurar nada é derivar a localização do arquivo temporário do próprio caminho de destino. O HotXLS calcula a própria pasta do alvo e entrega esse diretório diretamente a GetTempFileNameW, de modo que o arquivo temporário é sempre criado na mesma unidade, o mesmo volume, do arquivo que está prestes a substituir, automaticamente, em todo salvamento. Se a biblioteca em vez disso colocasse as escritas em etapas na pasta temp do sistema, um caminho de destino em uma unidade diferente ou um volume de rede mapeado transformaria a etapa final em uma operação entre volumes, que a API do Windows ou recusa por completo, ou, se quem chama optar explicitamente por uma flag extra que o HotXLS não define aqui, degrada silenciosamente para uma cópia não atômica seguida de uma exclusão, reabrindo exatamente a janela de interrupção que todo esse mecanismo existe para fechar
A etapa de confirmação: MoveFileExW, write-through, e o que acontece em caso de falha
A etapa final de todo salvamento é exatamente uma chamada de API do Windows, MoveFileExW, carregando duas flags que cada uma faz um trabalho distinto. MOVEFILE_REPLACE_EXISTING é o que permite que a renomeação pouse em um arquivo que já existe; sem ela, uma renomeação que mira em um caminho existente simplesmente falha, o que derrotaria todo o propósito de um salvamento destinado a substituir uma pasta de trabalho que você já tem. MOVEFILE_WRITE_THROUGH cobre durabilidade: diz à função para não retornar até que a movimentação de fato tenha se completado no disco, em vez de retornar assim que a renomeação está meramente enfileirada, fechando uma janela de corrida mais estreita, mas real, onde uma queda imediatamente depois de SaveAs retornar ainda poderia pegar a troca em andamento. Se o arquivo temporário não puder ser criado, ou a renomeação final falhar por qualquer motivo (um problema de permissão, um destino travado, uma incompatibilidade de volume), o HotXLS exclui o arquivo temporário por conta própria, em vez de deixar lixo para trás, e o arquivo de destino é deixado exatamente como estava antes da chamada
Result := Book.SaveAs(TargetPath, xlsxOpenXMLWorkbook);
if Result <> 1 then
begin
// TargetPath on disk is unchanged; safe to retry, alert, or
// fall back to a different path without touching prior output
LogWriter.Write(Format('SaveAs failed (%d): %s',
[Book.LastDiagnostic.Code, Book.LastDiagnostic.Message]));
Exit(False);
end;
O próprio SaveAs mantém a convenção de retorno compartilhada em todo o HotXLS, um em caso de sucesso, um número negativo em caso de falha, mas um inteiro puro não diz por que um salvamento falhou, e tratar todo resultado negativo da mesma forma joga fora informação que uma política de nova tentativa poderia realmente usar. A propriedade LastDiagnostic, e a coleção Diagnostics mais completa por trás dela, carrega a mensagem que o HotXLS gerou internamente, distinguindo um arquivo temporário que não pôde ser criado de uma renomeação que o Windows recusou. Um job em lote que registra Code e Message em todo SaveAs que falha acumula exatamente a evidência que você quer na única vez que um cliente relatar um salvamento que silenciosamente não fez nada
XLS clássico paga com memória, XLSX e ODS pagam com disco
Os dois motores de salvamento chegam ao mesmo resultado à prova de falha por rotas diferentes, e a diferença importa se você já está ajustando algum dos dois para um job em lote grande. O escritor XLS clássico constrói o documento composto OLE inteiro em memória primeiro, usando armazenamento estruturado apoiado por um handle de memória, e só copia esse buffer finalizado para o arquivo temporário irmão em uma única escrita; o raciocínio no próprio código-fonte do HotXLS é direto: construir o arquivo inteiro em memória primeiro é o que impede que um salvamento falho ou cancelado jamais trunque o destino. O escritor XLSX e ODS, em vez disso, transmite suas entradas ZIP para o arquivo temporário conforme são produzidas, o mesmo estágio em nível de arquivo com um perfil de memória diferente. Se você já está se apoiando em StreamingWrite para manter uma exportação XLSX grande dentro do limite de memória de um contêiner, saiba que a alavanca equivalente para a exportação de XLS clássico não existe na mesma forma: a garantia à prova de falha é incondicional de qualquer forma, mas uma exportação .xls legada muito grande mantém sua saída completa em RAM independentemente, uma compensação coberta em mais profundidade em nosso artigo sobre escritas em streaming para jobs em lote em servidor
Aplicando o mesmo padrão fora do HotXLS, e onde a garantia termina
Emprestar o padrão é principalmente uma questão de conectar as mesmas duas chamadas de API do Windows nas quais o HotXLS se apoia internamente. GetTempFileNameW entrega um arquivo vazio, de nome único, em uma pasta de sua escolha, e MoveFileExW confirma sua escrita finalizada sobre o destino real em uma única etapa; uma versão mínima da mesma rotina que o HotXLS roda antes de todo SaveAs se parece com isto
function SaveFileAtomically(const Path: WideString; const Contents: TBytes): Boolean;
var
Dir, TempName: WideString;
Buffer: array[0..MAX_PATH] of WideChar;
FS: TFileStream;
begin
Result := False;
Dir := ExtractFilePath(ExpandFileName(Path));
FillChar(Buffer, SizeOf(Buffer), 0);
if GetTempFileNameW(PWideChar(Dir), 'app', 0, @Buffer[0]) = 0 then
Exit;
TempName := PWideChar(@Buffer[0]);
try
FS := TFileStream.Create(TempName, fmCreate or fmShareExclusive);
try
FS.WriteBuffer(Contents[0], Length(Contents));
finally
FS.Free;
end;
Result := MoveFileExW(PWideChar(TempName), PWideChar(ExpandFileName(Path)),
MOVEFILE_REPLACE_EXISTING or MOVEFILE_WRITE_THROUGH);
finally
if not Result then
DeleteFileW(PWideChar(TempName));
end;
end;
A garantia tem limites reais que vale a pena conhecer antes de confiar nela cegamente. Colocar uma cópia completa em etapa antes de substituir a original significa que um salvamento precisa brevemente de espaço em disco tanto para o arquivo antigo quanto para o novo, aproximadamente o dobro do tamanho da pasta de trabalho durante a escrita, o que é tranquilo para um relatório e vale a pena verificar para uma exportação de vários gigabytes rodando contra um volume quase cheio. O arquivo temporário também precisa pousar na mesma pasta do destino, de modo que qualquer conta sob a qual o HotXLS esteja rodando precisa de permissão de criação de arquivo naquela pasta especificamente, não apenas permissão para sobrescrever o único arquivo que já conhece; uma implantação que trava uma pasta de destino apenas para edições no lugar de nomes de arquivo específicos existentes, em vez de acesso de escrita em nível de pasta, vai ver SaveAs falhar na etapa de arquivo temporário mesmo que a escrita direta equivalente tivesse tido sucesso
Dois outros limites valem a pena sinalizar claramente. Um destino em um compartilhamento de rede ou dentro de uma pasta sincronizada pelo OneDrive ou um cliente similar pode se comportar de forma diferente do NTFS local mesmo que o Windows ainda o reporte como um único volume, já que o driver de sistema de arquivos na frente dele pode não implementar a renomeação da mesma forma; se seu alvo de implantação salva através de um caminho de rede, vale a pena testar uma interrupção forçada ali especificamente, em vez de assumir que o comportamento de disco local se transfere. E todo o mecanismo é restrito a salvar em um arquivo nomeado. Chame SaveAs contra um TStream em vez disso, e o HotXLS escreve diretamente no stream que você entregou, sem arquivo de destino para colocar em etapa ou proteger, porque a durabilidade desse stream (um buffer de memória, um upload de rede, um blob de banco de dados) é inteiramente responsabilidade do seu código a partir daquele ponto
Uma passada de verificação pode confiar exatamente nessa garantia depois, incluindo o tipo embutido em uma bancada de auditoria e conversão de pastas de trabalho: um arquivo reaberto que volta curto ou ausente é um problema real de conversão para investigar, nunca um salvamento que foi interrompido no meio e deixou algo ambíguo no disco. Escritas em etapas à prova de falha vêm embutidas em SaveAs para toda pasta de trabalho XLSX, ODS e XLS clássica produzida pelo Componente HotXLS para Delphi e C++Builder, sem nenhuma configuração necessária para ativá-la