CopyFile - 保存而不覆盖现有文件

CopyFile - save without overwriting existing file

我在做什么:

因此,无论用户加载文件时文件的名称如何,它都会与他的 FirstnameLastName 一起保存到磁盘,这是我从一些字符串变量中获得的。

UniMainModule.foldername = 包含保存文件的文件夹路径。

UniMainModule.FirstName = 包含用户的名字

UniMainModule.LastName = 包含用户的姓氏

因此,文件被保存为 FirstName_LastName.pdf 在磁盘上由 foldername 字符串提供的位置。

这是我正在使用的代码:

procedure TsomeForm.UniFileUpload1Completed(Sender: TObject; AStream: TFileStream);
var
  DestName : string;
  DestFolder : string;
begin
   DestFolder:=UniServerModule.StartPath+'files\'+UniMainModule.foldername+'\';
   DestName:=DestFolder+UniMainModule.FirstName+'_'+UniMainModule.LastName+'.pdf';
   CopyFile(PChar(AStream.FileName), PChar(DestName), False);
   ModalResult:= mrOk;
end;

据我了解,在 msdn 上阅读了一些关于 CopyFile 的信息后,传递 False 意味着它应该并且将会覆盖现有文件。

如果文件在该位置不存在该名称,没关系,它会被保存。

但是如果用户决定再次使用文件上传并上传新文件,新文件将覆盖之前的文件。因为他们正在用相同的名字保存。

如果文件已经存在(该位置存在具有该确切名称的文件),那么您如何确保它不会被覆盖,但我不知道,它被分配了一个 (1) 在名称或其他内容,保留两个文件?

你有一个文件名,所以使用 FileExists 检查文件是否存在。如果它确实将 (1) 附加到文件名,然后重试。重复增加 n 直到你得到一个不存在的文件名。所以,有点像这样:

procedure TsomeForm.UniFileUpload1Completed(Sender: TObject; AStream: TFileStream);
var
  DestName : string;
  DestFolder : string;
  n : integer;
  additional : string;
begin
   DestFolder:=UniServerModule.StartPath+'files\'+UniMainModule.foldername+'\';
   DestName:=DestFolder+UniMainModule.FirstName+'_'+UniMainModule.LastName;
   n := 0;
   additional :='.pdf';
   while FileExists( DestName + additional ) do
   begin
     inc(n);
     additional := '(' + intToStr(n) + ')'+'.pdf';
   end;
   CopyFile(PChar(AStream.FileName), PChar(DestName + additional), False);
   ModalResult:= mrOk;
end;

这是我的解决方案

    procedure TsomeForm.UniFileUpload1Completed(Sender: TObject; AStream: TFileStream);
    var
      DestName, NewName : string;
      DestFolder : string;
      Cnt: integer;
    begin
      DestFolder:=UniServerModule.StartPath+'files\'+UniMainModule.foldername+'\';
      DestName:=DestFolder+UniMainModule.FirstName+'_'+UniMainModule.LastName+'.pdf';
      if FileExists(DestName) then begin
        Cnt:=0;
        repeat
          Inc(Cnt);
          NewName:=Format(DestFolder+UniMainModule.FirstName+'_'+UniMainModule.LastName+'(%d).pdf',[Cnt]);
        until not FileExists(NewName);
        DestName:=NewName;
      end;
      CopyFile(PChar(AStream.FileName), PChar(DestName), False);
      ModalResult:= mrOk;
    end;

循环调用 CopyFile(),将其 bFailIfExists 参数设置为 TRUE,这样如果 CopyFile() 失败并出现 ERROR_FILE_EXISTS,您可以使用新文件名重试] 错误代码。

例如:

procedure TsomeForm.UniFileUpload1Completed(Sender: TObject; AStream: TFileStream);
var
  DestName : string;
  DestFolder : string;
  n : integer;
begin
  DestFolder := UniServerModule.StartPath + 'files\' + UniMainModule.foldername + '\';
  DestName := UniMainModule.FirstName + '_' + UniMainModule.LastName + '.pdf';
  n := 0;

  while not CopyFile(PChar(AStream.FileName), PChar(DestFolder + DestName), True) do
  begin
    if GetLastError() <> ERROR_FILE_EXISTS then
    begin
      // error handling...
      Break;
    end;
    Inc(n);
    DestName := UniMainModule.FirstName + '_' + UniMainModule.LastName + ' (' + IntToStr(n) + ').pdf';
  end;

  ModalResult := mrOk;
end;

但是,与其手动处理,不如让 OS 为您完成。特别是因为 OS 有自己的方式来重命名复制的文件,并且命名方案可以从一个 OS 版本更改(并且已经)到另一个版本。

不使用 CopyFile(),而是使用 SHFileOperation(),它有一个 FOF_RENAMEONCOLLISION 标志:

Give the file being operated on a new name in a move, copy, or rename operation if a file with the target name already exists at the destination.

例如:

uses
  ..., Winapi.ShellAPI;

procedure TsomeForm.UniFileUpload1Completed(Sender: TObject; AStream: TFileStream);
var
  DestName : string;
  DestFolder : string;
  fo : TSHFileOpStruct;
