Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: WinAPI и системное программирование > Порядок файлов в ФС


Автор: MetalFan 23.4.2012, 00:09
Всем доброго времени суток.
Возник вопрос: как изменить порядок следования фалов в файловой системе в одном каталоге.

Предыстория: Есть некоторые mp3 плееры,  которые умеют воспроизводить контент папки только в той последовательности, в которой файлы были записаны в эту папку без какой-либо сортировки по названию файла.

Задача: написать функцию/программу, которая будет уметь отсортировать файлы физически в заданной папке по имени или в случайном порядке...

Придуман следующий алгортим (пока для "перемешивания" файлов в папке): 
  • получаем список файлов в заданной папке
  • "перемешиваем" его
  • переносим пофайлово во временную папку с (или без) присвоением каждому файлу префикса в виде №п/п(номера по-порядку) в перемешанном списке. 
  • Удаляем исходную(уже пустую) папку
  • Переименовываем временную папку в исходную
Все вроде как красиво... но. 
Алгоритм на 99.99% срабатывает только в том случае, если происходит перенос с переименованием файла с учетом  НПП в имени файла.
Т.е. порядок копирования учитывается только для FAT, для NTFS похоже существует некий свой алгоритм сортировки "по умолчанию"...
Опытные люди, просветите, прав ли я в том, что порядок копирования имеет значение только для FAT(32)?

Пример кода, реализующего алгоритм (DXE):
Код

var
  lFiles: TStringDynArray;
  i: Integer;
  lFN, lNewFN: string;
  lRnd: Integer;
  lTmpPath: string;
  lLen: Integer;
begin
  if FMask = '' then
    FMask := '*';
  lFiles := TDirectory.GetFiles(FPath, FMask, TSearchOption.soTopDirectoryOnly );
  lLen := Length(IntToStr(Length(lFiles)));
  for i := 0 to High(lFiles) do
  begin
    lRnd := Random( Length(lFiles) );
    lFN :=  ExtractFileName( lFiles[lRnd] );
    lFiles[lRnd] := ExtractFileName( lFiles[i] );
    lFiles[i] := lFN;
  end;
  TInterlocked.Exchange( FTotal, Length(lFiles));
  lTmpPath := ExcludeTrailingPathDelimiter(FPath)+'~tmp\';
  if not ForceDirectories(lTmpPath) then Exit;
  TInterlocked.Exchange( FProgress, 0);
  for i := 0 to High(lFiles) do
  begin
    lNewFN := lFiles[i];
    lNewFN := TRegEx.Replace( lNewFN, '^\d+\.\s+', '');
    if FInclNumber then
       lNewFN := Format('%.'+IntToStr(lLen)+'d. %s', [succ(i), lNewFN] );
    if Terminated then Break;
    MoveFileEx( PChar(FPath + lFiles[i]), PChar({FPath }lTmpPath + lNewFN),  MOVEFILE_WRITE_THROUGH or MOVEFILE_COPY_ALLOWED);
    TInterlocked.Exchange( FProgress, Succ(i));
  end;
  lTmpPath := ExcludeTrailingPathDelimiter(lTmpPath);
  FPath := ExcludeTrailingPathDelimiter(FPath);
  CU_ShFlOp(sfoDelete, [FPath], []);
  CU_ShFlOp(sfoRename, [lTmpPath], [FPath] );

Код является "вытяжкой" из класса-потока и использует самописную функцию-обертку над SHFileOperation (CU_ShFlOp).

Автор: MetalFan 24.4.2012, 06:18
Keeper89, ну да... вот такие они загадочные. 
Такой порядок файлов, к примеру, показывает тотал коммандер, когда выбрана сортировка "Без сортировки". Т.е. для FAT это порой "в перемешку", и на деле в том порядке, в каком в эту папку были скопированы/перенесены файлы.
И самая засада слушать аудиокниги, которые "нечаянно" были записаны таким образом. То 2 глава 5й части, а следом 10 глава 2 части...

Автор: Чучмек 24.4.2012, 09:44
Вообще в NTFS структура каталога оптимизирована для быстрого нахождения файла. Поэтому поиск через FindFirstFile/FindNextFile ,будет выдавать файлы согласно этой оптимизации. 
Сейчас ради интереса проверил - возвращает отсортированные по имени, если файл переименовать - порядок меняется.

Цитата(MetalFan @  23.4.2012,  00:09 Найти цитируемый пост)
Предыстория: Есть некоторые mp3 плееры,  которые умеют воспроизводить контент папки только в той последовательности, в которой файлы были записаны в эту папку без какой-либо сортировки по названию файла.

