Автогенерация имени файла при наличии дублей
При разработке одной из своих программ передо мной встала задача сохранения файлов, но не просто так, а чтобы имя файла автоматически изменялось, если такой файл уже существует. С подобной задачей очень хорошо справляются менеджеры закачек и браузеры, в случае совпадения дополняя имя нового файла числовым индексом. В результате у меня получилась вот такая функция:Code (Assembler) : Убрать нумерацию
- ;---------------------------------------------------------------
- ; Функция автогенерации имени файла
- ;---------------------------------------------------------------
- ; lpOldName - указатель на проверяемое имя файла
- ; lpNewName - указатель на доступное имя файла
- ;---------------------------------------------------------------
- proc generate_filename lpOldName:DWORD, lpNewName:DWORD
- locals
- fname dd ?
- fext dd ?
- filename rb MAX_PATH
- buff rb MAX_PATH
- idx dd ?
- endl
- pusha
- ; Получить полный путь к файла и указатель на имя
- lea edi,[filename]
- lea eax,[fname]
- invoke GetFullPathName,[lpOldName],MAX_PATH,edi,eax
- ; Проверить наличие файла или каталога
- invoke PathFileExists,edi
- or eax,eax
- ; Файла нет, сразу вернуться
- jz .loc_use_filename
- ; Индекс для автогенерации имени файла
- mov [idx],1
- ; Поискать расширение в названии файла
- invoke lstrlen,edi
- add edi,eax
- mov [fext],edi
- std
- mov ecx,edi
- sub ecx,[fname]
- mov al,'.'
- repne scasb
- cld
- or ecx,ecx
- jz @f
- ; Расширение найдено
- inc edi
- mov byte [edi],0
- inc edi
- mov [fext],edi
- @@:
- ; Сгенерировать имя файла с индексом
- lea edi,[buff]
- lea ebx,[filename]
- invoke wsprintf,edi,.mask,ebx,[idx],[fext]
- add esp,20
- ; Проверить наличие файла или каталога
- invoke PathFileExists,edi
- or eax,eax
- ; Файла нет, использовать это имя
- jz .loc_use_filename
- ; Следуюший номер счетчика
- inc [idx]
- jmp @b
- .loc_use_filename:
- ; Скопировать имя файла в буфер-приемник
- invoke lstrcpy,[lpNewName],edi
- popa
- ret
- ; Маска для автогенерации имени файла
- .mask db '%s (%i).%s',0
- endp
Для обратного преобразования имени файла в некоторых случаях помогает штатная функция PathUndecorate, избавляющая строку с именем файла от числовых индексов, а нас от придумывания собственных функций. Но это только если числовое дополнение имеет строго определенный формат, в других ситуациях рукоделия не избежать.
Как правильно подсказали в комментариях, при очень большом количестве файлов перебор имен по единичке может сильно тормозить. Для решения этой проблемы алгоритм поиска свободного имени файла изменяется. Стадия 1: индекс увеличивается с шагом 1000 до первого свободного имени. Стадия 2: индекс уменьшается с шагом 100 до первого занятого имени. Стадия 3: индекс увеличивается с шагом 10 до первого свободного имени. Финальная стадия: индекс уменьшается на единицу до первого занятого имени. Даже при нескольких тысячах файлов количество итераций не будет превышать 20-30.
Code (Assembler) : Убрать нумерацию
- ;---------------------------------------------------------------
- ; Функция автогенерации имени файла
- ;---------------------------------------------------------------
- ; lpOldName - указатель на проверяемое имя файла
- ; lpNewName - указатель на доступное имя файла
- ;---------------------------------------------------------------
- proc generate_filename lpOldName:DWORD, lpNewName:DWORD
- locals
- fname dd ?
- fext dd ?
- filename rb MAX_PATH
- buff rb MAX_PATH
- idx dd ?
- endl
- pusha
- ; Получить полный путь к файла и указатель на имя
- lea edi,[filename]
- lea eax,[fname]
- invoke GetFullPathName,[lpOldName],MAX_PATH,edi,eax
- ; Проверить наличие файла или каталога
- invoke PathFileExists,edi
- or eax,eax
- ; Файла нет, сразу вернуться
- jz .loc_use_filename
- ; Поискать расширение в названии файла
- invoke lstrlen,edi
- add edi,eax
- mov [fext],edi
- std
- mov ecx,edi
- sub ecx,[fname]
- mov al,'.'
- repne scasb
- cld
- or ecx,ecx
- jz @f
- ; Расширение найдено
- inc edi
- mov byte [edi],0
- inc edi
- mov [fext],edi
- @@:
- ; Индекс для автогенерации имени файла
- mov [idx],1
- stdcall .generate_filename
- or eax,eax
- ; Сразу использовать это имя
- jz .loc_use_filename
- ;------------------------------------------------------
- ; Стадия 1 - инкрементальный поиск с шагом 1000
- ;------------------------------------------------------
- .stage_1:
- add [idx],1000
- stdcall .generate_filename
- or eax,eax
- jnz .stage_1
- ;------------------------------------------------------
- ; Стадия 2 - декрементальный поиск с шагом 100
- ;------------------------------------------------------
- .stage_2:
- sub [idx],100
- stdcall .generate_filename
- or eax,eax
- jz .stage_2
- ;------------------------------------------------------
- ; Стадия 3 - инкрементальный поиск с шагом 10
- ;------------------------------------------------------
- .stage_3:
- add [idx],10
- stdcall .generate_filename
- or eax,eax
- jnz .stage_3
- ;------------------------------------------------------
- ; Стадия 4 - декрементальный поиск с шагом 1
- ;------------------------------------------------------
- .stage_4:
- dec [idx]
- stdcall .generate_filename
- or eax,eax
- jz .stage_4
- ; Окончательный индекс
- inc [idx]
- stdcall .generate_filename
- .loc_use_filename:
- ; Скопировать имя файла в буфер-приемник
- invoke lstrcpy,[lpNewName],edi
- jmp .loc_ret
- .generate_filename:
- ; Сгенерировать имя файла с индексом
- lea edi,[buff]
- lea ebx,[filename]
- invoke wsprintf,edi,.mask,ebx,[idx],[fext]
- add esp,20
- ; Проверить наличие файла или каталога
- invoke PathFileExists,edi
- retn
- .loc_ret:
- popa
- ret
- ; Маска для автогенерации имени файла
- .mask db '%s (%i).%s',0
- endp
В приложении примеры программ с исходным кодом, создающие при каждом запуске пустой файл filename.txt. Имя файла меняется в зависимости от того, какие имена файлов уже заняты.
Просмотров: 2741 | Комментариев: 13
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(08.02.2016 в 14:38):
Добавил второй вариант алгоритма. Аттач тоже обновил.
ManHunter
(08.02.2016 в 07:57):
Ценное уточнение, спасибо! Но я бы сделал не так.
1. Инкремент индекса с шагом 1000 до первого НЕ найденного файла.
2. Декремент индекса с шагом 100 до первого найденного файла.
3. Инкремент индекса с шагом 10 до первого НЕ найденного файла.
4. Декремент индекса с шагом 1 до первого найденного файла.
5. Профит!
Даже при нескольких десятках тысяч файлов суммарно будет выполнено всего 20-30 итераций. Допишу и добавлю такой вариант тоже.
1. Инкремент индекса с шагом 1000 до первого НЕ найденного файла.
2. Декремент индекса с шагом 100 до первого найденного файла.
3. Инкремент индекса с шагом 10 до первого НЕ найденного файла.
4. Декремент индекса с шагом 1 до первого найденного файла.
5. Профит!
Даже при нескольких десятках тысяч файлов суммарно будет выполнено всего 20-30 итераций. Допишу и добавлю такой вариант тоже.
Сергей Качалов
(08.02.2016 в 01:51):
Если одноименных файлов наберется достаточно большое количество, алгоритм начинает нехило тормозить. Был прецедент в банке. Алгоритм изменил следующим образом: когда счетчик переваливает десяток, номер генерится случайным образом, тоже с проверкой на дублирование. Это если кто в промышленных масштабах собирается использовать.
ManHunter
(28.09.2015 в 16:31):
Было б за что. Обычная разминка для ума. А под xor eax,eax я имел в виду вот это:
; EAX=1 - файл есть
; EAX=0 - файла нет
proc existA lpszFileName:DWORD
local wfd:WIN32_FIND_DATA
lea eax,[wfd]
invoke FindFirstFileA,[lpszFileName],eax
cmp eax,-1
je @f
invoke FindClose,eax
xor eax,eax ; <--- в этом контексте совпадает с dec eax, но на 1 байт больше
@@:
inc eax
ret
endp
; EAX=1 - файл есть
; EAX=0 - файла нет
proc existA lpszFileName:DWORD
local wfd:WIN32_FIND_DATA
lea eax,[wfd]
invoke FindFirstFileA,[lpszFileName],eax
cmp eax,-1
je @f
invoke FindClose,eax
xor eax,eax ; <--- в этом контексте совпадает с dec eax, но на 1 байт больше
@@:
inc eax
ret
endp
addhaloka
(28.09.2015 в 15:05):
Это вроде бы лишнее - проверил в одном патче - не работает (хотя, может я что-то не так делаю). А вот
invoke FindClose,eax
dec eax
@@:
inc eax
ret
срабатывает, как нужно, и при
stdcall existW,targetFile
cmp eax,1
je метка
и при
stdcall existW,targetFile
cmp eax,0
jne метка
Ещё раз спасибо.
ManHunter
(28.09.2015 в 13:20):
Даже не так. Можно сделать еще на байт короче, если посмотреть в отладчике, что FindClose в случае успеха возвращает EAX=1. Финальная версия:
; EAX=1 - файл есть
; EAX=0 - файла нет
proc existA lpszFileName:DWORD
local wfd:WIN32_FIND_DATA
lea eax,[wfd]
invoke FindFirstFileA,[lpszFileName],eax
cmp eax,-1
je @f
invoke FindClose,eax
dec eax
@@:
inc eax
ret
endp
; EAX=1 - файл есть
; EAX=0 - файла нет
proc existA lpszFileName:DWORD
local wfd:WIN32_FIND_DATA
lea eax,[wfd]
invoke FindFirstFileA,[lpszFileName],eax
cmp eax,-1
je @f
invoke FindClose,eax
dec eax
@@:
inc eax
ret
endp
ManHunter
(28.09.2015 в 13:04):
xor eax,eax после invoke FindClose,eax решает проблему. Все равно получается красивее :)
addhaloka
(28.09.2015 в 12:56):
Спасибо, так красивей. Я раньше эту функцию вообще в дизассемблированном виде юзал (пока не узнал, что исходники в m32lib лежат)), типа так:
proc existA
push ebp
mov ebp,esp и т. д.
Но в таком виде, в этом примере она не работала, поэтому переписал по нормальному.
p.s. Хотя в оптимизированном виде несколько ограничен простор, т. е. так уже нельзя:
stdcall existW,targetFile
cmp eax,1
je .skipopen
а только так:
stdcall existW,targetFile
cmp eax,0
jne .skipopen
А у меня в большинстве проектов именно первый вариант. Но всё-равно, большое спасибо - этот и много других примеров из блога оказались очень полезны, как в изучении FASM и вообще ассемблера, так и в использовании в своих поделиях. :)
ManHunter
(28.09.2015 в 11:29):
Тут еще можно поспорить, что займет меньше места. Добавление двух функций в импорт kernel32 + отдельная процедура проверки, или импорт PathFileExists.
Да и процедуру можно немного оптимизировать :)
; EAX=0 - файла нет
; EAX!=0 - файл есть
proc existA lpszFileName:DWORD
local wfd:WIN32_FIND_DATA
lea eax,[wfd]
invoke FindFirstFileA,[lpszFileName],eax
cmp eax,-1
je @f
invoke FindClose,eax
@@:
inc eax
ret
endp
Да и процедуру можно немного оптимизировать :)
; EAX=0 - файла нет
; EAX!=0 - файл есть
proc existA lpszFileName:DWORD
local wfd:WIN32_FIND_DATA
lea eax,[wfd]
invoke FindFirstFileA,[lpszFileName],eax
cmp eax,-1
je @f
invoke FindClose,eax
@@:
inc eax
ret
endp
addhaloka
(28.09.2015 в 10:45):
Вместо PathFileExists (чтобы shlwapi.dll не подключать) можно использовать exist из masm32 (давно уже в своих поделках юзаю), как-то так:
...
; Проверить наличие файла или каталога
stdcall existA,edi
...
proc existA lpszFileName:DWORD
local wfd:WIN32_FIND_DATA
lea eax,[wfd]
invoke FindFirstFileA,[lpszFileName],eax
cmp eax,0FFFFFFFFh
jne @F
mov eax,0
jmp .endexist
@@:
invoke FindClose,eax
mov eax,1
.endexist:
ret
endp
...
; Проверить наличие файла или каталога
stdcall existA,edi
...
proc existA lpszFileName:DWORD
local wfd:WIN32_FIND_DATA
lea eax,[wfd]
invoke FindFirstFileA,[lpszFileName],eax
cmp eax,0FFFFFFFFh
jne @F
mov eax,0
jmp .endexist
@@:
invoke FindClose,eax
mov eax,1
.endexist:
ret
endp
SMaSm-94
(25.09.2015 в 19:22):
Понятно. Я тоже быстро считаю, но привык наоборот макросами пользоваться. Они немного упрощают процесс написания программ.
А за статейку спасибо. У Вас на сайте очень много эксклюзивного контента для FASM'а)
А за статейку спасибо. У Вас на сайте очень много эксклюзивного контента для FASM'а)
ManHunter
(25.09.2015 в 08:08):
Да как-то привык уже. И количество считаю чуть ли не на подсознании :)
SMaSm-94
(25.09.2015 в 02:26):
Уважаемый ManHunter, почему вы не юзаете макрос cinvoke с wsprintf? Код же можно немного уменьшить с его помощью + не надо считать сколько байт нужно выталкивать из стека))
Добавить комментарий
Заполните форму для добавления комментария