Autorun

Обсуждение только плагинов и утилит для Total Commander.

Модераторы: Avada, CaptainFlint, Nik, d-view, Моторокер

Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

AkulaBig писал(а):куда правильно вставить это условие, чтобы все заработало.
Тут чекушку открывать надо, а сейчас за рулем, не могу.

Но если навскидку, то функция ET запускается 60 раз. Процесс exiftool должен запуститься один раз, и желательно за пределами этой функции. FieldIndex = 1 должен сделать запрос к запущенному процессу и ждать ответа. Остальные 59 вызовов будут просто разбирать полученный выхлоп на строки.

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

Вы можете сами протестировать и убедиться в этом. Тестовая функция с тремя режимами как раз и создавалась для этого:

Win+[ запуск exiftool для каждого файла.
Win+] держит процесс открытым и взаимодействует с ним через файл аргументов.
Win+\ взаимодействует с процессом через stdin и stdout.

Результат сохраняется во временный каталог, в файлы exif_[0,1,2]. Все три файла должны совпадать за исключением метки последнего времени доступа и маркера {ready}.

[cut]

Код: Выделить всё

SetHotkeyAction /K:W /V:219 TestExiftoolSpeed 0
SetHotkeyAction /K:W /V:221 TestExiftoolSpeed 1
SetHotkeyAction /K:W /V:220 TestExiftoolSpeed 2

Static sExif = COMMANDER_PATH & "\Plugins\wlx\ExifToolView\exiftool.exe"
Static sExifOut = TEMP & "\exiftool_out.txt", sExifArgs = TEMP & "\exiftool_args.txt"

Static hChildStdInWr = 0
Static hChildStdOutRd = 0
Static hChildStdInRd = 0
Static hChildStdOutWr = 0

Static hProcess = 0
Static hThread = 0

#TestExiftoolSpeed(3)

Func TestExiftoolSpeed(StayOpen)
    Local sFile = "", j, files = 0, str
    Local sPath = RequestCopyDataInfo("SP")
    Local sName = RequestCopyDataInfo("SN")

    Static aSel = List()
    aSel.Count = 0   
    aSel.Text = GetSelectedItems(3, 0)
    If aSel.Count = 0 Then aSel.Add(sName)
   
    If StayOpen = 1 And Not ProcessExist("exiftool.exe") Then
       DllCall("kernel32.dll\DeleteFileW", "Wstr", sExifOut)       
       DllCall("kernel32.dll\DeleteFileW", "Wstr", sExifArgs)   
       FileWrite(sExifArgs, "", "UTF-8 NOBOM")       
       ShellExec /TT /SW_HIDE %sExif% "-stay_open True -@ ""%sExifArgs%"""
       ShowHint("Запуск exiftool.exe")
       Sleep(1000)
       Local nPID = ProcessGetId("exiftool.exe")
       If nPID = 0 Then Return MsgBox("Процесс ExifTool не запущен")
    EndIf
   
    If StayOpen = 2 Then
       If Not StartExifProcess() Then Return ShowHint("Операция прервана")
    EndIf
   
    If StayOpen = 3 Then
       If StartExifProcess() Then
          WriteLine("-G") 
          WriteLine("-S") 
          WriteLine("-lang")
          WriteLine("ru")       
          WriteLine("d:\Temp\Images\test.jpg")
          WriteLine("-execute")
          #MsgBox(hProcess & " " & hChildStdInWr  & " " & hChildStdOutRd  & " " & hChildStdInRd  & " " & hChildStdOutWr )
          MsgBox(ReadResponse())
          Return ExifClose()
       Else
          Return ShowHint("Операция прервана")
       EndIf
    EndIf
   
    T1 = GetUptime()
   
    ShowHint("Обработка выделенного Exiftool.exe")
    For j = 0 To aSel.Count - 1
   
        sFile = sPath & aSel[j]
       
        If FileExist(sFile) And Not StrPos(FileGetAttr(sFile), "D") Then
           files += 1
           str &= ExiftoolInfo(sFile, StayOpen)
        EndIf   
       
    Next
   
    T2 = Round(GetUptime() - T1, 0) / 1000
    ShowHint("Выбрано: " & aSel.Count & auCRLF & _
             "Обработано: " & files & auCRLF & _
             "-stay_open: " & (StayOpen ? "Да" : "Нет") & auCRLF & _
             "stdin/stdout: " & (StayOpen = 2 ? "Да" : "Нет") & auCRLF & _             
             "Объем данных: " & SizeFormat(StrLen(str), 0, 'B', 2) & auCRLF & _             
             "Время операции: " &  StrFormat("%.3f", T2) & " sec")
    ClipPut(str)
    FileWrite(TEMP & "\_exif_" & StayOpen, str, "UTF-8")
    If StayOpen = 2 Then ExifClose()
    If StayOpen = 1 Then
       ProcessTerminate("exiftool.exe")
       Sleep(100)
       DllCall("kernel32.dll\DeleteFileW", "Wstr", sExifOut)       
       DllCall("kernel32.dll\DeleteFileW", "Wstr", sExifArgs)
    EndIf   
EndFunc

