MultiHash

Share your advanced PureBasic knowledge/code with the community.
User_Russian
Addict
Addict
Posts: 1549
Joined: Wed Nov 12, 2008 5:01 pm
Location: Russia

MultiHash

Post by User_Russian »

This program calculates the hash (CRC32, MD5, SHA 1, SHA2 and SHA3) of the selected file. It is optimized for multicore processors, and may use up to 12 cores, upon activation of all hash functions.

Code: Select all

Enumeration FormWindow
  #Window_Main
EndEnumeration

Enumeration FormGadget
  #String_0
  #Button_0
  #Text_0
  #ProgressBar_0
  #Button_1
  #ListIcon_0
EndEnumeration

Declare ResizeGadgetsWindow_Main()

Procedure OpenWindow_Main(x = 0, y = 0, width = 548, height = 300)
  OpenWindow(#Window_Main, x, y, width, height, "", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget | #PB_Window_ScreenCentered)
  StringGadget(#String_0, 4, 8, 460, 20, "", #PB_String_ReadOnly)
  ButtonGadget(#Button_0, 472, 4, 72, 24, "Browse")
  TextGadget(#Text_0, 4, 36, 460, 16, "")
  ProgressBarGadget(#ProgressBar_0, 4, 52, 460, 16, 0, 100, #PB_ProgressBar_Smooth)
  ButtonGadget(#Button_1, 472, 44, 72, 24, "Start")
  ListIconGadget(#ListIcon_0, -2, 72, 550, 228, "Function", 130, #PB_ListIcon_CheckBoxes | #PB_ListIcon_MultiSelect | #PB_ListIcon_GridLines | #PB_ListIcon_FullRowSelect | #PB_ListIcon_AlwaysShowSelection)
  AddGadgetColumn(#ListIcon_0, 1, "Result", 410)
  AddGadgetItem(#ListIcon_0, -1, "CRC32")
  AddGadgetItem(#ListIcon_0, -1, "MD5")
  AddGadgetItem(#ListIcon_0, -1, "SHA1")
  AddGadgetItem(#ListIcon_0, -1, "SHA2 - 224")
  AddGadgetItem(#ListIcon_0, -1, "SHA2 - 256")
  AddGadgetItem(#ListIcon_0, -1, "SHA2 - 384")
  AddGadgetItem(#ListIcon_0, -1, "SHA2 - 512")
  AddGadgetItem(#ListIcon_0, -1, "SHA3 - 224")
  AddGadgetItem(#ListIcon_0, -1, "SHA3 - 256")
  AddGadgetItem(#ListIcon_0, -1, "SHA3 - 384")
  AddGadgetItem(#ListIcon_0, -1, "SHA3 - 512")
EndProcedure

Procedure ResizeGadgetsWindow_Main()
  Protected FormWindowWidth, FormWindowHeight
  FormWindowWidth = WindowWidth(#Window_Main)
  FormWindowHeight = WindowHeight(#Window_Main)
  ResizeGadget(#String_0, 4, 8, FormWindowWidth - 88, 20)
  ResizeGadget(#Button_0, FormWindowWidth - 76, 4, 72, 24)
  ResizeGadget(#Text_0, 4, 36, FormWindowWidth - 88, 16)
  ResizeGadget(#ProgressBar_0, 4, 52, FormWindowWidth - 88, 16)
  ResizeGadget(#Button_1, FormWindowWidth - 76, 44, 72, 24)
  ResizeGadget(#ListIcon_0, -2, 72, FormWindowWidth - -2, FormWindowHeight - 72)
EndProcedure

UseCRC32Fingerprint()
UseMD5Fingerprint()
UseSHA1Fingerprint()
UseSHA2Fingerprint()
UseSHA3Fingerprint()

CompilerIf #PB_Compiler_Thread=0
  CompilerError "Please, enable option 'threadsafe'"
CompilerEndIf

EnableExplicit

#ProgName = "MultiHash 1.0"

#FileBuffSize = 1024*1024
#FileBuffCount = 50

Enumeration
  #CRC32
  #MD5
  #SHA1
  #SHA2_224
  #SHA2_256
  #SHA2_384
  #SHA2_512
  #SHA3_224
  #SHA3_256
  #SHA3_384
  #SHA3_512
  #CountFunct  ; Число хеш-функций.
EndEnumeration

#Mask = (1<<#CountFunct)-1

Structure ThInfo_FileBuff ; Буфер прочитанных данных с диска.
  *Point              ; Буфер с данными.
  Size.l              ; Число байт в буфере.
  FinishFlag.l        ; Флаги завершения использования блока данных хеш-функциями.
  NumBlock.q          ; Номер блока данных.
EndStructure

Structure ThInfo_File
  List Buff.ThInfo_FileBuff()
  *ID          ; Идентификатор файла.
  Size.q       ; Размер файла.
  Pos.q        ; Текущая позиция от начала файла.
  NumCounter.q ; Счетчик для нумерации блоков данных.
  Eof.a        ; #True - файл прочитан полностью.
EndStructure

Structure ThInfo_Hash
  *Fingerprint ; Идентификатор хеш-функции.
  Result.s     ; Результат работы функции.
  CurrentBlock.q
  Type.a       ; Тип хеш-функции.
  Work.a
EndStructure

Structure ThreadInfo
  List Hash.ThInfo_Hash()
  Array ThreadID.i(0) ; Массив идентификаторов потоков хеш-функций.
  FileThreadID.i      ; Идентификатор потока чтения данных из файла.
  File.ThInfo_File
  Mutex.i             ; Мьютекс...
  Semaphore.i
  HashMask.l          ; Маска используемых хеш-функций.
  FinishStatus.a
EndStructure


Define Event, Gadget, NewList SpeedList.q()
Global gThread.ThreadInfo, gBreakThread=#False, gSpeed=0

Macro SetBit(Var, Bit) ; Установка бита.
  Var | (Bit)
EndMacro

Macro ClearBit(Var, Bit) ; Обнуление бита.
  Var & (~(Bit))
EndMacro

Macro TestBit(Var, Bit) ; Проверка бита (#True - установлен; #False - обнулен).
  Bool(Var & (Bit))
EndMacro

Macro NumToBit(Num) ; Позиция бита по его номеру.
  (1<<(Num))
EndMacro

Procedure.q Add_AverageList_Quad(List MyList.q(), Add.q, MaxList.l)
  Protected Result.q, x.l, i, Size
  
  Size = ListSize(MyList())
  If Size>MaxList
    LastElement(MyList())
    For i=Size-1 To MaxList Step -1
      DeleteElement(MyList())
    Next i
    Size = ListSize(MyList())
  EndIf
    
  x=#False
  If Size<MaxList
    FirstElement(MyList())
    If InsertElement(MyList())
      x=#True
      Size + 1
    EndIf
  Else
    x=#True
    If Size=MaxList
      LastElement(MyList())
      MyList()=0
      MoveElement(MyList(), #PB_List_First)
    EndIf
  EndIf
  
  
  If x=#True
    FirstElement(MyList())
    MyList() = Add
  EndIf
  
  
  Result=0 : x=0
  ForEach MyList()
    Result + MyList()
    x + 1
  Next
  
  Result / x
  
  ProcedureReturn Result
EndProcedure


Procedure FileThread(*x) ; Процедура потока чтения данных из файла.
  Protected i, x, Eof=#False
  Protected *Point, Bytes
  
  Repeat
    WaitSemaphore(gThread\Semaphore) ; Ожидание сигнала чтения данных из файла.
    
    If gBreakThread=#True
      Break
    EndIf
    
    Repeat 
      
      If Eof(gThread\File\ID)
        Eof=#True
        Break 2
      EndIf
      
      x=#False
      LockMutex(gThread\Mutex)
      If ListSize(gThread\File\Buff())<#FileBuffCount
        x = #True ; Нужно загрузить данные из файла в буфер.
      EndIf
      UnlockMutex(gThread\Mutex)
      
      If x = #False
        Break
      EndIf
      
      *Point = AllocateMemory(#FileBuffSize, #PB_Memory_NoClear)
      If *Point
        
        Bytes = ReadData(gThread\File\ID, *Point, #FileBuffSize)
        
        If Bytes<=0 Or Bytes>#FileBuffSize
          Break 2
        EndIf
        
        LockMutex(gThread\Mutex)
        gThread\File\Pos + Bytes
        LastElement(gThread\File\Buff())
        If AddElement(gThread\File\Buff())
          With gThread\File\Buff()
            \Point = *Point
            \Size  = Bytes
            \FinishFlag=0
            \NumBlock = gThread\File\NumCounter
          EndWith
          gThread\File\NumCounter+1
        Else
          FreeMemory(*Point)
          UnlockMutex(gThread\Mutex)
          Break 2
        EndIf
        UnlockMutex(gThread\Mutex)
        
      Else
        Delay(2)
      EndIf
      
    ForEver
    
  Until gBreakThread=#True
  
  LockMutex(gThread\Mutex)
  If Eof
    gThread\File\Eof = #True
  EndIf
  x=gThread\File\ID
  gThread\File\ID=0
  UnlockMutex(gThread\Mutex)
  
  If x And IsFile(x)
    CloseFile(x)
  EndIf
  
EndProcedure

Procedure HashThread(*x) ; Процедура потока выполнения хеш-функций.
  Protected i, NumFunct, NoData
  Protected *Point, Size, NumBlock.q
  Protected *Fingerprint
  
  Repeat
    
    *Point=0 : *Fingerprint=0 
    NumFunct=-1 : NumBlock=-1
    NoData = #True
    LockMutex(gThread\Mutex)
    
    ForEach gThread\Hash() ; Ищем хеш-функцию которая сейчас не выполняется в других потоках.
      If gThread\Hash()\Work = #False And gThread\Hash()\Fingerprint
        
        ForEach gThread\File\Buff() ; Ищем блок данных, который следующим нужно прохешировать.
          If gThread\File\Buff()\NumBlock = gThread\Hash()\CurrentBlock
            NumBlock = gThread\Hash()\CurrentBlock
            gThread\Hash()\CurrentBlock+1
            *Fingerprint=gThread\Hash()\Fingerprint
            *Point=gThread\File\Buff()\Point
            Size=gThread\File\Buff()\Size
            NumFunct = gThread\Hash()\Type
            gThread\Hash()\Work=#True
            NoData = #False
            Break 2
          EndIf
        Next
        
      EndIf
    Next
 
    UnlockMutex(gThread\Mutex)
    
    If *Point And *Fingerprint And Size>0 And NumBlock>=0 And (NumFunct>=0 And NumFunct<#CountFunct)
      
      AddFingerprintBuffer(*Fingerprint, *Point, Size) ; Вычисляем хеш...
      
      LockMutex(gThread\Mutex)
      ForEach gThread\File\Buff()
        
        If gThread\File\Buff()\NumBlock = NumBlock
          SetBit(gThread\File\Buff()\FinishFlag, NumToBit(NumFunct)) ; Отмечаем блок данных обработаным текущей функцией.
          
          ForEach gThread\Hash() ; Ищем идентификатор хеш-функции.
            If gThread\Hash()\Type = NumFunct
              gThread\Hash()\Work=#False
              Break
            EndIf
          Next
          
          ; Блок данных обработан всеми активными хеш-функциями.
          If (gThread\File\Buff()\FinishFlag ! gThread\HashMask) & #Mask = 0
            FreeMemory(gThread\File\Buff()\Point)
            DeleteElement(gThread\File\Buff())
            If gThread\File\Eof = #False
              SignalSemaphore(gThread\Semaphore) ; Загрузка следующего блока данных.
            EndIf
          EndIf
          
          Break
        EndIf
      Next
      UnlockMutex(gThread\Mutex)
      
    Else
      i=#False
      If NoData = #True
        LockMutex(gThread\Mutex)
        If gThread\File\Eof = #True
          If gThread\FinishStatus=#False And ListSize(gThread\File\Buff())<=0
            ForEach gThread\Hash()
              gThread\Hash()\Result=FinishFingerprint(gThread\Hash()\Fingerprint)
              gThread\Hash()\Fingerprint=0
            Next
            gThread\FinishStatus=#True
          EndIf
          i=#True
        EndIf
        UnlockMutex(gThread\Mutex)
      EndIf
      If i=#True
        Break
      EndIf
      Delay(2)
    EndIf
    
  Until gBreakThread=#True
EndProcedure

Procedure Timer()
  Shared SpeedList()
  Protected Pos.f, Speed, x, Count, i
  
  LockMutex(gThread\Mutex)
  If gThread\File\Pos>0
    Pos=100/(gThread\File\Size / gThread\File\Pos)
  Else
    Pos=0
  EndIf
  Speed = gThread\File\Pos-gSpeed
  gSpeed=gThread\File\Pos
  x=gThread\FinishStatus
  UnlockMutex(gThread\Mutex)
  
  If x=#False
    Speed = Add_AverageList_Quad(SpeedList(), Speed*4, 10)
    SetGadgetState(#ProgressBar_0, Pos)
    SetGadgetText(#Text_0, "Processed "+Str(Pos)+"%  Speed "+StrF((Speed/1024/1024), 1)+" MB/s")
  Else
    
    
    x=#False
    Count=ArraySize(gThread\ThreadID())
    For i=0 To Count
      If gThread\ThreadID(i)<>0 And IsThread(gThread\ThreadID(i))
        x=#True
        Break
      EndIf
    Next i
    
    If x=#False
      ForEach gThread\Hash()
        SetGadgetItemText(#ListIcon_0, gThread\Hash()\Type, gThread\Hash()\Result, 1)
      Next
      SetGadgetText(#Text_0, "Done")
      SetGadgetText(#Button_1, "Start")
      SetGadgetState(#ProgressBar_0, 0)
      UnbindEvent(#PB_Event_Timer, @Timer(), #Window_Main, 2)
      RemoveWindowTimer(#Window_Main, 2)
    Else
      SetGadgetText(#Text_0, "Waiting for completion threads")
    EndIf
    
  EndIf
  
EndProcedure

Macro ThreadWait(Thread)
  If Thread<>0 And IsThread(Thread) ; Нашли активный поток.
    WaitThread(Thread, 4000)        ; Даем потоку время на завершение (4 секунды).
    If IsThread(Thread)             ; Если поток не завершился,
      KillThread(Thread)            ; тогда поможем ему это сделать.
      x=#True
    EndIf
  EndIf
  Thread=0
EndMacro

Procedure StopAndFree()
  Shared SpeedList()
  Protected i, Count, x
  
  gBreakThread=#True ; Флаг прерывания потоков.
  x=#False
  
  UnbindEvent(#PB_Event_Timer, @Timer(), #Window_Main, 2)
  RemoveWindowTimer(#Window_Main, 2)
  SetGadgetText(#Text_0, "")
  SetGadgetState(#ProgressBar_0, 0)
  SetGadgetText(#Button_1, "Start")
  gSpeed=0
  ClearList(SpeedList())
  
  SignalSemaphore(gThread\Semaphore)
  ThreadWait(gThread\FileThreadID)
  
  Count=ArraySize(gThread\ThreadID())
  
  For i=0 To Count
    ThreadWait(gThread\ThreadID(i))
  Next i
  
  If x=#True ; Если потоки были принудительно завершены, нужно пересоздать мьютекс.
    If gThread\Mutex
      FreeMutex(gThread\Mutex)
    EndIf
    gThread\Mutex = CreateMutex()
  EndIf
  
  If gThread\File\ID<>0 And IsFile(gThread\File\ID)
    CloseFile(gThread\File\ID)
    gThread\File\ID=0
  EndIf
  
  ForEach gThread\File\Buff()
    If gThread\File\Buff()\Point
      FreeMemory(gThread\File\Buff()\Point)
      gThread\File\Buff()\Point=0
    EndIf
  Next
  
  ForEach gThread\Hash()
    If gThread\Hash()\Fingerprint And IsFingerprint(gThread\Hash()\Fingerprint)
      FinishFingerprint(gThread\Hash()\Fingerprint)
      gThread\Hash()\Fingerprint=0
    EndIf
  Next
  
  ClearList(gThread\File\Buff()) ; Очистка списков.
  ClearList(gThread\Hash())
  
  gThread\HashMask=0
EndProcedure

Procedure StartStop()
  Protected i, x, String.s, CountFunct
  Protected Plugin, Bits, Err=#False
  
  If IsThread(gThread\FileThreadID)
    If MessageRequester(#ProgName, "Cancel calculating hash sum?",
                        #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
      StopAndFree() ; Освобождение всех ресурсов, используемых в потоках.
    EndIf
    ProcedureReturn
  EndIf
    
  StopAndFree() ; Освобождение всех ресурсов, используемых в потоках.
  gBreakThread=#False
  
  gThread\HashMask=0
  gThread\File\Eof=#False
  gThread\FinishStatus=#False
  gThread\File\NumCounter=0
  CountFunct=0
  x=CountGadgetItems(#ListIcon_0)-1 ; Очистка колонки "Результат" таблицы.
  For i=0 To x
    SetGadgetItemText(#ListIcon_0, i, "", 1)
    If GetGadgetItemState(#ListIcon_0, i) & #PB_ListIcon_Checked
      If AddElement(gThread\Hash())
        
        Bits=0
        Select i
          Case #CRC32
            Plugin=#PB_Cipher_CRC32
          Case #MD5
            Plugin=#PB_Cipher_MD5
          Case #SHA1
            Plugin=#PB_Cipher_SHA1
          Case #SHA2_224
            Plugin=#PB_Cipher_SHA2
            Bits=224
          Case #SHA2_256
            Plugin=#PB_Cipher_SHA2
            Bits=256
          Case #SHA2_384
            Plugin=#PB_Cipher_SHA2
            Bits=384
          Case #SHA2_512
            Plugin=#PB_Cipher_SHA2
            Bits=512
          Case #SHA3_224
            Plugin=#PB_Cipher_SHA3
            Bits=224
          Case #SHA3_256
            Plugin=#PB_Cipher_SHA3
            Bits=256
          Case #SHA3_384
            Plugin=#PB_Cipher_SHA3
            Bits=384
          Case #SHA3_512
            Plugin=#PB_Cipher_SHA3
            Bits=512
        EndSelect
        
        gThread\Hash()\Type=i
        If i<=#SHA1
          gThread\Hash()\Fingerprint = StartFingerprint(#PB_Any, Plugin)
        Else
          gThread\Hash()\Fingerprint = StartFingerprint(#PB_Any, Plugin, Bits)
        EndIf
        
        If gThread\Hash()\Fingerprint
          SetBit(gThread\HashMask, NumToBit(i))
          CountFunct+1
        Else
          DeleteElement(gThread\Hash())
          Goto m1
        EndIf
        
      Else
        m1:
        SetGadgetItemText(#ListIcon_0, i, "Error function", 1)
      EndIf
    EndIf
  Next i
  
  If gThread\HashMask=0
    MessageRequester("", "Select hash-functions")
    ProcedureReturn
  EndIf
  
  String = GetGadgetText(#String_0)
  If String<>"" And FileSize(String)>0
    gThread\File\ID = ReadFile(#PB_Any, String, #PB_File_SharedRead|#PB_File_SharedWrite)
    If gThread\File\ID
      
      gThread\File\Size = Lof(gThread\File\ID)
      gThread\File\Pos  = 0
      
      BindEvent(#PB_Event_Timer, @Timer(), #Window_Main, 2)
      AddWindowTimer(#Window_Main, 2, 250)
      SetGadgetText(#Button_1, "Stop")
      
      SignalSemaphore(gThread\Semaphore)
      gThread\FileThreadID = CreateThread(@FileThread(), 0)
      If gThread\FileThreadID
        ThreadPriority(gThread\FileThreadID, 30) ; Высокий приоритет.
        
        x=CountCPUs(#PB_System_ProcessCPUs)-1 ; Количество доступных ядер процессора.
        CountFunct-1
        If CountFunct<x ; Число активных функций меньше чем доступных ядер процессора.
          x=CountFunct
        EndIf
        
        If ArraySize(gThread\ThreadID())<>x
          ReDim gThread\ThreadID(x)
        EndIf
        
        For i=0 To x
          gThread\ThreadID(i) = CreateThread(@HashThread(), 0)
          If gThread\ThreadID(i) = 0
            Goto m2
          EndIf
        Next i
        
      Else
        m2:
        Err=#True
        MessageRequester(#ProgName, "Failed to start thread")
      EndIf
      
    Else
      MessageRequester(#ProgName, "Could not open file")
    EndIf
  Else
    MessageRequester(#ProgName, "Select the file path")
  EndIf
  
  If Err=#True ; Произошла ошибка.
    StopAndFree() ; Освобождение всех ресурсов, используемых в потоках.
  EndIf
  
EndProcedure

Procedure SelectFile()
  Protected File.s
  
  File = OpenFileRequester("","","All files (*.*)|*.*", 0)
  If File<>"" And FileSize(File)>0
    SetGadgetText(#String_0, File)
  EndIf
  
EndProcedure

Procedure CopyToClipboard()
  Protected i, Count
  Protected String.s="", Result.s
  
  Count=CountGadgetItems(#ListIcon_0)-1
  
  For i=0 To Count
    String = GetGadgetItemText(#ListIcon_0, i, 1)
    If String<>""
      If Result<>"" : Result+#CRLF$ : EndIf
      Result+GetGadgetItemText(#ListIcon_0, i, 0)+"    "+String
    EndIf
  Next i
  
  If Result<>""
    SetClipboardText(Result)
  EndIf
  
EndProcedure

Procedure ResizeWin()
  ResizeGadgetsWindow_Main()
  SetGadgetItemAttribute(#ListIcon_0, 0, #PB_ListIcon_ColumnWidth, 
                         GadgetWidth(#ListIcon_0)-10-GetGadgetItemAttribute(#ListIcon_0, 0,
                                                                            #PB_ListIcon_ColumnWidth, 0), 1)
EndProcedure

gThread\Mutex = CreateMutex()
gThread\Semaphore = CreateSemaphore()
ReDim gThread\ThreadID(CountCPUs(#PB_System_ProcessCPUs)-1) ; Количество доступных ядер процессора.

If CreatePopupMenu(0)
  MenuItem(0, "Copy")
EndIf
  
OpenWindow_Main()
SetWindowTitle(#Window_Main, #ProgName)
SmartWindowRefresh(#Window_Main, #True)
WindowBounds(#Window_Main, 300, 200, #PB_Ignore, #PB_Ignore)
BindEvent(#PB_Event_Gadget, @SelectFile(), #Window_Main, #Button_0)
BindEvent(#PB_Event_Gadget, @StartStop(), #Window_Main, #Button_1)
BindEvent(#PB_Event_SizeWindow, @ResizeWin(), #Window_Main)
BindEvent(#PB_Event_Menu, @CopyToClipboard(), #Window_Main, 0)

Repeat
  Event = WaitWindowEvent()
  
  If Event = #PB_Event_Gadget
    If EventGadget()=#ListIcon_0 And EventType()=#PB_EventType_RightClick
      DisplayPopupMenu(0, WindowID(#Window_Main))
    EndIf
  EndIf
  
Until Event = #PB_Event_CloseWindow

StopAndFree()

If gThread\Mutex
  FreeMutex(gThread\Mutex)
EndIf

If gThread\Semaphore
  FreeSemaphore(gThread\Semaphore)
EndIf

End