Что нового

Отображение структуры файлов, взятых из текстового документа

JSman

Знающий
Сообщения
22
Репутация
5
Приветствую! В настоящий момент подвернулась непростая для меня задача.

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

Как лучше это сделать? Текстовый файл может весить 16 мб.
Может сделать виртуальный фтп, который будет отдавать программам (проводнику, тотал командеру) списки?
 
Автор
J

JSman

Знающий
Сообщения
22
Репутация
5
Не понял вопроса. Мне нужно получить наглядное представление структуры каталогов с файлами.
 

AZJIO

Меценат
Меценат
Сообщения
2,903
Репутация
1,200
JSman
Список покажите... Формат списка.
 
Автор
J

JSman

Знающий
Сообщения
22
Репутация
5
Пишу с телефона. Формат файла - это результат выполнения команды dir из cmd. Отсортированный текстовый файл, каждая строка которого полный абсолютный путь файла.
 

AZJIO

Меценат
Меценат
Сообщения
2,903
Репутация
1,200
JSman
Вариант - папки первыми в списке, обновлено 8 раз. Добавлен счётчик времени открытия.
Список можно создать утилитой Create_list_files (кстати обновил, добавив просмотрщик списка файлов).

Код:
#include <GUIConstantsEx.au3>
#include <GuiTreeView.au3>
#include <WindowsConstants.au3>
#include <Array.au3>

Opt("GUIOnEventMode", 1)

; входные параметы, список полный путей, можно и относительных, обязательно сортированный
; $ListFile = @ScriptDir & '\1.txt'
$ListFile = FileOpenDialog('Открыть список', @ScriptDir, 'Все (*.*)', 3)
If @error Then Exit

Global $sTVParent, $aLastPath[125][3] = [[0]] ; (массив = "папка | дескриптор пункта | счётчик папок")

; En
$LngTitle = 'View a list of files'
$sTVParent = 'List'
$Lng1 = 'item,'
$Lng2 = 'sec'

; Ru
; если русская локализация, то русский язык
If @OSLang = 0419 Then
	$LngTitle = 'Просмотр списка файлов'
	$sTVParent = 'Список'
	$Lng1 = 'пунктов, за'
	$Lng2 = 'сек'
EndIf

$hGui = GUICreate($LngTitle, 540, 560, -1, -1, $WS_OVERLAPPEDWINDOW)
GUISetOnEvent($GUI_EVENT_CLOSE, "_Exit")

$TreeView = GUICtrlCreateTreeView(0, 0, 540, 560, -1, $WS_EX_CLIENTEDGE)
GUICtrlSetResizing(-1, 2 + 4 + 32 + 64)
$hTreeView = GUICtrlGetHandle($TreeView)

$hImage = _GUIImageList_Create(16, 16, 5, 1) ; Создаём список иконок
_GUIImageList_AddIcon($hImage, @SystemDir & '\shell32.dll', -4)
_GUIImageList_AddIcon($hImage, @SystemDir & '\shell32.dll', -5)
_GUIImageList_AddIcon($hImage, @SystemDir & '\shell32.dll', 0)
_GUICtrlTreeView_SetNormalImageList($hTreeView, $hImage)

; Вытаскиваем иконки расширений из реестра и добавляем их в список изображений (скорость импорта 0,5 сек)
$E = ''
$i = 1
While 1
	$i += 1
	$sExt = RegEnumKey("HKCR", $i)
	If @error Or StringLeft($sExt, 1) <> '.' Then ExitLoop
	$ico1 = _FileDefaultIcon($sExt)
	If Not @error Then
		Switch UBound($ico1)
			Case 2
				If StringInStr(';.exe;.scr;.ico;.ani;.cur;', ';' & $sExt & ';') Then
					ContinueLoop
				Else
					_GUIImageList_AddIcon($hImage, $ico1[1], 0)
					If @error Then ContinueLoop
				EndIf
			Case 3
				_GUIImageList_AddIcon($hImage, $ico1[1], $ico1[2])
				If @error Then ContinueLoop
		EndSwitch
		$E &= '|' & $sExt
	EndIf
WEnd
$E = StringTrimLeft($E, 1)
$aE = StringSplit($E, '|') ; Создаём массив расширений, позиция которых совпадает с позицией иконок

$aLastPath[0][1] = _GUICtrlTreeView_Add($hTreeView, 0, $sTVParent, 0, 0) ; добавляем корневой пункт
$aList = StringSplit(StringReplace(FileRead($ListFile), ':', ''), @CRLF, 1) ; читаем список в массив
; _ArraySort($aList, 0, 1) ; сортируем список, обязательно если не сортированный
; _ArrayDisplay($aList, 'Array') ; для просмтора валидности массива
; Exit