Func ExiftoolInfo(FileName, StayOpen)
    If StayOpen = 2 Then
       #WriteLine("-G")
       #WriteLine("-S")
       #WriteLine("-lang")
       #WriteLine("ru")
       #WriteLine(FileName)       
       #WriteLine("-execute")
       WriteLine("-G" & auCRLF & _
                 "-S" & auCRLF & _
                 "-lang" & auCRLF & _
                 "ru"    & auCRLF & _
                 Filename & auCRLF & _                 
                 "-execute" & auCRLF)
       Return ReadResponse() & auCRLF
    EndIf
    If Not StayOpen Then
       ProcessExecGetOutput /C:65001 out %sExif% '-charset filename=Russian -lang ru -G -S "%FileName%"'
    Else   
       If FileExist(sExifOut) Then
          DllCall("kernel32.dll\DeleteFileW", "Wstr", sExifOut)
       EndIf
       Local sArgs = "-G"   & auCRLF & _
                     "-S"   & auCRLF & _             
                     "-lang" & auCRLF & _
                     "ru"    & auCRLF & _                     
                     "-W+!"  & auCRLF & sExifOut & auCRLF & FileName & auCRLF & "-execute" & auCRLF       
       Static obj = BinaryFile(sExifArgs, "a")   
       obj.WriteStr(sArgs, "", "UTF-8")
       out = "<time-out>"
       For i = 1 To 50
           If FileExist(sExifOut) Then
              out = FileRead(sExifOut, 0, "UTF-8")
              DllCall("kernel32.dll\DeleteFileW", "Wstr", sExifOut)
              Break
           EndIf
           Sleep(10)
       Next
    EndIf   
   Return out
EndFunc

Func StartExifProcess()
    # Настройка атрибутов безопасности для наследования дескрипторов
    Local Sec = Buffer(auX64 ? 24 : 12)
    Sec.Zero()
    Sec.SetNum(0, "UInt", Sec.size) # nLength
    If auX64 Then
       Sec.SetNum(8, "Ptr", 0)   # lpSecurityDescriptor
       Sec.SetNum(16, "Int", 1)  # bInheritHandle
    Else
       Sec.SetNum(4, "Ptr", 0)
       Sec.SetNum(8, "Int", 1)
    EndIf
   
    # Создаем Pipe для STDIN (Запись из Autorun -> Чтение в ExifTool)
    If Not DllCall("CreatePipe", "Ptr*", @hChildStdInRd, "Ptr*", @hChildStdInWr, "Ptr", Sec.Ptr, "UInt", 0) Then
       MsgBox("Error CreatePipe STDIN")
       Return 0
    EndIf   

    # Создаем Pipe для STDOUT (Запись из ExifTool -> Чтение в Autorun)
    if Not DllCall("CreatePipe", "Ptr*", @hChildStdOutRd, "Ptr*", @hChildStdOutWr, "Ptr", Sec.Ptr, "UInt", 0) Then
        DllCall("CloseHandle", "Ptr", hChildStdInRd)
        DllCall("CloseHandle", "Ptr", hChildStdInWr)
        MsgBox("Error CreatePipe")
        Return 0       
    EndIf

    # Запрещаем наследование нашей (записывающей/читающей) стороны каналов
    DllCall("SetHandleInformation", "Ptr", hChildStdInWr, "UInt", 1, "UInt", 0)
    DllCall("SetHandleInformation", "Ptr", hChildStdOutRd, "UInt", 1, "UInt", 0)

    # Настройка STARTUPINFO
    Local SI = Buffer(auPtrSize = 8 ? 104 : 68)
    SI.Zero()
    SI.SetNum(0, "UInt", SI.Size)
    SI.SetNum(auX64 ? 60 : 44, "UInt", 0x00000101) # STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW
    SI.SetNum(auX64 ? 64 : 48, "UShort", 0) # SW_HIDE (скрывает окно консоли)
    SI.SetNum(SI.Size - 3 * auPtrSize, "Ptr", hChildStdInRd)
    SI.SetNum(SI.Size - 2 * auPtrSize, "Ptr", hChildStdOutWr)
    SI.SetNum(SI.Size - auPtrSize, "Ptr", hChildStdOutWr) # Дублируем stdout в stderr

    # Настройка PROCESS_INFORMATION
    PI = Buffer(auPtrSize = 8 ? 24 : 16)
    PI.Zero()

    # Командная строка запуска
    Local cmdLine = '"' & sExif & '" -stay_open True -@ -'

    # Флаг CREATE_NO_WINDOW (0x08000000) полностью блокирует создание консольного окна
    If Not DllCall("CreateProcessW", "Ptr", 0, "WStr", cmdLine, _
                                     "Ptr", 0, "Ptr", 0, "Int", 1, "UInt", 0x08000000, _
                                     "Ptr", 0, "Ptr", 0, "Ptr", SI.Ptr, "Ptr", PI.Ptr) Then
        # Закрываем все открытые дескрипторы при ошибке
        DllCall("CloseHandle", "Ptr", hChildStdInRd)
        DllCall("CloseHandle", "Ptr", hChildStdInWr)
        DllCall("CloseHandle", "Ptr", hChildStdOutRd)
        DllCall("CloseHandle", "Ptr", hChildStdOutWr)
        MsgBox("Error CreateProcess")
        Return 0 
    EndIf

    # Сохраняем нужные дескрипторы
    hProcess = PI.GetNum(0, "Ptr")
    hThread = PI.GetNum(auPtrSize, "Ptr")

    # Закрываем дескрипторы, которые больше не нужны родительскому процессу
    DllCall("CloseHandle", "Ptr", hChildStdInRd)
    DllCall("CloseHandle", "Ptr", hChildStdOutWr)
   
    Return 1