Но половина из них все же сортируют файлы с короткими именами (8+3).
И на всех из них (почти?) FAT.

Автор: Keeper89 24.4.2012, 11:01
А что если не копировать файлы в новую папку, а побайтно читать и писать в новый? После, старый удалить.

Автор: Qu1nt 24.4.2012, 12:44
MetalFan, уверен, что ты это видел, но все-таки:
Цитата(FindNextFile function)

The order in which the search returns the files, such as alphabetical order, is not guaranteed, and is dependent on the file system. If the data must be sorted, the application must do the ordering after obtaining all the results.

The order in which this function returns the file names is dependent on the file system type. With the NTFS file system and CDFS file systems, the names are usually returned in alphabetical order. With FAT file systems, the names are usually returned in the order the files were written to the disk, which may or may not be in alphabetical order. However, as stated previously, these behaviors are not guaranteed.

Автор: MetalFan 24.4.2012, 14:21
Цитата(Чучмек @  24.4.2012,  09:44 Найти цитируемый пост)
Но половина из них все же сортируют файлы с короткими именами (8+3).
И на всех из них (почти?) FAT.

Да вот как раз имею парочку дешевых китайских плееров из второй половины, которые читают файлы в том порядке, в каком они есть.
Плеер на столько примитивен, что имеет только три кнопки и 6 функций: плей/пауз/некст/прев/громче/тише. И не умеет, зараза, читать файлы ни рандомно ни по алфавиту.
Вот и заморочился такой программкой.
А читать/писать файлы по-байтам, имхо, еще более накладно, чем как сейчас сделано. Папку в 4Гб и почти 400 файлов алгортим обрабатывает не дольше 2-3х минут.

В идеале было бы вызывать какую-нибудь "хитрую" системную функцию, которая отсортирует/перемешает файлы физически так, как надо без доп.переноса в другую папку....


Qu1nt, Представь себе, если и читал, то совершенно из головы вылетело. 
Хотя я к такому же заключению и так пришел экспериментально...

Автор: Keeper89 24.4.2012, 16:52
MetalFan, посмотри вот это
  • http://www.codeproject.com/Articles/95721/FAT-32-Sorter
  • http://www.murraymoffatt.com/software-problem-0010.html

Автор: MetalFan 25.4.2012, 12:30
Keeper89, ой спасибо... особенно за первую ссылку.
я что-то сам не додумался погуглить...
Попробую реализовать то же самое под делфю...

Автор: Keeper89 25.4.2012, 13:38
MetalFan, а зачем реализовывать если там уже есть готовое? Это часть какой-то утилиты?

Автор: MetalFan 25.4.2012, 13:49
Keeper89, да понятно, что реализовано, но на сях...
а хочется свою утилиту и на делфе) И более продвинутую...

Автор: Чучмек 28.4.2012, 23:51
Цитата(Keeper89 @  24.4.2012,  16:52 Найти цитируемый пост)
http://www.codeproject.com/Articles/95721/FAT-32-Sorter

Особо не вчитывался, но думаю логика совпадает - переставить местами заголовки файлов.
Когда то писал нечто подобное, только менял местами заголовки разделов. А для файлов/каталогов - делал просто читалку(должна была выводить список файлов).
Могу поискать код.    

Автор: MetalFan 29.4.2012, 09:11
Цитата(Чучмек @  28.4.2012,  23:51 Найти цитируемый пост)
Могу поискать код.
 Ну если будет время и вдруг найдется код, то я конечно же буду не против на него посмотреть)

Автор: Чучмек 6.5.2012, 21:16
Найти - нашел. Зае...ся переводить в читаемый вид.
А еще застопорился на http://forum.vingrad.ru/forum/topic-351410.html - пока нашел в чем дело... - (часть каталогов открывалась, а часть нет) уже дошло до того что начал искать FAT сжатие.
Код

var fat:TFATclass1;

procedure TForm1.FormCreate(Sender: TObject);
begin
 fat:=TFATclass1.create;
 if cr_NoFAT = fat.assignDisk('L') then
   begin
   fat.destroy;
   application.Terminate;
   exit;
   end;
 TreeView1.Items.AddChild(TreeView1.Items.Add(nil,fat.DriveLetter+':\   '+fat.VolumeLabel),'').ImageIndex:=-1;
end;

procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode;
  var AllowExpansion: Boolean);
var
 fc:TContentFATFolder;
 fh:TFileHeader;
 tmpnode:TtReeNode;
 i,n:integer;
