PureBasic - форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » PureBasic - форум » Программирование на PureBasic » Поиск дубликатов файлов


Поиск дубликатов файлов

Сообщений 1 страница 24 из 24

1

Search duplicates
программа поиска дубликатов файлов

Скачать: yandex upload.ee

https://i.imgur.com/9AWhBGy.png

Назначение
Поиск дубликатов файлов.

Использование
Бросить файлы и папки в окно программы или открыть используя кнопки. Далее нажать кнопку поиска, появится список файлов дубликатов по группам размеров. Далее нажать кнопку "Удалить", чтобы отмеченные галкой были удалены. При ошибках удаления файлов будет выдано сообщение, например для файлов только для чтения. Кнопка "Очистить" очищает оба списка и можно добавлять новые папки/файлы для проверки дубликатов.
У нижнего списка есть контекстное меню, чтобы открыть файл или его расположение, а также двойной клик открывает файл.

план
Сделать поддержку CSV
Сделать установку уровня запрета удаления.

Cтолкнулся с проблемой. Как сделать иконки в ListIconGadget в том числе и в колонках? Ранее у меня была отдельная колонка с иконкой замка чтобы запретить удалять с какой либо папки. В AutoIt3 используется структура LVITEM, но мне хотелось бы сделать это в Linux, то есть чисто средствами PureBasic сделать. Там есть одна функция, которая меняет иконку, но у неё нет параметра колонки из чего я делаю вывод, что иконка меняется только у первой колонки (и создаётся только у первой). Была ещё идея включить флаг чекбоксов и попробовать заменить иконку чекбокса своей иконкой, то есть у меня 3 положения как и у 3-х позиционного чекбокса, то есть замок открыт, закрыт с возможностью открыть, закрыт без возможности открыть. Либо отказаться от иконки и показывать запрет в виде -1, 0, 1 и дальше уровни приоритета до 9, хотя это не так очевидно/наглядно как иконки.

Примерно так должно быть: скрин

Отредактировано AZJIO (11.08.2021 20:05:00)

0

2

Может имеет смысл вместо ListIconGadget использовать таблицу на основе канваса?
Несколько примеров https://www.purebasic.fr/english/viewto … mp;t=72402
https://www.purebasic.fr/english/viewto … mp;t=54022

0

3

Когда писал подобную утилиту, остановился на
https://i115.fastpic.org/big/2021/0805/c1/9136eda28bea5c2a14f818a8e9c557c1.png

0

4

egons
Вы спутали темы, то была тема Синхронизация
_____________________________________________

Я тут поэкспериментировал и мне удалось создать карту списков. Изначально не понимал как добавлять элемент в список в уже существующий. Ну и конечно лишняя операция конвертирование размера файла в строку, чтобы использовать как ключ карты.

Код:
Structure Files
    List Path.s()
EndStructure

NewMap FilesPS.Files()


If AddElement(FilesPS()/Path())
	FilesPS()/Path() = 
EndIf
Код:
; Перебор файлов
tmp$ = aSePath(c) + sName
Size$ = Str(FileSize(tmp$))
If FindMapElement(FilesPS(), Size$) ; поиск делает элемент текущим, поэтому сразу добавляем в него элемент списка
	If AddElement(FilesPS()\Path())
    FilesPS()\Path() = tmp$
	EndIf