EndFunc

Func WriteLine(text)   
    rawString = text & auCRLF
    # ExifTool ожидает UTF-8 или системную кодировку (зависит от настроек, UTF-8 наиболее универсален)
    bufLen = StrLen(rawString) * 2
    buf = Buffer(bufLen)
    buf.Zero()
    bufWr = buf.SetStr(rawString, 0, bufLen, "UTF-8")
    # Запись через WinAPI WriteFile
    Local bytesWritten = 0
    DllCall("WriteFile", "Ptr", hChildStdInWr, "Ptr", buf.ptr, "UInt", bufWr, "UInt*", @bytesWritten, "Ptr", 0)
    Free(buf)
EndFunc

Func ReadResponse()
    Local output = "", chunk = ""
    # Буфер для побайтового/посимвольного чтения из пайпа
    Static readBuf = Buffer(1024*2)
    readBuf.Zero()
    While 1       
       Local bytesRead = 0
       If Not DllCall("ReadFile", _
                      "Ptr", hChildStdOutRd, _
                      "Ptr", readBuf.Ptr, "UInt", readBuf.Size, "UInt*", @bytesRead, "Ptr", 0) Then
          Break
       EndIf

       If bytesRead <= 0 Then Break

       chunk = readBuf.GetStr(0, bytesRead, "UTF-8")
       output &= chunk             
       If StrPos(chunk, "{ready}") Then
          Break       
       EndIf
    Wend
    Return StrTrim(output)
EndFunc

Func ExifClose()
    If hChildStdInWr Then
       WriteLine("-stay_open")
       WriteLine("False")
       DllCall("CloseHandle", "Ptr", hChildStdInWr)
       hChildStdInWr = 0
    EndIf
    If hChildStdOutRd Then
       DllCall("CloseHandle", "Ptr", hChildStdOutRd)
       hChildStdOutRd = 0
    EndIf   
    if hProcess Then
       DllCall("CloseHandle", "Ptr", hProcess)
       hProcess = 0
    EndIf   
    If hThread Then
       DllCall("CloseHandle", "Ptr", hThread)
       hThread = 0
    EndIf   
EndFunc
[/cut]
Чем больше выделено файлов, тем больше (должен быть) прирост. Это было критично только для набора колонок в режиме просмотра х64. Но опция SetFieldsParam("Delayed", 1) вроде решает эту проблему. Вы ее пробовали?

Там только два прохода почему-то.
AkulaBig
Сообщения: 639
Зарегистрирован: Ср дек 03, 2008 11:48

Сообщение AkulaBig »

Orion9 писал(а):Процесс exiftool должен запуститься один раз, и желательно за пределами этой функции.
За пределами он будет запускаться при старте ТС, что не очень хорошо. Но он запускается один раз, поэтому на данном этапе место запуска не имеет значения.
Orion9 писал(а):В подсказке прирост скорости менее очевиден.
Очень заметен.
Orion9 писал(а):лучше оставить общение с процессом через файл
Лучше, но проблема в том, при запуске колонок они засекаться начинают и все подвисает. А так у меня и вариант с файлами есть. но и там FieldIndex = 1 работает непонятно. Без него работает ощутимо медленней, а с ним когда начинаешь крякозябры убирать, почему-то по одному полю обрабатывается. То ли с FieldIndex = 1 надо алгоритм менять, то ли знать, куда его впихнуть. Но я многие варианты уже попробовал. Пока не получается.
Orion9 писал(а):SetFieldsParam("Delayed", 1)
Пока не пробовал. Не до нее. Сейчас надо сделать чтобы все работало правильно. А в дальнейшем похоже для подсказки придется запускать отдельный экземпляр ET и временные файлы с другим названием.
FallenAngel
Сообщения: 47
Зарегистрирован: Вт дек 30, 2025 20:01

Сообщение FallenAngel »

В последнее время часто вылетает на Win11 25H2

Код: Выделить всё

Имя сбойного приложения: TOTALCMD64.EXE, версия: 11.58.0.0, метка времени: 0x00000000
Имя сбойного модуля: Autorun.wdx64, версия: 2.2.21.3, метка времени: 0x68ef4c72
Код исключения: 0xc0000005
Смещение ошибки: 0x000000000005fa02
Идентификатор сбойного процесса: 0x229C
Время запуска сбойного приложения: 0x1DD50D5A31BB41E
Путь сбойного приложения: D:\soft\Total Commander 64\TOTALCMD64.EXE
Путь сбойного модуля: D:\soft\Total Commander 64\Plugins\Autorun\Autorun.wdx64
Идентификатор отчета: 0c64ebd0-8054-4fdf-8c0b-3116aec21693
Полное имя сбойного пакета: 
Код приложения, связанного со сбойным пакетом: 
AkulaBig
Сообщения: 639
Зарегистрирован: Ср дек 03, 2008 11:48

Сообщение AkulaBig »

Orion9
Вот вариант с временными файлами. Ваш вариант летает как ракета, если FieldIndex = 1. Но кривой вывод:
Изображение
А если оставить -G -S, то только на английском и с наименованием группы.
А вот мой вариант с корректным выводом полей, но без FieldIndex:
[cut]

Код: Выделить всё