$sep = Opt("GUIDataSeparatorChar", "\") ; устанавливаем разделитель для простоты работы функций TreeView
$timer = TimerInit() ; вычисляем скорость построения списка
; _GUICtrlTreeView_BeginUpdate($hTreeView)
For $i = 1 To $aList[0]
	_AddFile($aList[$i]) ; функция добавления массива путей в TreeView
Next
; _GUICtrlTreeView_EndUpdate($hTreeView)
WinSetTitle($hGui, '', $LngTitle & ' (' & $aList[0] & ' ' & $Lng1 & ' ' & Round(TimerDiff($timer) / 1000, 2) & ' ' & $Lng2 & ')')
Opt("GUIDataSeparatorChar", $sep) ; восстанавливаем разделитель по умолчанию
ControlTreeView($hGUI, '', $hTreeView, 'Expand', $sTVParent) ; раскрыть корневой
GUISetState()

While 1
	Sleep(100000)
WEnd

Func _Exit()
	Exit
EndFunc

Func _AddFile($sPath)
	Local $hItem, $i, $ind = 0, $j, $tmp, $tmp1
	$tmp = StringSplit($sPath, '\') ; делим путь
	For $i = 1 To $tmp[0]
		If $tmp[$i] <> $aLastPath[$i][0] Then ; ищем последнее различие в пути, то есть уровеь вложения, на котором новый пункт не совпадает с предыдущим
			For $j = $i To $tmp[0]
				$aLastPath[$j][0] = $tmp[$j] ; кэшируем новый путь в двумерный массив
				$aLastPath[$j + 1][2] = 0 ; сбрасываем счётчик папок в 0
			Next
			ExitLoop
		EndIf
	Next
	For $i = $i To $tmp[0] ; отсчёт цикла начинается с индекса на котором было несовпадение, а значит они ещё не созданы
		If $i = $tmp[0] Then ; если последний элемент пути (то есть файл), то... (условие для определения иконки пункту)
			$tmp1 = StringRegExpReplace($tmp[$i], '.*(\.\S+)', '\1') ; ищем расширение
			$ind = _ArraySearch($aE, $tmp1) + 2 ; ищем расширение в масиве, чтобы определить индекс
			If @error Then $ind = 2 ; если нет расширения
			$aLastPath[$i][1] = _GUICtrlTreeView_AddChild($hTreeView, $aLastPath[$i - 1][1], $tmp[$i], $ind, $ind) ; добавляем пункт (файл) в конец списка
		Else
			; если папка
			If $aLastPath[$i][2] Then
				$hItem = _GUICtrlTreeView_GetItemByIndex($hTreeView, $aLastPath[$i - 1][1], $aLastPath[$i][2] - 1) ; десриптор последнего пункта папки
				$aLastPath[$i][1] = _GUICtrlTreeView_InsertItem($hTreeView, $tmp[$i], $aLastPath[$i - 1][1], $hItem, $ind, $ind)
			Else ; иначе если индекс 0, добавляем первым в список, так как Insert не может добавить в самое начало
				$aLastPath[$i][1] = _GUICtrlTreeView_AddChildFirst($hTreeView, $aLastPath[$i - 1][1], $tmp[$i], $ind, $ind) ; добавляем пункт
			EndIf
			$aLastPath[$i][2] += 1 ; увеличиваем счётчик папок на 1 для вставки папок оп индексу
		EndIf
	Next
EndFunc

Func _FileDefaultIcon($sExt)
	If $sExt = '' Or StringInStr($sExt, ':') Then Return SetError(1)

	Local $aCall = DllCall("shlwapi.dll", "int", "AssocQueryStringW", _
			"dword", 0x00000040, _ ;$ASSOCF_VERIFY
			"dword", 15, _ ;$ASSOCSTR_DEFAULTICON
			"wstr", $sExt, _
			"ptr", 0, _
			"wstr", "", _
			"dword*", 65536)

	If @error Then Return SetError(1, 0, "")

	If Not $aCall[0] Then
		$sExt = StringReplace($aCall[5], '"', '')
		$sExt = StringSplit($sExt, ',')
		Opt('ExpandEnvStrings', 1)
		$sExt[1] = $sExt[1]
		Opt('ExpandEnvStrings', 0)
		Return SetError(0, 0, $sExt)
	ElseIf $aCall[0] = 0x80070002 Then
		Return SetError(1, 0, "{unknown}")
	ElseIf $aCall[0] = 0x80004005 Then
		Return SetError(1, 0, "{fail}")
	Else
		Return SetError(2, $aCall[0], "")
	EndIf
EndFunc
 
Автор
J

JSman

Знающий
Сообщения
22
Репутация
5
AZJIO, очень интересно получилось! Буду заниматься оптимизацией Вашего скрипта. Так, у меня получился результат 115159 пунктов за 148,46 секунд. Думаю отслеживать открытые узлы и подгружать дочерние узлы 2 уровня динамически.
 

AZJIO

Меценат
Меценат
Сообщения
2,903
Репутация
1,200
JSman [?]
подгружать дочерние
Попробовал для теста закомментировать функции TreeView и список созделся за 0.31 взамен 0.55, то есть скорость не сильно возрасла почти в 2 раза. Кроме того придётся делать именно массив, так как подгружать со списка возможно не лучшая идея, каждый раз просамтривать целый список, чтобы догрузить 3 файла в очередной узел, кто знает сколько требуется для поиска в таком списке, может постоянные задержки будут.
Если подготовить массив, то тоже на создание его потребуется как раз та разница что была в выйгрыше. Просто я не вижу с чего можно выйграть по времени, это же не работа с файлами, которые уже есть и получить к ним доступ быстрее. Есть ещё вариант, если вам нужен частый доступ к файлам списка, то можно подготовить особый формат списка, чтобы в последующем был более быстрый доступ.
 
Автор
J

JSman

Знающий
Сообщения
22
Репутация
5
AZJIO, что касается изменения формата списка, то тут полностью согласен. Можно даже в базу данных конвертировать.
 

AZJIO

Меценат
Меценат
Сообщения
2,903
Репутация
1,200
JSman
База данных имеет табличный вид, к тому же я ещё не видел чтобы скорость добавления в базу была приемлемой, по крайней мере в примерах на AutoIt. Скорость создания базы в памяти быстро, но нет возможности её скопировать на диск.

В плане форматированного списка, пытаю пару идей, но глухо:
Код:
<root<C<папка1<папка2<папка3<файл1|Описание.txt>|папка4>>>>>
<0>C:<1>папка1<2>папка2<3>папка3<4>файл1<4>Описание.txt<3>папка4<4>файл4<4>Описание.txt<3>папка5<4>файл5<3>файл6

C:\папка1\папка2\папка3\файл1
C:\папка1\папка2\папка3\Описание.txt
C:\папка1\папка2\папка4\файл4
C:\папка1\папка2\папка4\Описание.txt
C:\папка1\папка2\папка5\файл2
C:\папка1\папка2\папка5\файл5
C:\папка1\папка2\файл6
C:\папка1\папка2\файл7
Две строки как пример попытки разложить файлы в виде дерева. Либо метка перед элементом, чтобы при клике мы раскладываем кликнутый пункт на элементы, потом ищем последовательно элемент согласно уровню вложения и имени, то есть ищем метку.

Формулировка задачи: Чтобы найти содержимое папки "папка2", определяем, что папка третьего уровня по уровню в дереве и ищем "<3>папка2<", естественно может быть найдено только одно значение., так как папки на одном уровне не могут повторятся. Далее ищем метку <3>, которая говорит о том, что это уже следующая соседняя папка. Далее на этом участке ищем все элементы с меткой "<4>(.+*)<", вот они искомые элементы указанной папки.

И второй вариант, который в первой строке мне кажется сложнее.
Формулировка задачи: для поиска содержимого папки "папка2" нужно найти эту папку и чтобы счётчик открытых тегов соответствовал уровню вложения этой папки. Далее от первого открытого тега искать закрытый тег, при котором счётчик открытых тегов был равен 0. Далее на этом участке удалить содержимое тегов за один проход, используя счётчик открытия тегов и оставшееся будет список текущих элементов с разделителем "|".

Задачка для конкурса, голову сломаешь...
 
Автор
J

JSman

Знающий
Сообщения
22
Репутация
5
AZJIO, в общем долго я думал, пока пришел к следующему:
можно использовать так называемые "интервальные деревья", а именно:
создаем базу данных (например, accdb), в которой будут две таблицы.
Первая таблица содержит колонки идентификатор(индекс), название папки/файла, а также типа объекта (папка/файл).
Вторая таблица состоит из колонок "идентификатор", "идентификатор родителя", "идентификатор первого дочернего узла текущего элемента" (так называемая "левая граница"), "идентификатор крайнего потомка с ограничением по второй уровень глубины".

Что касается затрат времени на создание такого файла, то на это можно не обращать внимание: один раз подождем, а во все последующие разы будет "летать".

Как Вам такая мысль?
 

AZJIO

Меценат
Меценат
Сообщения
2,903
Репутация
1,200
JSman
Отсутствует вдохновение, если сделаете оболочку, я помогу сделать парсинг списка, который я предложил. Там ещё есть мелочи "ищем метку <3>" на самом деле "ищем метку <[0-3]>" и конец списка закрыть тегом <0>.

С таблицами SQL делать ничего не буду пока не увижу пример который приемлимо работает
Код:
_SQLite_Exec(-1, "Create Table IF NOT Exists '"&$sNameTable&"' ("&$ID1&" int, '"&$ID2&"' Text, '"&$ID3&"' Text);")
$kod=''
For $i = 1 To 3000
	$kod&="Insert into '" & $sNameTable & "' (" & $ID1 & "," & $ID2 & "," & $ID3 & ") values ('" & $i & "','" & Random(10000, 12000, 1) & "','" & Random(10000, 99999, 1) & "');"
Next
_SQLite_Exec(-1, $kod)

Не дождался создания 3000 элементов, убил процесс через 4 минуты, запустил заново, в базе за это время создалось 2600 элементов. А если файлов 150 тыс. или миллион, на ночь что ли компьютер оставлять? И последующий запрос не гарантирует скорость быстрее моего варианта.
Кроме того базы создаваемые в AutoIt3 не являются древовидными, поэтому я не понял структуру вашего варианта.
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,323
AZJIO [?]
С таблицами SQL делать ничего не буду пока не увижу пример который приемлимо работает
А так?
Код:
#include <Array.au3>
#include <SQLite.au3>
;#include <SQLite.dll.au3>

$s_FileDB = @ScriptDir & '\test.db'
$i_Error = 1
Dim $aTest[150001][3] = [[150000, 2]]

$iStart = TimerInit()
For $i = 1 To $aTest[0][0]
	For $j = 0 To $aTest[0][1]
		$aTest[$i][$j] = Random(1, 1000000, 1)
	Next
Next
ConsoleWrite('Array create: ' & Round(TimerDiff($iStart)) & ' ms' & @LF)
$iStart = TimerInit()
$s_Text = 'BEGIN;DROP TABLE IF EXISTS TEST;CREATE TABLE TEST ([NUM_1] INT, [NUM_2] INT, [NUM_3] INT);'
For $j = 1 To 1
	_SQLite_Startup()
	If @error Then ExitLoop
	$h_DB = _SQLite_Open($s_FileDB)
	If @error Then ExitLoop
	For $i = 1 To $aTest[0][0]
		$s_Text &= 'INSERT INTO TEST VALUES (' & $aTest[$i][0] & ', ' & $aTest[$i][1] & ', ' & $aTest[$i][2] & ');'
	Next
	$s_Text &= 'COMMIT;'
	_SQLite_Exec($h_DB, $s_Text)
	If @error Then ExitLoop
	$i_Error = 0
Next
_SQLite_Close($h_DB)
_SQLite_Shutdown()
ConsoleWrite('Data base create: ' & Round(TimerDiff($iStart)) & ' ms' & @LF)
ConsoleWrite('Error: ' & $i_Error & @LF)
 

AZJIO

Меценат
Меценат
Сообщения
2,903
Репутация
1,200
madmasles
Как бы теперь посмотреть что там, моя оболочка не может открыть вашу тестовую базу.
Достаточно было указать про "BEGIN TRANSACTION;" и "COMMIT;", прочитал сечас про них на официальном сайте SQLite, добавил их в начало и конец командной строки, теперь работает практически мгновенно.
Но база все равно не древовидная.
 
Автор
J

JSman

Знающий
Сообщения
22
Репутация
5
AZJIO сказал(а):
Но база все равно не древовидная.
:smile: Хочу Вас разочаровать: иерархическая модель вполне укладывается в реляционную БД. Постараюсь написать код в ближайшее время. Пока есть возможность только поделиться ссылкой.
 
Автор
J

JSman

Знающий
Сообщения
22
Репутация
5
В общем написал конвертер перевода результата выполнения команды DIR в промежуточный формат БД и из промежуточного формата в БД SQLite.
Осталось дело за малым: извлекать нужную нам информацию:smile:

Первый код написан на JS (среда Windows Script Host). Создаем текстовый файл, сохраняем его с расширением JS и бросаем на скрипт текстовый файл со списком файлов.
Код:
var T = new Date();
var cache = [[0,"<ROOT>"]], Index=0;

var fso = FSO = new ActiveXObject("Scripting.FileSystemObject");

function CreateTextFile(Path)
{
	return fso.OpenTextFile(Path, 2, 1)
}

function TextReader() {}

TextReader.prototype = 
{
	foreach : function (Path, callback)
	{
		var e;
		
		this.File = fso.OpenTextFile(Path, 1, 1);
		this._Stop = false;
		
		while (!this.File.eof && !this._Stop)
		{
			try 
			{
				var re; callback(re=this.File.ReadLine());
			} catch (e) {break;}
		}
		
		this.File.Close();
		this._Stop = false;
		return true;
	}, 
	
	stop : function () {this._Stop = true;}
}

function GetIndex(Path)
{
	for (var i=cache.length-1;i>-1;i--)
	{
		if (cache[i][1]==Path) return cache[i][0];// index 
	}
	return -1; //not found
}

function GetParent(Path)
{
	var _Path=Path.replace(/\\[^\\]+$/,"");
	if (_Path==Path) return 0;
	return GetIndex(_Path);
}

var File1=WScript.Arguments(0).replace(/[^\\]+$/,"")+"ID-Name-Parent.txt";

var Report = CreateTextFile(File1);
var CurrentParentIndex=0, LastParentIndex=0;

function callback(a)
{
	var f = a.split('\\'), parent="", t="", type="1"; // type 1 - is a folder, 0 - is a file
	
	for (var i=0,l=f.length;i<l;i++)
	{
		t+=((i!=0)?"\\":"")+f[i];
		
		if (GetIndex(t)==-1)
		{
			if (i==(l-1)) type="0";
			CurrentParentIndex=GetParent(t);
			
			if (CurrentParentIndex<LastParentIndex) 
			{
				var j=cache.length-1;
				while(CurrentParentIndex<cache[j][0])
				{
					cache.splice(j,1);
					j--;
				}
			}
			Index++;
			if (type=="1") cache.push([Index, t]);
			
			var tmp=t.match(/[^\\]+$/);
			Report.WriteLine([Index, tmp, CurrentParentIndex, type].join("|"));
			LastParentIndex = CurrentParentIndex;
		}
	}
	t="";
}

var TR = new TextReader();
TR.foreach(WScript.Arguments(0), callback);
Report.Close();
WScript.Echo(Math.round((new Date()-T)/1000)+" sec");

Далее, второй код (конвертер в SQLite). Скорость какая-то медленная. Может как-то неправильно сделал?
Код:
#include <Array.au3>
#include <SQLite.au3>
;#include <SQLite.dll.au3>

Dim $h_DB
$s_FileDB = @ScriptDir & '\test.db'
$i_Error = 1

$file = FileOpen("ID-Name-Parent.txt", 0)

If $file = -1 Then
	MsgBox(4096, "Ошибка", "Невозможно открыть файл.")
	Exit
EndIf

Dim $ar

	_SQLite_Startup()
	$h_DB = _SQLite_Open($s_FileDB)

$s_Text = 'BEGIN;DROP TABLE IF EXISTS TREE;CREATE TABLE TREE ([ID] INT, [NAME] Text, [PARENT_ID] INT, [FTYPE] INT);COMMIT;'
_SQLite_Exec($h_DB, $s_Text)
$i =0
$s_Text = "BEGIN;"
While 1
	$line = FileReadLine($file)
	If @error = -1 Then ExitLoop
	
	$i=$i+1
	$ar = StringSplit($line,"|", 1)
    If ($ar[0]<4) Then ContinueLoop
	$s_Text &= 'INSERT INTO TREE VALUES (' & $ar[1] & ', "' & $ar[2] & '", ' & $ar[3] & ', ' & $ar[4] & ');'
   
   If (Mod($i,65536)=0) Then
   $s_Text &= 'COMMIT;'
   	_SQLite_Exec($h_DB, $s_Text)
   
	$s_Text="BEGIN;"
   EndIf

WEnd

   $s_Text &= 'COMMIT;'
   	_SQLite_Exec($h_DB, $s_Text)  
FileClose($file)

_SQLite_Close($h_DB)
_SQLite_Shutdown()



Добавлено:
Сообщение автоматически объединено:

Да, необходимо дополнить. База данных состоит из одной таблицы. Формат следующий: Идентификатор, Имя папки/файла, Идентификатор родителя, Флаг папки или файла.
 

AZJIO

Меценат
Меценат
Сообщения
2,903
Репутация
1,200
JSman
Скорость какая-то медленная
Наверно потому что в цикле нужно присоединять команды в строку, а после формирования строки вне цикла вызвать _SQLite_Exec для импорта.

Первый код написан на JS
Сразу вспомнил, что на JavaScript это уже осуществлено Tigra Tree Menu PRO, вот пример его работы. Формат списка файлов древовидный, именно как я предлагал, только квадратные скобки вместо <>, но [] могут находится в именах файлов.
 
Автор
J

JSman

Знающий
Сообщения
22
Репутация
5
Ну, вот и готово:smile: Программа читает базу данных SQLite "test.db", содержащую сведения о структуре каталогов, и отображает их в TreeView. Как создать такую БД, описано выше.

Код:
#include <GUIConstantsEx.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <TreeViewConstants.au3>
#include <GuiTreeView.au3>
#include <Array.au3>
#include <SQLite.au3>
;#include <SQLite.dll.au3>

If $CmdLine[0] = 0 Then
	Dim $s_FileDB = FileOpenDialog('Открыть список', @ScriptDir, 'База данных (*.db)', 3)
	If @error Then Exit
Else
	$s_FileDB = $CmdLine[1]
EndIf

;Dim $s_FileDB = @ScriptDir & '\test.db'
Global $hTreeView, $fExpanded = 0

Dim $TreeNodeCollection[1000]
Dim $FolderCollection[1000]
Dim $ExpandedCollection[1000]
Dim $CollectionLength = 0

Dim $Loaded[1000]
Dim $LoadedLength = 0
Dim $hGui = GUICreate("Explorer", 540, 560, -1, -1, $WS_OVERLAPPEDWINDOW)

Dim $CountElements = 0

$hTreeView = GUICtrlCreateTreeView(0, 0, 540, 560, BitOR($TVS_DISABLEDRAGDROP, $TVS_CHECKBOXES, $GUI_SS_DEFAULT_TREEVIEW, $TVS_SHOWSELALWAYS, $TVS_HASLINES), $WS_EX_CLIENTEDGE)
Opt("GUIDataSeparatorChar", "\")

$hImage = _GUIImageList_Create(16, 16, 5, 1) ; Создаём список иконок
_GUIImageList_AddIcon($hImage, @SystemDir & '\shell32.dll', -4)
_GUIImageList_AddIcon($hImage, @SystemDir & '\shell32.dll', -5)
_GUIImageList_AddIcon($hImage, @SystemDir & '\shell32.dll', 0)
_GUICtrlTreeView_SetNormalImageList($hTreeView, $hImage)

$E = ''
$i = 1
While 1
	$i += 1
	$sExt = RegEnumKey("HKCR", $i)
	If @error Or StringLeft($sExt, 1) <> '.' Then ExitLoop
	$ico1 = _FileDefaultIcon($sExt)
	If Not @error Then
		Switch UBound($ico1)
			Case 2
				If StringInStr(';.exe;.scr;.ico;.ani;.cur;', ';' & $sExt & ';') Then
					ContinueLoop
				Else
					_GUIImageList_AddIcon($hImage, $ico1[1], 0)
					If @error Then ContinueLoop
				EndIf
			Case 3
				_GUIImageList_AddIcon($hImage, $ico1[1], $ico1[2])
				If @error Then ContinueLoop
		EndSwitch
		$E &= '|' & $sExt
	EndIf
WEnd
$E = StringTrimLeft($E, 1)
$aE = StringSplit($E, '|') ; Создаём массив расширений, позиция которых совпадает с позицией иконок


_SQLite_Startup()
$h_DB = _SQLite_Open($s_FileDB)
GUISetState()
GUIRegisterMsg($WM_NOTIFY, "WM_Notify_Events")
LoadTree(0, 1, True)
;ConsoleWrite("-----------------------" & @LF)

Dim $i1=0
While 1
	$iMsg = GUIGetMsg()
	Select
		Case $iMsg = $GUI_EVENT_CLOSE
			_Exit()
		Case $fExpanded = 1
			; node closed so reset all nodes to current states

			$L = $CollectionLength
			For $i1 = 1 To $L
				$ExpandedCollection[$i1] = _GUICtrlTreeView_GetExpanded($hTreeView, $TreeNodeCollection[$i1])
			Next
			$fExpanded = 0
		Case $fExpanded = 2
			; node opened so check which changed
			$L = $CollectionLength
			
			For $i1 = 1 To $L
			   If _GUICtrlTreeView_GetExpanded($hTreeView, $TreeNodeCollection[$i1]) <> $ExpandedCollection[$i1] Then
					Dim $DB_Index = GetDBIndexByTVHandle($TreeNodeCollection[$i1])
					;ConsoleWrite($i1)
				
					$ExpandedCollection[$i1] = True
					;ConsoleWrite("Expanded!" & $DB_Index & "  $i=" & $i1 & "  $TreeNodeCollection[$i1]=" & $TreeNodeCollection[$i1] &  "  " & TVGetPathByHandle($TreeNodeCollection[$i1]) & @LF)
					

					ReLoad($DB_Index)
					ExitLoop
				EndIf
				
			Next
			$fExpanded = 0
	EndSelect
WEnd

Func _FileDefaultIcon($sExt)
	If $sExt = '' Or StringInStr($sExt, ':') Then Return SetError(1)

	Local $aCall = DllCall("shlwapi.dll", "int", "AssocQueryStringW", _
			"dword", 0x00000040, _ ;$ASSOCF_VERIFY
			"dword", 15, _ ;$ASSOCSTR_DEFAULTICON
			"wstr", $sExt, _
			"ptr", 0, _
			"wstr", "", _
			"dword*", 65536)

	If @error Then Return SetError(1, 0, "")

	If Not $aCall[0] Then
		$sExt = StringReplace($aCall[5], '"', '')
		$sExt = StringSplit($sExt, ',')
		Opt('ExpandEnvStrings', 1)
		$sExt[1] = $sExt[1]
		Opt('ExpandEnvStrings', 0)
		Return SetError(0, 0, $sExt)
	ElseIf $aCall[0] = 0x80070002 Then
		Return SetError(1, 0, "{unknown}")
	ElseIf $aCall[0] = 0x80004005 Then
		Return SetError(1, 0, "{fail}")
	Else
		Return SetError(2, $aCall[0], "")
	EndIf
EndFunc   ;==>_FileDefaultIcon

Func __ArraySearch(ByRef $avArray, $vValue, $iStart = 0, $iEnd = 0)
   If $iEnd=0 Then $iEnd=Ubound($avArray)-1

   Dim $i=0, $found=false
	
   For $i=$iStart To $iEnd
	  
	  If ($avArray[$i]=$vValue) Then 
		 $found=true
		 ;ConsoleWrite($I & @LF)
		 ExitLoop
	  EndIf
   Next
	  
	  If not $Found Then 
		 SetError(1)
		 Return -1
	  EndIf
		 
	  Return $i
	  
EndFunc

Func DBGetIndexByName($name, $parent)
	Dim $hQuery, $aRow
	_SQLite_Query(-1, "SELECT * FROM TREE WHERE PARENT_ID='" & $parent & "' AND NAME='" & StringReplace($name, "'", "''") & "';", $hQuery)
	If _SQLite_FetchData($hQuery, $aRow) = $SQLITE_OK Then Return $aRow[0]
	Return -1
EndFunc   ;==>DBGetIndexByName

Func DBGetIndexPath($path)
	If $path = "" Then Return 0
	Dim $p = 0
	Dim $parts = StringSplit($path, "\", 1)
	For $i = 1 To $parts[0]
		$p = DBGetIndexByName($parts[$i], $p)
	Next
	Return $p
EndFunc   ;==>DBGetIndexPath

Func DBGetFiles($parent)
	Dim $Result[1000], $ar_index = 0, $CurrentLength = 1000
	Dim $hQuery, $aRow
	_SQLite_Query(-1, "SELECT * FROM TREE WHERE PARENT_ID='" & $parent & "' AND FTYPE='0';", $hQuery)
	While _SQLite_FetchData($hQuery, $aRow) = $SQLITE_OK
		$ar_index += 1
		if $ar_index >= $CurrentLength Then
			$CurrentLength += 1000
			ReDim $Result[$CurrentLength]
		EndIf
		$Result[$ar_index] = $aRow[1]
	WEnd
	$Result[0] = $ar_index
	;Redim $Result[$ar_index+1]
	Return $Result
EndFunc   ;==>DBGetFiles

Func DBGetFolders($parent)
	Dim $Result[1000], $ar_index = 0, $CurrentLength = 1000
	Dim $hQuery, $aRow
	_SQLite_Query(-1, "SELECT * FROM TREE WHERE PARENT_ID='" & $parent & "' AND FTYPE='1';", $hQuery)
	While _SQLite_FetchData($hQuery, $aRow) = $SQLITE_OK
		$ar_index += 1
		if $ar_index >= $CurrentLength Then
			$CurrentLength += 1000
			ReDim $Result[$CurrentLength]
		EndIf
		$Result[$ar_index] = $aRow[1]
	WEnd
	$Result[0] = $ar_index
	;Redim $Result[$ar_index+1]
	Return $Result
EndFunc   ;==>DBGetFolders

Func DBGetFoldersIndexes($parent)
	Dim $Result[1000], $ar_index = 0, $CurrentLength = 1000
	Dim $hQuery, $aRow
	_SQLite_Query(-1, "SELECT * FROM TREE WHERE PARENT_ID='" & $parent & "' AND FTYPE='1';", $hQuery)
	While _SQLite_FetchData($hQuery, $aRow) = $SQLITE_OK
		$ar_index += 1
		if $ar_index >= $CurrentLength Then
			$CurrentLength += 1000
			;ReDim $Result[$CurrentLength]
		EndIf
		$Result[$ar_index] = $aRow[0]
	WEnd
	$Result[0] = $ar_index
	;Redim $Result[$ar_index+1]
	Return $Result
EndFunc   ;==>DBGetFoldersIndexes

Func DBGetByIndex($index)
	Dim $hQuery, $aRow
	_SQLite_Query(-1, "SELECT * FROM TREE WHERE ID='" & $index & "' ;", $hQuery)
	If _SQLite_FetchData($hQuery, $aRow) = $SQLITE_OK Then Return $aRow
	Return -1
EndFunc   ;==>DBGetByIndex

Func DBGetParent($index)
	Dim $hQuery, $aRow
	$aRow = DBGetByIndex($index)
	Return DBGetByIndex($aRow[2])
EndFunc   ;==>DBGetParent

Func DBGetPathByIndex($index)
	Dim $hQuery, $part = DBGetByIndex($index), $_index = -2, $path = "", $ar

	If $part[3] = 0 Then
		$path = $part[1]
	EndIf

	While 1
		If $index = 0 Then ExitLoop
		$ar = DBGetByIndex($index)
		$index = $ar[2]

		$path = $ar[1] & "\" & $path

	WEnd

	Return $path
EndFunc   ;==>DBGetPathByIndex

Func TVGetHandleByPath($path)
	Opt("GUIDataSeparatorChar", "\")
	Return _GUICtrlTreeView_FindItemEx($hTreeView, $path, 0)
EndFunc   ;==>TVGetHandleByPath

Func TVGetPathByHandle($handle)
	Return _GUICtrlTreeView_GetTree($hTreeView, $handle)
EndFunc   ;==>TVGetPathByHandle

Func GetDBIndexByTVHandle($index)
	Dim $Result = __ArraySearch($TreeNodeCollection, $index, 0, $CollectionLength)

	If $CollectionLength = 0 Or $Result = -1 Then Return 0
	Return $FolderCollection[$Result]

EndFunc   ;==>GetDBIndexByTVHandle

Func GetTVHandleByDBIndex($DBIndex)
	Dim $Result = __ArraySearch($FolderCollection, $DBIndex, 0, $CollectionLength)
	;ConsoleWrite("GetTVHandleByDBIndex " & GetDBIndexByTVHandle($Result) & "  " & $DBIndex & @LF)
	If $Result <> -1 Then Return $TreeNodeCollection[$Result]
	Return -1

EndFunc   ;==>GetTVHandleByDBIndex

Func LoadDataToTV($ParentNodeHandle = 0) ;ToDo
	Dim $DB_Index = GetDBIndexByTVHandle($ParentNodeHandle)
	Dim $_Folders, $_Files, $Result[1000], $i = 0, $L

	Dim $ar = DBGetFoldersIndexes($DB_Index)
	$_Folders = DBGetFolders($DB_Index)

	Dim $Flag = False

	If $ar[0] > 1 Then
		If GetTVHandleByDBIndex($ar[1]) > -1 Then $Flag = True
	EndIf
	
	$L = $_Folders[0]
	
	If $L >= 1000 Then ReDim $Result[$L]
	For $i = 1 To $L
		If not $Flag Then
			$Result[$i] = AddFolder($_Folders[$i], $ParentNodeHandle, $ar[$i])
		else
			$Result[$i] = GetTVHandleByDBIndex($ar[$i])
		endIf
		
	Next
	
	$Result[0] = $L
	
	$LoadedLength += 1
	$Loaded[$LoadedLength] = $ParentNodeHandle
	$Loaded[0] = $LoadedLength

	
	If $Flag Then
		ConsoleWrite(Round(TimerDiff($timer) / 1000, 2) & @LF)
		Return $Result
	EndIf
	
	$_Files = DBGetFiles($DB_Index)
	$L = $_Files[0] 
	For $i = 1 To $L
		AddFile($_Files[$i], $ParentNodeHandle)
	Next
	
	Return $Result
EndFunc   ;==>LoadDataToTV

Func LoadTree($ParentNodeHandle, $lvl = 1, $force = False)
	If $lvl = -1 Then Return

	If Not $force Then
		If __ArraySearch($Loaded, $ParentNodeHandle, 1, $LoadedLength) <> -1 Then
			Return
		EndIf
	EndIf
    _GUICtrlTreeView_Delete($hTreeView, _GUICtrlTreeView_GetFirstChild($hTreeView, $ParentNodeHandle))
 	Dim $Dirs = LoadDataToTV($ParentNodeHandle)
	If $Dirs[0] = 0 Then Return

	For $i = 1 To $Dirs[0]
		LoadTree($Dirs[$i], $lvl - 1)
	 Next
 

EndFunc   ;==>LoadTree

Func AddFolder($name, $ParentNodeHandle = 0, $DB_Index = -1)
	Local $Result = _GUICtrlTreeView_InsertItem($hTreeView, $name, $ParentNodeHandle)
	;Local $Result = _GUICtrlTreeView_AddChild($hTreeView, $ParentNodeHandle, $name)
	
	$CountElements += 1
	
	$CollectionLength += 1
	
	if $CollectionLength >= Ubound($TreeNodeCollection) Then
		ReDim $TreeNodeCollection[Ubound($TreeNodeCollection) + 1000]
		ReDim $ExpandedCollection[Ubound($TreeNodeCollection) + 1000]
		ReDim $FolderCollection[Ubound($TreeNodeCollection) + 1000]
	EndIf

	$TreeNodeCollection[$CollectionLength] = $Result
	$ExpandedCollection[$CollectionLength] = False
	$FolderCollection[$CollectionLength] = $DB_Index
	;ConsoleWrite ($DB_Index & @LF)

    AddFile("..", $Result)

	Return $Result
EndFunc   ;==>AddFolder

Func AddFile($name, $parent = 0)
	Dim $tmp1 = StringRegExpReplace($name, '.*(\.\S+)', '\1') ; ищем расширение
	Dim $ind = __ArraySearch($aE, $tmp1) + 2 ; ищем расширение в масиве, чтобы определить индекс
	If @error Then $ind = 2 ; если нет расширения
	   $CountElements += 1
					
	_GUICtrlTreeView_InsertItem($hTreeView, $name, $parent, $ind, $ind, $ind)
	;_GUICtrlTreeView_AddChild($hTreeView, $parent, $name, $ind, $ind)
EndFunc   ;==>AddFile

Func ReLoad($DB_Index)
	Dim $timer = TimerInit()
	$CountElements = 0
	
	LoadTree(GetTVHandleByDBIndex($DB_Index), 0)
	
	; Или так
	
	;Dim $ar = DBGetFoldersIndexes($DB_Index)
	;Dim $L = $ar[0]
	;For $j = 1 To $L
;		LoadTree(GetTVHandleByDBIndex($ar[$j]), 0)
;	Next

	ConsoleWrite("Time " & Round(TimerDiff($timer) / 1000, 2) & "  Elements added " & $CountElements & @LF)
EndFunc   ;==>ReLoad

Func WM_Notify_Events($hWndGUI, $iMsgID, $wParam, $lParam)

	#forceref $hWndGUI, $iMsgID
	Local $tagNMHDR = DllStructCreate("int;int;int;int", $lParam)
	If @error Then Return
	Local $iEvent = DllStructGetData($tagNMHDR, 3)
	Select
		Case $wParam = $hTreeView
			Switch $iEvent
				Case $TVN_ITEMEXPANDEDW, $TVN_ITEMEXPANDEDA
					$fExpanded = DllStructGetData($tagNMHDR, 4) ; 1 = node closed, 2 = node expanded
			EndSwitch
	EndSelect
	$tagNMHDR = 0
	Return $GUI_RUNDEFMSG

EndFunc   ;==>WM_Notify_Events

Func _Exit()
	_SQLite_Close($h_DB)
	_SQLite_Shutdown()
	Exit
EndFunc   ;==>_Exit
 
Верх