Как я могу установить атрибут сжатия файла в Delphi?

Как я могу сжать файлы (установить атрибут «c») из Delphi? Я говорю о функции «сжать содержимое для экономии места на диске», доступной в NTFS.

Кажется, что FileSetAttr не позволяет мне установить атрибут 'c' для файла.


person Z80    schedule 09.08.2011    source источник


Ответы (3)


В документации для SetFileAttributes() поясняется, что флаг FILE_ATTRIBUTE_COMPRESSED не принимается этой функцией (хотя он для GetFileAttributes). Вместо этого он указывает:

Чтобы установить степень сжатия файла, используйте DeviceIoControl. с помощью FSCTL_SET_COMPRESSION операция.

Ссылка FSCTL_SET_COMPRESSION, в частности, объясняет как именно это сделать. Это выглядит примерно так:

const
  COMPRESSION_FORMAT_NONE = 0;
  COMPRESSION_FORMAT_DEFAULT = 1;
  COMPRESSION_FORMAT_LZNT1 = 2;

procedure SetCompressionAttribute(const FileName: string; const CompressionFormat: USHORT);
const
  FSCTL_SET_COMPRESSION = $9C040;
var
  Handle: THandle;
  Flags: DWORD;
  BytesReturned: DWORD;
begin
  if DirectoryExists(FileName) then
    Flags := FILE_FLAG_BACKUP_SEMANTICS
  else if FileExists(FileName) then
    Flags := 0
  else
    raise Exception.CreateFmt('%s does not exist', [FileName]);

  Handle := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, Flags, 0);
  Win32Check(Handle <> INVALID_HANDLE_VALUE);
  try
    if not DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, @CompressionFormat, SizeOf(Comp), nil, 0, BytesReturned, nil) then
      RaiseLastOSError;
  finally
    CloseHandle(Handle);
  end;
end;
person David Heffernan    schedule 09.08.2011
comment
Я предполагаю, что это допустимо в GetFileAttributes()? Если нет, то его можно просто удалить из исходников. - person Rudy Velthuis; 10.08.2011
comment
Правильно, @Rudy. Есть несколько атрибутов, которые можно прочитать с помощью GetFileAttributes, но нельзя установить с помощью SetFileAttributes; они перечислены на странице SFA MSDN. - person Rob Kennedy; 10.08.2011
comment
@Altar Почему принять изменение? Были ли проблемы с моим ответом? - person David Heffernan; 09.12.2013
comment
Я не уверен, что часть if Handle=0 верна. Разве вы не должны вместо этого проверить INVALID_HANDLE_VALUE? См. stackoverflow.com/a/8241115 ???? - person Günther the Beautiful; 27.06.2019
comment
Также меня озадачивает использование типа System.Comp - Is неужели гарантируется совпадение размера компа и нетипизированной константы (для Win32 и Win64)? Вы можете остановиться на этом? - person Günther the Beautiful; 27.06.2019
comment
@GünthertheBeautiful Спасибо, вы правы, я обновил ответ. - person David Heffernan; 27.06.2019
comment
@GünthertheBeautiful Конечно, здесь не место говорить о System.Comp - person David Heffernan; 27.06.2019

вы также можете использовать CIM_DataFile и CIM_Directory классов WMI, оба имели два метода с именем Сжатие и UnCompress, который можно использовать для установки сжатия NTFS в файле или папке.

Проверьте эти образцы (если )

Сжать (NTFS) или распаковать файл

function  CompressFile(const FileName:string;Compress:Boolean):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_DataFile.Name="%s"',[StringReplace(FileName,'\','\\',[rfReplaceAll])]));
  if Compress then
    Result:=FWbemObject.Compress()
  else
    Result:=FWbemObject.UnCompress();
end;

Сжать (NTFS) или распаковать папку

function  CompressFolder(const FolderName:string;Recursive, Compress:Boolean):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
  StopFileName  : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_Directory.Name="%s"',[StringReplace(FolderName,'\','\\',[rfReplaceAll])]));
  if Compress then
    if Recursive then
     Result:=FWbemObject.CompressEx(StopFileName, Null, Recursive)
    else
     Result:=FWbemObject.Compress()
  else
    if Recursive then
     Result:=FWbemObject.UnCompressEx(StopFileName, Null, Recursive)
    else
     Result:=FWbemObject.UnCompress();
end;
person RRUZ    schedule 09.08.2011
comment
Мне любопытно, можно ли что-нибудь получить от использования WMI, а не родного Win32? - person David Heffernan; 10.08.2011
comment
@David, существуют некоторые ситуации, когда это может быть очень полезно, например: 1) использование механизма сценариев Object pascal, который не поддерживает функции WinApi 2) использование из установщика, такого как Inno Setup 3) когда вам нужно сжать папку или файл на удаленном компьютере. .. и, наконец, только для того, чтобы показать, что всегда есть более одного способа содрать шкуру с кошки :) - person RRUZ; 10.08.2011
comment
@RRUZ: Хотя я предпочитаю не сдирать кожу с кошек :-), мне всегда нравятся альтернативные решения проблемы. - person Marjan Venema; 10.08.2011