Func ET(FileName, FieldIndex, UnitIndex)
	# Расположение файла Autorun.ini
	# Location of Autorun.ini file
	Local ini_TypeHints = COMMANDER_PATH & "\Ini\Autorun.ini"
	# Читаем номер вида
	# Reading type number
	Local TypeHintsNum = IniRead(ini_TypeHints, "TypeHints", "CurrVal", 0)
	# 0 - стандартный вид подсказки над файлами
	# 1 - расширенный вид подсказки над файлами
	If TypeHintsNum = 0 Then Return

	If StrPos(FileGetAttr(FileName), "D") Then Return

	Local iPID = 0, sOutFile = TEMP & "\out.txt", sArgFile = TEMP & "\args.txt"

	iPID = ProcessGetId("exiftool.exe")
	If iPID = 0 Then
		FileWrite(sArgFile, "", "UTF-8 NOBOM")
		ShellExec /TT /SW_HIDE %util_ET% "-stay_open True -@ ""%sArgFile%"""
	EndIf

	# ShellExec /SW_HIDE /WAIT %COMSPEC% "/c break > ""%sOutFile%"""
	DllCall("kernel32.dll\DeleteFileW", "Wstr", sOutFile)

	Local ansi

   If FieldIndex = 1 Then
		Local sArgs = "-lang" & auCRLF & _
			"ru" & auCRLF & _
			"-m" & auCRLF & _
			"-q" & auCRLF & _
			"-q" & auCRLF & _
			"-W+!" & auCRLF & _
			sOutFile & auCRLF & FileName & auCRLF & "-execute" & auCRLF

		Local obj = BinaryFile(sArgFile, "a")
		obj.WriteStr(sArgs, "", "UTF-8")
		Free(obj)

		# Проверяем наличие файла для записи данных
		While 1
			If FileExist(sOutFile) Then
				# Читаем данные из файла
				ansi = FileRead(sOutFile)
				If ansi <> "" Then
					# ShellExec /SW_HIDE /WAIT %COMSPEC% "/c break > ""%sOutFile%"""
					DllCall("kernel32.dll\DeleteFileW", "Wstr", sOutFile)
					Break
				EndIf
			EndIf
			Sleep(10)
		WEnd
	EndIf

	Return TrimSpaces1(StrPart(ansi, auLF, FieldIndex))
EndFunc

Func TrimSpaces1(String)
	Local a, b
	If Not StrPos(String, ":") Then Return String

	a = StrPart(String, ": ", 1)
	b = StrPart(String, ": ", 2)

	Return StrTrim(FixCPET(a)) & ": " & StrTrim(FixCPET(b))
EndFunc

Func FixCPET(String)
	Static a = List(), u = List()
	Local ansi = String, utf8, wrong = Chr(0xFFFD)
	Local nSize = StrLen(ansi)
	Local buf = Buffer(nSize)
	buf.Zero()
	buf.SetStr(ansi, 0, nSize, "ANSI")
	utf8 = buf.GetStr(0, nSize, "CP:65001")
	Free(buf)
	a.Count = 0
	u.Count = 0
	a.Text = ansi
	u.Text = utf8
	For i = 0 to u.Count - 1
	   If StrPos(u[i], wrong) Then u[i] = a[i]
	Next
	Return u.text
EndFunc
[/cut]
Изображение
Он работает медленно. А если прописать FieldIndex, то получается фигня:
Изображение
Но с MI этот-же алгоритм работает без проблем. Очень хочется добиться быстрой и правильной работы.
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

FallenAngel писал(а):Имя сбойного приложения: TOTALCMD64.EXE, версия: 11.58.0.0, метка времени: 0x00000000
Имя сбойного модуля: Autorun.wdx64, версия: 2.2.21.3, метка времени: 0x68ef4c72
Код исключения: 0xc0000005
К сожалению, это мало о чем говорит. Ошибка произошла в модуле Autorun, но что именно случилось, не понятно.
AkulaBig писал(а):Он работает медленно.
Не мудрено. У вас сейчас в этой функции 60 раз читается ini, 60 раз проверяется id процесса и 60 раз удаляется файл. И это для каждой подсказки.
AkulaBig писал(а):А если прописать FieldIndex, то получается фигня
FieldIndex = 1 как раз нужен, чтобы сделать иницилизацию только один раз, когда вызывается первое поле подсказки, у меня так и сделано

Код: Выделить всё

Func ExifHint(FileName, FieldIndex, UnitIndex)
    If FieldIndex > gHintLines Then Return
    If StrPos(FileGetAttr(FileName), "D") Then Return

    Local bCtrl = BitAND(DllCall("GetKeyState", "int", 0x14, "short"), 1)    
    If Not bCtrl then bCtrl = IsPressed (0x11)
    If gHintCaps And Not bCtrl Then Return
          
    If FieldIndex = 1 Then
       #ProcessExecGetOutput /C:65001 gHintPipe %gMediaExifX32% ' -G -S -lang ru "%FileName%"'      
       gHintPipe = GetExifInfo(FileName)
    Endif     
    #Return FixSpaces(StrPart(gHintPipe, auLF, FieldIndex))
    Return StrPart(gHintPipe, auLF, FieldIndex)   
EndFunc
Даже этот код можно попробовать оптимизировать, убрав все лишнее: т.е. убрать все, что выше If FieldIndex = 1, в само условие If FieldIndex = 1.

Объект BinaryFile должен быть в статике, чтобы он тоже не создавался 60 раз

Код: Выделить всё