begin

  if ((NODE.Data<>nil)or(TreeView1.Items[0]=NODE))and(Node.GetNextChild(Node.getFirstChild)=nil)  then
   begin
   fc:=fat.getFolderContent(cardinal(NODE.Data));
   fc.Sort;
   if fc.isRootDirectory then n:=0 else n:=2;
   for i:=n to fc.FileCount-1 do
    begin
    fh:=fc.Files[i];
    tmpnode:=TreeView1.Items.AddChild(node,fh.FileName);
    tmpnode.Data:=pointer(fh.FATShortNameFileHeader.FirstClusterLow or (fh.FATShortNameFileHeader.FirstClusterHigh shl 16));
    if (fh.FATShortNameFileHeader.FileAttributes and cfa_Dir) =cfa_Dir then
      begin
      tmpnode.ImageIndex:=0;
      tmpnode.SelectedIndex:=0;
      with TreeView1.Items.AddChild(tmpnode,'')do
       begin
       ImageIndex:=-1;
       SelectedIndex:=-1;
       end;
      end   else with tmpnode do
                 begin
                 ImageIndex:=-1;
                 SelectedIndex:=-1;
                 end;
    end;
   fc.Destroy;
   end;



end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if fat<>nil then fat.Destroy;
end;

Для сохранения отсортированного каталога в TFATclass1 нужно добавить добавил простую функцию SaveSortedFolder принимающую в качестве параметра TFileHeader TContentFATFolder


Автор: MetalFan 9.5.2012, 20:50
Чучмек, ого, спасибо, покурю на досуге. Это полностью самописное или откуда-то выбранное?

Автор: Чучмек 9.5.2012, 22:00
Только с описания FAT.
Сохранение каталога нужно переписать. Свободное место в каталоге нужно забивать нулями до конца, а не ограничиваться одной завершающей записью. 

Автор: Чучмек 10.5.2012, 03:41
Вот переписал
Код

function TFATclass1.SaveSortedFolder(Folder: TContentFATFolder): integer;
var
  nCurrentCluster:cardinal;
  n:cardinal;
  rc,ir,psc:cardinal;
begin
if Folder.pID<>ID then
 begin
 result:=crs_NoBelong;
 exit;
 end;
result:=0;
rc:=ClusterSize div sizeof(TFATNameFileHeader);
Folder.nr:=0;     // неудобно использовать getFirst
Folder.nf:=0;
if (FATtype = cft_FAT16)and Folder.isRootDirectory then
  begin
  nCurrentCluster:=2;
  psc:=ROOT;
  ir:=0;
  while ir<FATFirstSector.BPB.Root do
   begin
   if FATFirstSector.BPB.Root<(rc+ir)then ReadCluster(psc,nCurrentCluster,ClusterBuf); //Если размер корневого каталога не кратен размеру кластера
   for n:=0 to rc-1 do
    begin
    inc(ir);
    PFATNameFileHeader(cardinal(ClusterBuf)+n*sizeof(TFATNameFileHeader))^:=Folder.GetNext;
    if ir>=FATFirstSector.BPB.Root then break;
    end;
   SaveCluster(psc,nCurrentCluster,ClusterBuf);
   inc(nCurrentCluster);
   end;
  end else
   begin
   nCurrentCluster:=Folder.FFirstCluster;
   psc:=dClusters;
   while {(nCurrentCluster<>LastClusterCode)and}(nCurrentCluster>=2)and(nCurrentCluster<=MaxNextClusterCode) do
    begin
    for n:=0 to  rc-1 do
    PFATNameFileHeader(cardinal(ClusterBuf)+n*sizeof(TFATNameFileHeader))^:=Folder.GetNext;
    SaveCluster(psc,nCurrentCluster,ClusterBuf);
    nCurrentCluster:=getClusterInfo(nCurrentCluster);
    end;
   end;

end;


Нашел еще один бок - Если у файла без LFN контрольная сумма равна 0,то поле FileName структуры TFileHeader не заполняется, поскольку SFN такого файла воспринимается как завершение цепочки LFN
Код

procedure TContentFATFolder.ADD(NFH:TFATNameFileHeader);
...
if (FileHeaders[FCount].n<>0)or(FileHeaders[FCount].Chksum<>sum)then 
 
sum=0, а n и Chksum у вновь добавленного элемента массива FileHeaders могут содержать все что угодно, и очень даже вероятно что ноль.

Unit во вложении заменил на исправленный. 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)