Ну вот. Вызовите это для файла или папки, и он сделает эту работу за вас. State=true делает его сжатым, State=false отменяет сжатие. Помните, однако, что если вы запустите его для папки, он только изменит атрибут и сделает так, чтобы будущие файлы, созданные в этой папке, были сжаты. Чтобы сжать те, которые уже есть, вам нужно выполнить итерацию и вызвать это для каждого файла (FindFirst/FindNext/FindClose). ХТН.

function CompressFile(filepath: string; state: boolean): boolean;
  const
    COMPRESSION_FORMAT_DEFAULT = 1;
    COMPRESSION_FORMAT_NONE = 0;
    FSCTL_SET_COMPRESSION: DWord = $9C040;
  var
    compsetting: Word;
    bytesreturned: DWord;
    FHandle: THandle;
  begin
   //if not os_is_nt then
   //  raise Exception.Create('A Windows NT based OS is required for this function.');
    FHandle := CreateFile(PChar(filepath), GENERIC_READ or GENERIC_WRITE,
              0, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
    if FHandle = INVALID_HANDLE_VALUE then
      raise Exception.Create('CompressFile Message: ' + SysErrorMessage(GetLastError));
    if state = true then
      compsetting := COMPRESSION_FORMAT_DEFAULT
    else
      compsetting := COMPRESSION_FORMAT_NONE;
    try
      Result := DeviceIOControl(FHandle, FSCTL_SET_COMPRESSION, @compsetting,
         sizeof(compsetting), nil, 0, bytesreturned, nil);
    finally
      CloseHandle(FHandle);
    end;
  end;
person Glenn1234    schedule 09.08.2011
comment
Почему вы указываете FILE_SHARE_READ or FILE_SHARE_WRITE? Почему вы указываете FILE_FLAG_BACKUP_SEMANTICS? Почему бы тебе не написать result := DeviceIOControl? Вы также должны проверить наличие FHandle=0. А также для файла, который не существует. Короче говоря, это может быть связано с хорошей уборкой. - person David Heffernan; 10.08.2011
comment
1 и 2. Это имеет значение? Это было скопировано. 3. Опять же имеет ли значение, пока работает? 4. Функция используется в программе, которая предоставляет надежные имена/пути к файлам, так что это не имеет значения. Но да, на автономном FHandle следует проверять 0 или не 5. Отсутствие файла не имеет отношения к этой функции, поскольку функция FileExists не работает с папками. Проверка на это делает его частично нефункциональным, поскольку эту функцию можно правильно вызывать для папок. - person Glenn1234; 10.08.2011
comment
а на 2 для сжатия папок требуется FILE_FLAG_BACKUP_SEMANTICS. - person Glenn1234; 10.08.2011
comment
Флаги обмена кажутся странными. Почему вы хотите, чтобы другой процесс открывал файл во время его сжатия? Похоже, это не лучшая идея. Это работает? - person David Heffernan; 10.08.2011
comment
Хорошо, я нашел документацию для FILE_FLAG_BACKUP_SEMANTICS. В документах CreateFile говорится, что вы должны установить этот флаг, чтобы получить дескриптор каталога. Я этого не знал. Меня все еще интересует, как система может сжимать файл, одновременно позволяя другим процессам записывать в него. - person David Heffernan; 10.08.2011
comment
Опять же, имеет ли это значение, пока это работает? По стечению обстоятельств для меня это очень похоже на программирование. - person johnny; 10.08.2011
comment
Примечание. Я не утверждаю, что думаю, что вы ошибаетесь, я просто хотел бы понять, почему вы сделали это именно так. - person David Heffernan; 10.08.2011
comment
Справедливо. Некоторое время я думал, что я был в Usenet с ответами, которые это получило. Совместное использование флагов не имеет смысла, я согласен. Обычно я не люблю переусердствовать с кодом (как было сказано, он был скопирован из программы, где правильный путь/файл уже проверен в другом месте). Тем не менее, разве проверка GetLastError = 2 после CreateFile не будет достаточной для проверки пути/файла? - person Glenn1234; 10.08.2011
comment
@ Glenn1234 - Да, Usenet может быть, мягко говоря, интересным местом. - person TWA; 10.08.2011
comment
@Glenn Множество способов проверить существование, это не так важно, как вы сказали. Был ли обмен, который казался самым странным, и я вижу, что вы его отредактировали. - person David Heffernan; 10.08.2011
comment
@johnny Больше похоже на реакцию на кого-то, кто заботится о том, есть ли один или два пробела после := . Имеет ли значение, скажу ли я, если Function_That_Returns_Condition, то Result := true else Result := false или Result := Function_That_Returns_Condition? Второй выглядит чище, но оба работают одинаково, верно? - person Glenn1234; 11.08.2011
comment
@ Glenn1234, ты прав, пункт 3 не так важен. Хотя когда это сочеталось с Имеет ли это значение? Это было скопировано. Я немного забеспокоился. - person johnny; 11.08.2011