Static obj = BinaryFile(gExifArgs, "a") 
Free(obj) тогда не нужен.
AkulaBig писал(а):Ваш вариант летает как ракета
Работает он быстро, но не стабильно. Скорее всего файл не успевает удаляться, поэтому не всегда результат в подсказке корректен. Нужно делать дополнительню проверку удаления файла, чтобы гарантировать надежность, но, понятное дело, ценой потери в скорости. Как сказал болван
Если один и тот же файл постоянно перезаписывается, никогда не доверяйте успешному возврату из DeleteFileW. Либо переименовывайте файл в случайное имя перед удалением (чтобы сразу освободить оригинальное имя), либо используйте цикл с GetFileAttributesW, чтобы дождаться реального исчезновения файла с диска.
И соответствующий код
[cut]

Код: Выделить всё

#Requires AutoHotkey v2.0

/**
 * Надежно удаляет файл и ждет, пока его имя полностью освободится в системе.
 * @param {String} filePath - Полный или относительный путь к файлу.
 * @param {Integer} maxAttempts - Максимальное количество попыток ожидания (по умолчанию 100).
 * @returns {Boolean} - true, если файл успешно и окончательно удален; false, если произошла ошибка или вышло время.
 */
ForceDeleteAndVerify(filePath, maxAttempts := 100) {
    ; 1. Проверяем, существует ли файл вообще
    if !FileExist(filePath) {
        return true
    }

    ; 2. Пытаемся удалить файл средствами AHK (внутри вызывается DeleteFileW)
    try {
        FileDelete(filePath)
    } catch Error as err {
        ; Если ошибка "Файл не найден" (код 2), значит файл уже удален
        if (err.Extra == 2) {
            return true
        }
        return false ; Другая ошибка (например, Access Denied / код 5)
    }

    ; 3. Цикл ожидания, пока файл физически исчезнет из файловой системы
    attempts := 0
    while (attempts < maxAttempts) {
        ; Вызываем GetFileAttributesW через DllCall
        ; INVALID_FILE_ATTRIBUTES равен 0xFFFFFFFF (или -1 в 32-битном знаковом представлении)
        attr := DllCall("Kernel32.dll\GetFileAttributesW", "Str", filePath, "UInt")
        
        if (attr == 0xFFFFFFFF) {
            lastError := DllCall("Kernel32.dll\GetLastError", "UInt")
            ; ERROR_FILE_NOT_FOUND = 2, ERROR_PATH_NOT_FOUND = 3
            if (lastError == 2 || lastError == 3) {
                return true ; Удаление наконец-то полностью завершено!
            }
        }

        ; Если файл все еще заблокирован в состоянии Pending Delete, ждем 10 мс
        Sleep(10)
        attempts++
    }

    return false ; Файл так и не исчез за отведенное время
}


; === ПРИМЕР ИСПОЛЬЗОВАНИЯ ===

targetFile := "C:\path\to\your\file.txt" ; Укажите свой путь к файлу

if ForceDeleteAndVerify(targetFile) {
    MsgBox("Файл успешно и гарантированно удален! Теперь можно создавать новый с тем же именем.")
    ; Здесь можно безопасно делать FileAppend или FileOpen с тем же именем
} else {
    MsgBox("Не удалось окончательно удалить файл (таймаут или ошибка доступа).")
}
[/cut]
AkulaBig писал(а):Но кривой вывод
Вывод не кривой, а сырой (неформатированный). Я так и не понял, что хочу от этой подсказки, поэтому она осталась в недоделанном состоянии.
AkulaBig
Сообщения: 639
Зарегистрирован: Ср дек 03, 2008 11:48

Сообщение AkulaBig »

Orion9 писал(а):У вас сейчас в этой функции 60 раз читается ini, 60 раз проверяется id процесса и 60 раз удаляется файл. И это для каждой подсказки.
Это я уже понял.
Orion9 писал(а):Я так и не понял, что хочу от этой подсказки, поэтому она осталась в недоделанном состоянии.
В этом как-раз вся суть моего вопроса. Я-же на скрине показал ваш неформатированный вывод и какой должен быть форматированный вывод на моем скрине. М вот здесь появляется большое "но". Для такого форматированного вывода требуется прописать a = StrPart(String, ": ", 1), b = StrPart(String, ": ", 2). То-есть обработать наименование полей и поля отдельно. Тогда и от пробелов можно избавиться и от крякозябров. Но этот код работает только если нет условия If FieldIndex = 1. Стоит нам его использовать и StrPart(String, ": ", 1) выводит только наименование ОДНОГО поля! Я это тоже на скрине показал. Можно ли придумать решение этой проблемы?
То-есть неформатированный вывод, как на вашем скрине с If FieldIndex = 1 работает. Форматированный вывод, как на моем скрине с If FieldIndex = 1 не работает.
Но для MI подобный алгоритм у меня работает. Не понимаю, почему? Кажется совершенно одинаковый код самого вывода.

Добавлено спустя 35 минут:

На всякий случай решил ваш код выложить с неформатированным выводом. Может вам легче с ним будет работать. Просто он тоже обработан мной. Так как этот код у вас входит в пример для переключения между типом подсказок.
[cut]

Код: Выделить всё

# путь к утилите ExifTool
Global gMediaExifX32 = COMMANDER_PATH & "\Plugins\wlx\ExifToolView\exiftool.exe"
Global gHintPipe = ""
Global gExifPID = 0, gExifStay = 1, gExifOut = TEMP & "\exiftool_out.txt", gExifArgs = TEMP & "\exiftool_args.txt"