Else ; иначе если не существует
	If AddMapElement(FilesPS(), Size$, #PB_Map_NoElementCheck) ; добавляем элемент карты, потом элемент списка
    If AddElement(FilesPS()\Path())
    	FilesPS()\Path() = tmp$
    EndIf
	EndIf
EndIf

пока сделал для размеров, надо из них (совпавших по размеру) ещё делать для хеш-сумм.

В общем если есть другие идеи, смысл в том, что мне надо перебрать все файлы, их размеры и если размеры файлов одинаковые, то для этого элемента списка сделать подсписок файлов, то есть это говорит что они подозреваемые на одинаковость содержимого. И потом каждый подсписок получить хеш-суммы и перебрать по совпадению хеш-сумм, то есть создать список хеш-сумм с 2-мя и более файлами, в подсписке которых будут перечисляться пути имеющие эту хеш-суммы. Ну и потом добавлять это в ListIconGadget.

От сторонних модулей ListIcon я пока отказался, так как неизвестно как они будут работать при 1000-100000 файлов по памяти, утечкам и т.д.

Ещё надо встроить, чтобы добавляемое не было подэлементом уже содержимого, т.е. файл/папка внутри уже добавленной папки.
Надо поискать есть ли в Linux возможность создание подгруппы (в Windows есть), иначе придётся делать пустышку хоть и с чекбоксом, но в виде разделителя данных.

Отредактировано AZJIO (07.08.2021 09:42:51)

0

5

AZJIO написал(а):

каждый подсписок получить хеш-суммы и перебрать по совпадению хеш-сумм, то есть создать список хеш-сумм с 2-мя и более файлами, в подсписке которых будут перечисляться пути имеющие эту хеш-суммы.

Я бы сделал так

Код:
Structure SubFiles
  Path.s
  Hash.s
EndStructure

Structure Files
  Size.q
  List SubFiles.SubFiles()
EndStructure

NewList Files.Files()

0

6

Пётр
Hash.s мне нужен как ключ, чтобы сделать проверку уникальности, то есть когда я добавляю ключ в карту, он проверяет есть ли в нём этот элемент, иначе надо вручную делать поиск, на каждый шаг проверить есть ли он в списке. Думаю Карта в принципе делает тоже самое, но из-за того что надо переводить в строку и отсутствует сортировка это даёт мне отрицательный результат. Я бы мог получить в список структур и сортировать по размеру, а потом проверял бы кучность одноразмерных файлов. Ну то есть есть ещё смысл поэкспериментировать другими способами.
Я уже выложил скомпилированный вариант, то есть всё работает пока в упрощённом виде. Нет уровней запрета удаления и нет поддержки CSV. А просто накидать файлов и папок и получить список, в которых выделены дубликаты кроме 1-го. То есть работает, в том числе и удаление отмеченных и выдача ошибок (забыл снимать галки с успешно удалённых). Конт.меню есть чтобы открыть файл или расположение.

0

7

Что значит переводить в строку?у вас же наверняка не только текстовые файлы а в них и типа нуливые байты могут быть как признак конец строки?

0

8

Сравнил скорость карты и списка без отладчика. Причём, если я в список добавляю ещё одно поле структуры - хеш, то список ещё замедляется на 200 мс. Что пришлось тестировать? В карту добавляется размер Size$ = Str(FileSize(tmp$)), то есть ключ надо чтобы был как строка, в то время как в список я добавляю размер в типе число Size.q, то есть число 24789 в Size.q займёт 8 байтов, а в строке ключа Size$ 5 букв - 10 байтов, хотя если UTF8, то 5 байтов. То есть поиск сравнения размеров алгоритмически одинаков, что в карте что в списке, разница, что в карте это сделано внутренним алгоритмом, а в списке написаны циклом, но сути не меняет, т.е. сравнить с неким числом перебирая элементы списка/карты. В итоге карта отработала быстрее. Причём замена Size.q на Size.l, то есть 4 байта на число (2 Гб максимальный файл) не увеличили скорость.
4696 - список
845 - карта

Список

Код:
EnableExplicit

Structure Files
  Size.q
  List Path.s()
EndStructure

NewList FilesPS.Files()

Procedure FileSearch(List FilesPS.Files(), sPath.s, Mask$ = "*", depth=130, level = 0)

	Protected sName.s, c = 0, LenSPath, tmp$, Size.q
	Protected Dim aExaDir(depth)
	Protected Dim aSePath.s(depth)
	Protected fNotFind
	
	If  Right(sPath, 1) <> #PS$
    sPath + #PS$
	EndIf
	LenSPath = Len(sPath)

	aSePath(c) = sPath
	aExaDir(c) = ExamineDirectory(#PB_Any, sPath, Mask$)
	If Not aExaDir(c)
    ProcedureReturn
	EndIf

	Repeat
    While NextDirectoryEntry(aExaDir(c))
    	sName=DirectoryEntryName(aExaDir(c))
    	If sName = "." Or sName = ".."
        Continue
    	EndIf
    	If DirectoryEntryType(aExaDir(c)) = #PB_DirectoryEntry_Directory
        If c >= depth
        	Continue
        EndIf
        sPath = aSePath(c)
        c + 1
        aSePath(c) = sPath + sName + #PS$
        aExaDir(c) = ExamineDirectory(#PB_Any, aSePath(c), Mask$)
        If Not aExaDir(c)
        	c - 1
        EndIf
    	Else
        tmp$ = aSePath(c) + sName
        Size = FileSize(tmp$)
        
        
        fNotFind = 1
        ForEach FilesPS()
            If FilesPS()\Size = Size
;             If AddElement(FilesPS())
            	If AddElement(FilesPS()\Path())
                FilesPS()\Path() = tmp$
            	EndIf
            	fNotFind = 0
            	Break
;             EndIf
        	EndIf
        Next
        If fNotFind
        	If AddElement(FilesPS())
            FilesPS()\Size = Size
            If AddElement(FilesPS()\Path())
            	FilesPS()\Path() = tmp$
            EndIf
        	EndIf
        EndIf
	
    	EndIf
    Wend
    FinishDirectory(aExaDir(c))
    c - 1
	Until c < 0
EndProcedure


Define StartTime.q, time$
StartTime = ElapsedMilliseconds()     ; Получить метку текущего времени
FileSearch(FilesPS(), "/home/user")
time$ = Str(ElapsedMilliseconds() - StartTime) ; вывод без отладчика
MessageRequester("", time$)
Debug time$ + #CRLF$ ; Получить разницу между текущем временем и предыдущей меткой времени
Debug "Размер списка: " + Str(ListSize(FilesPS())) + #CRLF$

ForEach FilesPS()
	Debug #CRLF$ + Str(FilesPS()\Size)
	ForEach FilesPS()\Path()
    Debug FilesPS()\Path()
	Next
Next

Карта

Код:
EnableExplicit

Structure Files
	List Path.s()
EndStructure

NewMap FilesPS.Files()

Procedure FileSearch(Map FilesPS.Files(), sPath.s, Mask$ = "*", depth=130, level = 0)

	Protected sName.s, c = 0, LenSPath, tmp$, Size$
	Protected Dim aExaDir(depth)
	Protected Dim aSePath.s(depth)

	If  Right(sPath, 1) <> #PS$
    sPath + #PS$
	EndIf
	LenSPath = Len(sPath)

	aSePath(c) = sPath
	aExaDir(c) = ExamineDirectory(#PB_Any, sPath, Mask$)
	If Not aExaDir(c)
    ProcedureReturn
	EndIf

	Repeat
    While NextDirectoryEntry(aExaDir(c))
    	sName=DirectoryEntryName(aExaDir(c))
    	If sName = "." Or sName = ".."
        Continue
    	EndIf
    	If DirectoryEntryType(aExaDir(c)) = #PB_DirectoryEntry_Directory
        If c >= depth
        	Continue
        EndIf
        sPath = aSePath(c)
        c + 1
        aSePath(c) = sPath + sName + #PS$
        aExaDir(c) = ExamineDirectory(#PB_Any, aSePath(c), Mask$)
        If Not aExaDir(c)
        	c - 1
        EndIf
    	Else
        tmp$ = aSePath(c) + sName
        Size$ = Str(FileSize(tmp$))
        If FindMapElement(FilesPS(), Size$) ; поиск делает элемент текущим, поэтому сразу добавляем в него элемент списка
        	If AddElement(FilesPS()\Path())
            FilesPS()\Path() = tmp$
        	EndIf
        Else ; иначе если не существует
        	If AddMapElement(FilesPS(), Size$, #PB_Map_NoElementCheck) ; добавляем элемент карты, потом элемент списка
            If AddElement(FilesPS()\Path())
            	FilesPS()\Path() = tmp$
            EndIf
        	EndIf
        EndIf
    	EndIf
    Wend
    FinishDirectory(aExaDir(c))
    c - 1
	Until c < 0
EndProcedure


Define StartTime.q, time$
StartTime = ElapsedMilliseconds()     ; Получить метку текущего времени
FileSearch(FilesPS(), "/home/user")
time$ = Str(ElapsedMilliseconds() - StartTime) ; вывод без отладчика
MessageRequester("", time$)
Debug time$ + #CRLF$ ; Получить разницу между текущем временем и предыдущей меткой времени
Debug "Размер карты: " + Str(MapSize(FilesPS())) + #CRLF$

ForEach FilesPS()
	Debug #CRLF$ + MapKey(FilesPS())
	ForEach FilesPS()\Path()
    Debug FilesPS()\Path()
	Next
Next

0

9

Из-за доступа к диску результат будет искажен. ОС кеширует доступ к файлам и первое сканирование будет идти дольше чем второе.

0

10

Пётр

Из-за доступа к диску результат будет искажен. ОС кеширует доступ к файлам и первое сканирование будет идти дольше чем второе.

Это я в курсе, поэтому я всегда начинаю тестировать со второго раза, когда файловые адреса текущего поиска заполняют память диска и когда объём данных не слишком велик, чтобы не поместиться в кеше. То есть я тестирую чистую скорость алгоритма. То есть при 5-краном запуске у меня разброс плюс/минус 1-2%, то есть 4700-4750, а пятикратный запуск 840-856, ну это не может быть совпадением такая кучность при разных алгоритмах.

0

11

Протестировал на папке Windows.
List - 15 секунд.
Map - 10 секунд.

0

12

Удалось обработать быстро списком, преимущество, что теперь есть сортировка по размеру, а в карте не было.

Смысл в следующем: после сортировки одинаковые размеры идут друг за другом. Поэтому можно проверить если старый элемент совпадает новому в цикле, то идут перечисление одинаковых размеров и можно сформировать список: позиция с которой началось повторение размеров и количество этих совпадений., то есть структура (Pos, Count), далее второй проход по этому списку уже получает группы путей и можно уже формирование MD5 тем же методом. И самое главное скорость 725 мс, то есть быстрее карты.

До этого я не мог понять как мне получить предыдущий элемент, был бы массив я бы просто сравнивал arr(i) и arr(i-1), но опять проблема что массив не может содержать разные типы размер и путь. Подумал может сделать два массива с разными типами, но тогда не отсортировать. Про массив структур я забыл, поэтому сделал просто кеширование предыдущего элемента списка (поле размер)

Код:
EnableExplicit

Structure Files
	Size.q
	Path.s
EndStructure

Structure DataLst
	Pos.i
	Count.i
EndStructure

NewList FilesPS.Files()
NewList DataLst.DataLst()

Procedure FileSearch(List FilesPS.Files(), sPath.s, Mask$ = "*", depth=130, level = 0)
	
	Protected sName.s, c = 0, LenSPath
	Protected Dim aExaDir(depth)
	Protected Dim aSePath.s(depth)
	
	If  Right(sPath, 1) <> #PS$
    sPath + #PS$
	EndIf
	LenSPath = Len(sPath)
	
	aSePath(c) = sPath
	aExaDir(c) = ExamineDirectory(#PB_Any, sPath, Mask$)
	If Not aExaDir(c)
    ProcedureReturn
	EndIf
	
	Repeat
    While NextDirectoryEntry(aExaDir(c))
    	sName=DirectoryEntryName(aExaDir(c))
    	If sName = "." Or sName = ".."
        Continue
    	EndIf
    	If DirectoryEntryType(aExaDir(c)) = #PB_DirectoryEntry_Directory
        If c >= depth
        	Continue
        EndIf
        sPath = aSePath(c)
        c + 1
        aSePath(c) = sPath + sName + #PS$
        aExaDir(c) = ExamineDirectory(#PB_Any, aSePath(c), Mask$)
        If Not aExaDir(c)
        	c - 1
        EndIf
    	Else
;         tmp$ = aSePath(c) + sName
        If AddElement(FilesPS())
        	FilesPS()\Path = aSePath(c) + sName
        	FilesPS()\Size = FileSize(FilesPS()\Path)
        EndIf
    	EndIf
    Wend
    FinishDirectory(aExaDir(c))
    c - 1
	Until c < 0
EndProcedure


Define StartTime.q, time$, i, tmp
StartTime = ElapsedMilliseconds()     ; Получить метку текущего времени
FileSearch(FilesPS(), "/home/user")
; SortList(FilesPS(), #PB_Sort_Ascending)
SortStructuredList(FilesPS(), #PB_Sort_Ascending , OffsetOf(Files\Size) , TypeOf(Files\Size))
Debug time$ + #CRLF$ ; Получить разницу между текущем временем и предыдущей меткой времени
Debug "Размер списка: " + Str(ListSize(FilesPS())) + #CRLF$

Define Count, open
i = 0
open = 0
tmp = -3
ForEach FilesPS()
	If tmp = FilesPS()\Size
    open = 1
    Count + 1
    If Count = 1
    	If AddElement(DataLst())
        DataLst()\Pos = i
    	EndIf
    EndIf
	Else
    If open = 1
    	DataLst()\Count = Count + 1
    EndIf
    open = 0
    Count = 0
	EndIf
	tmp = FilesPS()\Size
	i + 1
Next
If open = 1
	DataLst()\Count = Count + 1
EndIf

Define x, Point, Point2
i = 1
If ListSize(DataLst()) = 0 Or ListSize(FilesPS()) = 0
	End
EndIf
ResetList(DataLst())
ResetList(FilesPS())
NextElement(DataLst())
NextElement(FilesPS())
Repeat
; 	Debug "i = " + Str(i) + " Pos = " + Str(DataLst()\Pos)
	If i = DataLst()\Pos
    For x = 1 To DataLst()\Count
    	Debug Str(i) + " " + Str(FilesPS()\Size) + " " + FilesPS()\Path
    	If Not NextElement(FilesPS())
        Break
    	EndIf
    	i + 1
    Next
    Debug "_________________________"
    If Not NextElement(DataLst())
    	Break
    EndIf
	Else
    i + 1
    If Not NextElement(FilesPS())
    	Break
    EndIf
	EndIf
; 	Debug Str(i) + " " + Str(FilesPS()\Size) + " " + FilesPS()\Path
ForEver
Debug "_____КОНЕЦ________"
Debug "_________________________"
Debug "_________________________"
time$ = Str(ElapsedMilliseconds() - StartTime)
MessageRequester("", time$) ; вывод без отладчика

i = 0
ForEach FilesPS()
	i + 1
	Debug Str(i) + " " + Str(FilesPS()\Size) + " " + FilesPS()\Path
Next

ForEach DataLst()
    Debug Str(DataLst()\Pos) + " " + Str(DataLst()\Count)
Next

0

13

AZJIO написал(а):

До этого я не мог понять как мне получить предыдущий элемент

PreviousElement.

0

14

Пётр
Это понятно, но тогда на каждом шаге цикла надо делать один раз назад и 2 раза вперёд, то есть оперировать указателями, которые сами по себе число int, так не проще ли на каждом шаге кешировать этот int в переменную, число шагов будет меньше. То есть сама конструкция цикла становится сложнее, какие то ResetList, NextElement, то есть пройтись по списку. В данном случае с массивом я бы просто сразу обсчитывал DataLst() перешёл допустим к элементу 800 и снял бы данные с 800 по 805, а со списком мне нужно именно 800 раз делать NextElement, захватить данные и потом сдвигать текущий элемент дальше, то есть именно пройтись по всему списку, даже если в миллионе элементов их будет всего 2, а в массиве я бы сразу взял конкретно эти 2 элемента. Только сейчас и сам понимаю что нужно переделывать на массив.
Ну и для списка была мысль, что надо не позицию элемента сохранять, а указатель на элемент и с него двигать дальше, тогда да, будет аналогично массиву, хотя читать этот код сложнее. Ну или использовать SelectElement().

Упростил решение

Код:
ForEach DataLst()
	SelectElement(FilesPS(), DataLst()\Pos - 1)
	For x = 1 To DataLst()\Count
    Debug Str(DataLst()\Pos - 1) + " " + Str(FilesPS()\Size) + " " + FilesPS()\Path
    NextElement(FilesPS())
	Next
	Debug "_________________________"
Next
Debug "_____КОНЕЦ________"
Debug "_________________________"
Debug "_________________________"

Отредактировано AZJIO (10.08.2021 15:37:01)

0

15

Если кто желает попробовать новый исходник, то добавил в архив основным (по ссылке в шапке), но скомпилированные оставлены пока со старым исходником, так как ещё надо потестить может оптимизировать в функции участки кода. Что мне понравилось в отличии от предыдущего исходника с картами. Во первых и в главных, когда идет перебор элементов по размеру и добавление в карту очередной хеш-суммы мне показалось неправильно сверять его с кучей существующих, так как если хеш снят с группы из 5 файлов в 10 байт, то он явно не будет совпадать с другим хешем с размером файла 20 байт. То есть мне надо проверять не по миллионной базе хешей, а всего по 5-элементной базе, только из этой кучи возможно совпадение хешей. А это заметное торможение. Теперь же делается сортировка и поиск условно говоря по 5-ти элементному списку и если есть результат, то добавляется в GUI, если нет то список очищается и собирается новый из новой группы, естественно поиск среди 2-5 элементов в геометрической прогрессии меньше в случае если обсчитывается много файлов.

На линукс пока есть проблемы, ещё ранее предыдущая версия захватывала элементы с размером -1 (не существует) или -2 (папка), подозреваю что проблема файл-ссылок. То есть обсчёт "/home/user" не могу дождаться завершения, и датчики работы диска и проца не показывают активность. А на средних выборочных папках (Загрузки - 26 Гб, 3700 файлов) отрабатывает мгновенно.

Тест нового исходника на больших папках выдаёт проблему, в то время как старая на Map работает.
Ещё тест, показал, что не работает, когда функция отправлена в другой поток, чтобы была возможность отправлять сообщения о процессе в строку состояния. В то время как вариант с Map тоже иногда сбоит, если в другом потоке.

Отредактировано AZJIO (10.08.2021 23:35:59)

0

16

AZJIO написал(а):

а со списком мне нужно именно 800 раз делать NextElement

а про это забыл?
SelectElement()

но массив наверно будет быстрей листа

0

17

newJS написал(а):

а про это забыл?
SelectElement()

Забыл, но на несколько минут (позавчера), точнее просто посмотрел функции List и увидел возможный вариант. Уже полностью сделал.
Вчера сделал установщики для Linux, потому что многие не любители проверять, они понимают только как пакет, который кликнув установился и появился в меню. А для Windows сделал отдельный исходник с отправкой поиска дубликатов в отдельный поток, так как в Windows нет проблемы с потоком. Жалко что в Linux есть, потому что я не могу в промежутках отправить текст в строку состояния, она будет обновлена как только процесс по событию поиска закончится. И самое интересное что в AutoIt3 если я отправляю в строку состояния текущее выполнение части алгоритма это не блокирует окно, не понимаю, почему в PureBasic блокируется окно как зависшее, это можно проверить на моей аналогичной программе на AutoIt3, учитывая что он имеет потоков, всё в одном.

0

18

AZJIO написал(а):

в Windows нет проблемы с потоком. Жалко что в Linux есть

Если не работать с GUI в потоках, проблем обычно нет.

0

19

А есле сделать что то типа компонента со своим обработчиком и в этот обработчик слать типа асинхронного сообщения?

0

20

AZJIO написал(а):

если размеры файлов одинаковые, то для этого элемента списка сделать подсписок файлов, то есть это говорит что они подозреваемые на одинаковость содержимого.

А если дополнительно сверять тип файла? Или подсчитать хэш быстрее?

0

21

Sergeihik написал(а):

в этот обработчик слать типа асинхронного сообщения?

Для этого есть PostEvent.

0

22

Попробуйте применить https://ru.wikipedia.org/wiki/Фильтр_Блума

На английском форуме
https://www.purebasic.fr/english/viewto … 84#p573484

0

23

Пётр написал(а):

Для этого есть PostEvent.

Postevent ,название как бы намекает что это сообщение после завершения цикла обработчика?
А не получение события во время работы обработки.

0

24

https://www.purebasic.com/documentation … event.html

0


Вы здесь » PureBasic - форум » Программирование на PureBasic » Поиск дубликатов файлов