begin
  DestFolder := UniServerModule.StartPath + 'files\' + UniMainModule.foldername + '\';
  DestName := DestFolder + UniMainModule.FirstName + '_' + UniMainModule.LastName + '.pdf';

  ZeroMemory(@fo, SizeOf(fo));
  fo.Wnd := Handle;
  fo.wFunc := FO_COPY;
  fo.pFrom := PChar(AStream.FileName+#0);
  fo.pTo := PChar(DestName+#0);
  fo.fFlags := FOF_SILENT or FOF_NOCONFIRMATION or FOF_NOERRORUI or FOF_NOCONFIRMMKDIR or FOF_RENAMEONCOLLISION;

  if SHFileOperation(fo) <> 0 then
  begin
    // error handling...
  end
  else if fo.fAnyOperationsAborted then
  begin
    // abort handling ...
  end;

  ModalResult := mrOk;
end;

如果您需要知道 OS 为重命名的文件名选择了什么,还有一个 FOF_WANTMAPPINGHANDLE 标志:

If FOF_RENAMEONCOLLISION is specified and any files were renamed, assign a name mapping object that contains their old and new names to the hNameMappings member. This object must be freed using SHFreeNameMappings when it is no longer needed.

例如:

uses
  ..., Winapi.ShellAPI;

type
  PHandleToMappings = ^THandleToMappings;
  THandleToMappings = record
    uNumberOfMappings: UINT;                          // Number of mappings in the array.
    lpSHNameMappings: array[0..0] of PSHNAMEMAPPINGW; // array of pointers to mappings.
  end;

procedure TsomeForm.UniFileUpload1Completed(Sender: TObject; AStream: TFileStream);
var
  DestName : string;
  DestFolder : string;
  fo : TSHFileOpStruct;
  pMappings : PHandleToMappings;
  pMapping : PSHNAMEMAPPINGW;
begin
  DestFolder := UniServerModule.StartPath + 'files\' + UniMainModule.foldername + '\';
  DestName := DestFolder + UniMainModule.FirstName + '_' + UniMainModule.LastName + '.pdf';

  ZeroMemory(@fo, SizeOf(fo));
  fo.Wnd := Handle;
  fo.wFunc := FO_COPY;
  fo.pFrom := PChar(AStream.FileName+#0);
  fo.pTo := PChar(DestName+#0);
  fo.fFlags := FOF_SILENT or FOF_NOCONFIRMATION or FOF_NOERRORUI or FOF_NOCONFIRMMKDIR or FOF_RENAMEONCOLLISION or FOF_WANTMAPPINGHANDLE;

  if SHFileOperation(fo) <> 0 then
  begin
    // error handling...
  end else
  begin
    if fo.fAnyOperationsAborted then
    begin
      // abort handling...
    end;

    if fo.hNameMappings <> nil then
    begin
      try
        pMappings := PHandleToMappings(fo.hNameMappings);
        pMapping := pMappings^.lpSHNameMappings[0];
        SetString(DestName, pMapping^.pszNewPath, pMapping^.cchNewPath);
      finally
        SHFreeNameMappings(THandle(fo.hNameMappings));
      end;
      // use DestName as needed...
    end;
  end;

  ModalResult := mrOk;
end;

在 Vista 及更高版本上,您可以选择使用 IFileOperation.CopyItem() instead, which also supports renaming an item on collision. An IFileOperationProgressSink 如果发生重命名冲突,回调可用于发现新文件名。

例如:

uses
  ..., Winapi.ActiveX, Winapi.ShlObj, System.Win.Comobj;

type
  TMyCopyProgressSink = class(TInterfacedObject, IFileOperationProgressSink)
  public
    CopiedName: string;
    function StartOperations: HResult; stdcall;
    function FinishOperations(hrResult: HResult): HResult; stdcall;
    function PreRenameItem(dwFlags: DWORD; const psiItem: IShellItem;
      pszNewName: LPCWSTR): HResult; stdcall;
    function PostRenameItem(dwFlags: DWORD; const psiItem: IShellItem;
      pszNewName: LPCWSTR; hrRename: HResult; const psiNewlyCreated: IShellItem): HResult; stdcall;
    function PreMoveItem(dwFlags: DWORD; const psiItem: IShellItem;
      const psiDestinationFolder: IShellItem; pszNewName: LPCWSTR): HResult; stdcall;
    function PostMoveItem(dwFlags: DWORD; const psiItem: IShellItem;
      const psiDestinationFolder: IShellItem; pszNewName: LPCWSTR;
      hrMove: HResult; const psiNewlyCreated: IShellItem): HResult; stdcall;
    function PreCopyItem(dwFlags: DWORD; const psiItem: IShellItem;
      const psiDestinationFolder: IShellItem; pszNewName: LPCWSTR): HResult; stdcall;
    function PostCopyItem(dwFlags: DWORD; const psiItem: IShellItem;
      const psiDestinationFolder: IShellItem; pszNewName: LPCWSTR;
      hrCopy: HResult; const psiNewlyCreated: IShellItem): HResult; stdcall;
    function PreDeleteItem(dwFlags: DWORD; const psiItem: IShellItem): HResult; stdcall;
    function PostDeleteItem(dwFlags: DWORD; const psiItem: IShellItem; hrDelete: HResult;
      const psiNewlyCreated: IShellItem): HResult; stdcall;
    function PreNewItem(dwFlags: DWORD; const psiDestinationFolder: IShellItem;
      pszNewName: LPCWSTR): HResult; stdcall;
    function PostNewItem(dwFlags: DWORD; const psiDestinationFolder: IShellItem;
      pszNewName: LPCWSTR; pszTemplateName: LPCWSTR; dwFileAttributes: DWORD;
      hrNew: HResult; const psiNewItem: IShellItem): HResult; stdcall;
    function UpdateProgress(iWorkTotal: UINT; iWorkSoFar: UINT): HResult; stdcall;
    function ResetTimer: HResult; stdcall;
    function PauseTimer: HResult; stdcall;
    function ResumeTimer: HResult; stdcall;
  end;

function TMyCopyProgressSink.StartOperations: HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.FinishOperations(hrResult: HResult): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PreRenameItem(dwFlags: DWORD; const psiItem: IShellItem;
  pszNewName: LPCWSTR): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PostRenameItem(dwFlags: DWORD; const psiItem: IShellItem;
  pszNewName: LPCWSTR; hrRename: HResult; const psiNewlyCreated: IShellItem): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PreMoveItem(dwFlags: DWORD; const psiItem: IShellItem;
  const psiDestinationFolder: IShellItem; pszNewName: LPCWSTR): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PostMoveItem(dwFlags: DWORD; const psiItem: IShellItem;
  const psiDestinationFolder: IShellItem; pszNewName: LPCWSTR;
  hrMove: HResult; const psiNewlyCreated: IShellItem): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PreCopyItem(dwFlags: DWORD; const psiItem: IShellItem;
  const psiDestinationFolder: IShellItem; pszNewName: LPCWSTR): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PostCopyItem(dwFlags: DWORD; const psiItem: IShellItem;
  const psiDestinationFolder: IShellItem; pszNewName: LPCWSTR;
  hrCopy: HResult; const psiNewlyCreated: IShellItem): HResult; stdcall;
begin
  CopiedName := pszNewName;
  Result := S_OK;
end;

function TMyCopyProgressSink.PreDeleteItem(dwFlags: DWORD; const psiItem: IShellItem): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PostDeleteItem(dwFlags: DWORD; const psiItem: IShellItem; hrDelete: HResult;
  const psiNewlyCreated: IShellItem): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PreNewItem(dwFlags: DWORD; const psiDestinationFolder: IShellItem;
  pszNewName: LPCWSTR): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PostNewItem(dwFlags: DWORD; const psiDestinationFolder: IShellItem;
  pszNewName: LPCWSTR; pszTemplateName: LPCWSTR; dwFileAttributes: DWORD;
  hrNew: HResult; const psiNewItem: IShellItem): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.UpdateProgress(iWorkTotal: UINT; iWorkSoFar: UINT): HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.ResetTimer: HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.PauseTimer: HResult; stdcall;
begin
  Result := S_OK;
end;

function TMyCopyProgressSink.ResumeTimer: HResult; stdcall;
begin
  Result := S_OK;
end;

procedure TsomeForm.UniFileUpload1Completed(Sender: TObject; AStream: TFileStream);
var
  DestName : string;
  DestFolder : string;
  pfo : IFileOperation;
  psiFrom : IShellItem;
  psiTo : IShellItem;
  Sink : IFileOperationProgressSink;
  bAborted : BOOL;
begin
  DestFolder := UniServerModule.StartPath + 'files\' + UniMainModule.foldername + '\';
  DestName := UniMainModule.FirstName + '_' + UniMainModule.LastName + '.pdf';

  try
    OleCheck(SHCreateItemFromParsingName(PChar(AStream.FileName), nil, IShellItem, psiFrom));
    OleCheck(SHCreateItemFromParsingName(PChar(DestFolder), nil, IShellItem, psiTo));

    OleCheck(CoCreateInstance(CLSID_FileOperation, nil, CLSCTX_ALL, IFileOperation, pfo));
    OleCheck(pfo.SetOperationFlags(FOF_SILENT or FOF_NOCONFIRMATION or FOF_NOCONFIRMMKDIR or FOF_NOERRORUI or FOF_RENAMEONCOLLISION or FOFX_PRESERVEFILEEXTENSIONS));

    Sink := TMyCopyProgressSink.Create;
    OleCheck(pfo.CopyItem(psiFrom, psiTo, PChar(DestName), Sink));
    OleCheck(pfo.PerformOperations());

    pfo.GetAnyOperationsAborted(bAborted);
    if bAborted then
    begin
      // abort handling...
    end;

    DestName := TMyCopyProgressSink(Sink).CopiedName;
    // use DestName as needed...
  except
    // error handling...
  end;
end;