If Not ProcessExist("exiftool.exe") Then
  #gExifPID = 0
  FileWrite(gExifArgs, "", "UTF-8 NOBOM")       
  ShellExec /TT /SW_HIDE %gMediaExifX32% "-stay_open True -@ ""%gExifArgs%"""
EndIf

Func ET(FileName, FieldIndex, UnitIndex)
    If StrPos(FileGetAttr(FileName), "D") Then Return

    If FieldIndex = 1 Then
       #ProcessExecGetOutput /C:65001 gHintPipe %gMediaExifX32% ' -G -S -lang ru "%FileName%"'
       gHintPipe = GetExifInfo(FileName)
		gHintPipe = StrPart(gHintPipe, ": ", 1)
    Endif

    Return StrPart(gHintPipe, auLF, FieldIndex)   
EndFunc

Func GetExifInfo(FileName)
    Static a = List(), u = List()
    Local ansi, utf8, wrong = Chr(0xFFFD)
    If Not gExifStay Then
       # ProcessExecGetOutput ansi %gMediaExifX32% '-charset filename=Russian -G -S -lang ru "%FileName%"'
       ProcessExecGetOutput ansi %gMediaExifX32% '-charset filename=Russian -lang ru "%FileName%"'
    Else   
       If FileExist(gExifOut) Then
          DllCall("kernel32.dll\DeleteFileW", "Wstr", gExifOut)
       EndIf
       #Local sArgs = "-W+!" & auCRLF & gExifOut & auCRLF & FileName & auCRLF & "-execute" & auCRLF
       # Local sArgs = "-G"   & auCRLF & _
       #               "-S"   & auCRLF & _
       #               "-lang" & auCRLF & _
       #               "ru"    & auCRLF & _
       #               "-W+!"  & auCRLF & gExifOut & auCRLF & FileName & auCRLF & "-execute" & auCRLF

       Local sArgs = "-lang" & auCRLF & _
                     "ru"    & auCRLF & _
                     "-W+!"  & auCRLF & gExifOut & auCRLF & FileName & auCRLF & "-execute" & auCRLF

       Static obj = BinaryFile(gExifArgs, "a")   
       obj.WriteStr(sArgs, "", "UTF-8")
       #Free(obj)
       ansi = "<time-out>"
       For i = 1 To 50
           If FileExist(gExifOut) Then
              ansi = FileRead(gExifOut, 0, "UTF-8")
              DllCall("kernel32.dll\DeleteFileW", "Wstr", gExifOut)
              Break
           EndIf
           Sleep(20)   # time-out
       Next
       Return ansi
    EndIf   
    Local nSize = StrLen(ansi)
    Local buf = Buffer(nSize)
    buf.Zero()
    buf.SetStr(ansi, 0, nSize, "ANSI")
    utf8 = buf.GetStr(0, nSize, "CP:65001")
    Free(buf)
    a.Count = 0
    u.Count = 0   
    a.Text = ansi
    u.Text = utf8
    For i = 0 to u.Count - 1
       If StrPos(u[i], wrong) Then u[i] = a[i]
    Next
    Return u.text
EndFunc
[/cut]
Я закомментил несколько ваших строк, чтобы русский язык появился. При -S названия полей могут быть только на английском. А при -G обязательно будут выводиться группы. Что, конечно, не нужно. Если эти ключи убрать и попытаться использовать StrPart(String, ": ", 1) у вас получится как у меня, вывод только одного поля.
AkulaBig
Сообщения: 639
Зарегистрирован: Ср дек 03, 2008 11:48

Сообщение AkulaBig »

Orion9
Уф, решил проблему. Всего-то переменную ansi, в которую получаем результат, надо было объявить Static, а не Local.
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

AkulaBig
Если использовать функции для работы с exiftool через stdin/stdout, можно сделать так.

Код: Выделить всё

If Not StartExifProcess() Then Return ShowHint("Операция прервана")
Функция для обработки полей.

[cut]

Код: Выделить всё

Func ExifHint(FileName, FieldIndex, UnitIndex)
    # первое поле подсказки C1
    # инициализация новой подсказки
    If FieldIndex = 1 Then
       
       # объект является каталогом
       If StrPos(FileGetAttr(FileName), "D") Then 
          gHintPipe = ""
          Return
       EndIf   
       
       # проверка CapsLock и Ctrl
       Local bCtrl = BitAND(DllCall("GetKeyState", "int", 0x14, "short"), 1)    
       If Not bCtrl then bCtrl = IsPressed (0x11)
       
       # обратное поведение Ctrl
       If gHintCaps And Not bCtrl Then 
          gHintPipe = ""       
          Return
       EndIf   
       
       # разделение панели на три области
       Local x, y, w, sArgs
       Local hWnd = RequestInfo(ItemAtCursor("panel"))
       WinGetPos("", "", "w", "", hWnd)
       If w > 0 Then
          MouseGetPos("x","y")
          Static buf = Buffer(8)	# POINT
          buf.Zero()
          buf.SetNum(0, "long", x, "long", y)
          DllCall("ScreenToClient", "hwnd", hWnd, "ptr", buf.ptr)
          x = buf.GetNum(0)
          Switch Round(x/w*100,0)
             Case 0 To 9 
                 sArgs = "-G" & auCRLF & "-S"
             Case 10 To 90
                 sArgs = "-G0" & auCRLF & "-lang" & auCRLF & "ru"                 
             Case 91 To 100
                 sArgs = "-g" & auCRLF & "-s2" & auCRLF & "-lang" & auCRLF & "ru"
          EndSwitch
       EndIf
       
       # запись в stdin процесса
       WriteLine(sArgs & auCRLF & Filename & auCRLF & "-execute" & auCRLF)
       
       # получение ответа
       gHintPipe = ReadResponse() & auCRLF
    Endif
    
    # разделение ответа на строки 
    # выполняется для всех полей подсказки C1-C60
    If gHintPipe <> "" Then Return StrPart(gHintPipe, auCRLF, FieldIndex)   
EndFunc
[/cut]
[cut]Изображение[/cut]
Выхлопывает так, что треск стоит.
AkulaBig
Сообщения: 639
Зарегистрирован: Ср дек 03, 2008 11:48

Сообщение AkulaBig »

Orion9
Я все сделал полностью. Подсказки сделал через потоки и они стали отлично работать одновременно с показом колонок. Которые у меня через файлы. По скорости примерно как у вас. С учетом, что у меня время на форматирование тратится. Сейчас надо придумать, как сделать, чтобы ET не висел постоянно в памяти после отображения колонок. Сейчас у меня процесс отслеживается и прибивается, если нет колонок на экране. Но конечно он и подсказку отрубает.
Помнится мы обсуждали тему количества пользовательских полей в Авторане. Все-же они ограничены 60 или можно больше? Вроде в колонках и 120 отображается. Но кажется они не работают. Напомните, пожалуйста, как с этим дело обстоит? Хотелось бы и MI сохранить. Кажется для медиа-файлов он получше. Хотя надо прямо скрины делать. Попытался вручную сравнить. Запутался. Еще и delay не проверял. В общем причесывать буду. Главное наконец-то все работает.
AkulaBig
Сообщения: 639
Зарегистрирован: Ср дек 03, 2008 11:48

Сообщение AkulaBig »

Внимательно потестил выводимые MI и ET данные. Победителя нет. Разные расширения лучше обрабатывает то одна, то другая прога. Решил просто сделать выбор MI или ET для подсказок.
Периодически вылетает такая ошибка:
Изображение
На AU3 у меня тоже такое было. Там вроде решил эту проблему, хоть попотеть пришлось. А здесь можно что-то сделать. Ошибка однозначно как-то связана с условием If FieldIndex = 1. Раньше таких ошибок не было.
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

AkulaBig
Вариант с файлами нормальный, но он не гарантирует полную надежность, к сожалению.

Дело не только в файле exiftool_out.txt, который не успевает удаляться, но чаще сама exiftool не успевает закрыть файл, поэтому FileRead возвращает пусто.

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

Код: Выделить всё

Func GetTempFileName()
    Local tmpfile
    Do
      tmpfile = TEMP & "\temp" & Hex(Random(1, 0xFFFFFF, 1), 6) & ".tmp"
    Until Not FileExist(tmpfile)
    Return tmpfile
EndFunc 
И поставить первую задержку перед FileRead, а не после, чтобы уменьшить шанс пустого выхлопа. Полный фрагмент будет выглядеть так

Код: Выделить всё

sExifOut  = GetTempFileName()       
Local sArgs = "-G0"   & auCRLF & _             
              "-lang" & auCRLF & _
              "ru"    & auCRLF & _                     
              "-W+!"  & auCRLF & gExifOut & auCRLF & FileName & auCRLF & "-execute" & auCRLF       
Static obj = BinaryFile(gExifArgs, "a")    
obj.WriteStr(sArgs, "", "UTF-8")
ansi = "<time-out>"
For i = 1 To 10
    Sleep(50)
    If FileExist(gExifOut) Then
       ansi = FileRead(gExifOut, 0, "UTF-8")
       DllCall("kernel32.dll\DeleteFileW", "Wstr", gExifOut)
       Break
    EndIf
Next
Return ansi
Это существенно увеличивает надежность, но вряд ли гарантирует ее полностью.

Чем больше задержки, тем выше надежность, но скорость при этом, конечно, теряется, хотя незначительно, на подказке это не сильно отразится.

У вас файлы для тестов есть? Можете на дропбокс скинуть? Я чуть позже отпишусь.
AkulaBig
Сообщения: 639
Зарегистрирован: Ср дек 03, 2008 11:48

Сообщение AkulaBig »

Orion9 писал(а):не успевает удаляться, но чаще сама exiftool не успевает закрыть файл, поэтому FileRead возвращает пусто.
У меня через цикл While сделано. Так что вроде все успевает отработать. Задержка действительно задерживает вывод, поэтому не стоит ее такой большой делать. Ошибка, которая периодически появляется вообще никак с файлами не связано. Ведь подсказка у меня сделана на потоках. Она как-то связана с номером поля.
Файлы попозже выложу. Я сейчас причесываю код и подсказки не показывает. Надо будет выбрать, где максимальная задержка.
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

AkulaBig
Ну в общем понятно, почему я не сдал делать через stdin/stdout. Геморроя больше.

Когда взаимодействие идет через файлы, Autorun вообще ничего не знает о процессе exiftool: его задача - записать в один файл и ждать ответа в другом.

Но когда взаимодействие идет с самом процессом, приходится ждать пока он не отдаст все данные до конца, и так становится сложнее организовать таймаут. Пока я сделал так, но это приводит к другим проблемам

[cut]

Код: Выделить всё

Func ReadResponse()
    Local output = "", chunk = "", bytesAvailable, i = 0
    # Буфер для побайтового/посимвольного чтения из пайпа
    Static readBuf = Buffer(1024*2) 
    readBuf.Zero()
    While 1       
       If Not DllCall("PeekNamedPipe", _
                      "Ptr", hChildStdOutRd, _
                      "Ptr", 0, "UInt", 0, "Ptr", 0, "Dword*", @bytesAvailable, "Ptr", 0, "Int") Then
          Return "---" & auCRLF & _
                 "Exiftool <no-response>" & auCRLF & "---" 
       EndIf
       
       If bytesAvailable <= 0 Then
           i += 1       
           # OutputDebugString("bytesAvailable = " & bytesAvailable & "  try = " & i)    
           Sleep(10)       
           If i > 50 Then 
             Return "---" & auCRLF & "Exiftool <time-out>" & auCRLF & "---" & auCRLF & output
           Else   
              Continue
           EndIf   
       EndIf
       
       Local bytesRead = 0
       # OutputDebugString("ReadFile")
       If Not DllCall("ReadFile", _
                      "Ptr", hChildStdOutRd, _
                      "Ptr", readBuf.Ptr, "UInt", readBuf.Size, "UInt*", @bytesRead, "Ptr", 0) Then
          Break
       EndIf

       If bytesRead <= 0 Then Break

       # chunk = readBuf.GetStr(0, bytesRead, "ANSI") 
       chunk = readBuf.GetStr(0, bytesRead, "UTF-8")        
       output &= chunk              
       If StrPos(chunk, "{ready}") Then 
          Break       
       EndIf
    Wend
    Return StrTrim(output)
EndFunc
[/cut]
AkulaBig
Сообщения: 639
Зарегистрирован: Ср дек 03, 2008 11:48

Сообщение AkulaBig »

Orion9 писал(а):почему я не сдал делать через stdin/stdout. Геморроя больше.
Дак другого выхода нет. Вы попробуйте решить вопрос комплексно и увидите, что пользовательские колонки и подсказка одновременно не будут работать через временные файлы. Поэтому у меня сейчас колонки сделаны через файлы, а подсказка через поток. Все отлично работает.
Orion9 писал(а):так становится сложнее организовать таймаут
А если делать разными методами, то никакого таймаута не надо. А полное чтение данных обеспечивает цикл While. А попытки сделать заданное количество шагов приводят к непрочтению данных. Вы-же не знаете, сколько времени надо, чтобы прочитать какой либо файл. Чтобы прочитать все данные из моего тяжелого файла, мне пришлось и WinScriptsAdv переделать. В нем тоже этот момент не был учтен.
Я сборку доделал, так что можете посмотреть, как все отлично работает одновременно. Подсказка переключается на ПИ настроек. Можно стандартные, MI, ET. Сейчас хочу добавить чтобы можно было переключать временно источник подсказки через Ctrl. Ну и главный вопрос, что делать с запущенным экземпляром ET. Он запускается только после просмотра колонок, но после этого висит в памяти. А вот ET от подсказки не висит в памяти. Жаль, что в AU3 так не сделать. Вернее я могу конечно завершать ET на каждом файле, но это раза в 3 увеличивает время вывода инфы в колонках.

Добавлено спустя 25 минут:

Вот архивчик тяжелых файлов: https://www.dropbox.com/scl/fi/bqzcyojb ... 515a1&dl=1
Аватара пользователя
Orion9
Сообщения: 1283
Зарегистрирован: Пн янв 01, 2024 17:18

Сообщение Orion9 »

AkulaBig
Извиняюсь за задержку. Сейчас времени совсем мало, отрываться как раньше уже не получается и вряд ли скоро получится.
AkulaBig писал(а): Все-же они ограничены 60 или можно больше
Можно больше, но я не помню точное число. В общем, ничто не мешает создать еще 30-40 полей типа C61-C100, но они могут вылезать за экран.

Можно, при желании, сделать дополнительный алгоритм, который будет проверять текущие настройки экрана и в зависимости от них выделять нужное количество строк.
AkulaBig писал(а): Периодически вылетает такая ошибка:
Скорее всего индекс в массиве вышел за границы допустимого или, как вы верно заметили, номер поля оказался выше или ниже доступного. Например, если обратиться к полу C62, которого не существует, будет такая ошибка.
AkulaBig писал(а):А если делать разными методами, то никакого таймаута не надо
Таймаут это бонус, чтобы не ждать отклика от exiftool несколько секунд с повисшим интерфейсом ТС, но задачу надежности сам по себе он решает. Для полной надежности нужно проверять результат FileRead(gExifOut, 0, "UTF-8"), ждать пока ошибок чтения не будет. Или можно увеличить слип перед этим и забить на тот факт, что exiftool может в этот слип не уложиться.
AkulaBig писал(а):Ну и главный вопрос, что делать с запущенным экземпляром ET.
Такие вопросы нужно у [s]болвана[/s] Супер Интеллекта Трампа спрашивать, он их как семечки щелкает.

У вас же отдельный поток все время крутится, можно из него переодически проверять зависшие процессы exiftool.exe и прибивать.
Ответить