MD5-Hash mit Callback, Fortschritt, verbleibende Zeit, usw.

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

MD5-Hash mit Callback, Fortschritt, verbleibende Zeit, usw.

Beitrag von AND51 »

Hallo!

Basierend auf Kiffi's Code habe ich eine Prozedur geschrieben, die von einer beliebigen Datei MD5 Prüfsummen ermittelt.

Das Problem: Ist die Datei mehere MB oder sogar GB groß, kommt man mit MD5FileFingerprint() nicht sehr weit. Der Befehl ist unflexibel und bietet keinerlei RÜckmeldung während der Operation.

Anders meine Prozedur MD5FileFingerprintCallback(). Sie bietet
  • Fortschritt in Prozent
  • verbleibende Zeit
  • verstrichene Zeit
  • Geschwindigkeit in Byte pro Sekunde (mit der beigefügten bytecalc()-Prozedur von mir kann dies bequem in höhrere Einheiten umgerechnet werden)
  • Abbruchmöglichkeit, falls Callback einen Wert ungleich null zurückgibt
Hier der Code:

Code: Alles auswählen

#file="D:\Dateien\CD Image.img"



Procedure.s bytecalc(byte.q, NbDecimals.l=0)
	If byte < 1024
		If byte < 0
			ProcedureReturn "0 Byte"
		EndIf
		ProcedureReturn Str(byte)+" Byte"
	ElseIf byte >= 1<<60
		ProcedureReturn StrD(byte/1<<60, NbDecimals)+" EB"
	ElseIf byte >= 1<<50
		ProcedureReturn StrD(byte/1<<50, NbDecimals)+" PB"
	ElseIf byte >= 1<<40
		ProcedureReturn StrD(byte/1<<40, NbDecimals)+" TB"
	ElseIf byte >= 1<<30
		ProcedureReturn StrD(byte/1<<30, NbDecimals)+" GB"
	ElseIf byte >= 1<<20
		ProcedureReturn StrD(byte/1<<20, NbDecimals)+" MB"
	Else
		ProcedureReturn StrD(byte/1024, NbDecimals)+" KB"
	EndIf
EndProcedure


Procedure myCallback(file.s, progress.l, speed.l, remainingTime.l, elapsedTime.l)
	SetGadgetText(3, "Entire progress: "+Str(progress)+"%")
	SetGadgetText(4, "Speed: "+bytecalc(speed)+"/sec ("+Str(speed)+" Byte/s)")
	SetGadgetText(5, FormatDate("Elapsed: %hhh%iim%sss", elapsedTime)+#CRLF$+FormatDate("Remaining: %hhh%iim%sss", remainingTime))
	SetGadgetState(0, Int(progress))
	While WindowEvent() : Wend
	ProcedureReturn 0 ; Return 0 to continue, 1 to abort
EndProcedure

Procedure.s MD5FileFingerprintCallback(file$, *callback=0, callCallbackEvery=1000, bufferSize=4096)
	; Callback format: myCallback(file.s, progress.l, speed.l, remainingTime.l, elapsedTime.l)
	Protected startTime.l=Date(), file.l=ReadFile(#PB_Any, file$), hash.s
	If file
		Protected *buffer=AllocateMemory(bufferSize)
		If *buffer
			Protected fingerprint=ExamineMD5Fingerprint(#PB_Any)
			If fingerprint
				Protected progress.l, refresh.l, elapsed.l, speed.l, remaining.l
				While Not Eof(file)
					NextFingerprint(fingerprint, *buffer, ReadData(file, *buffer, bufferSize))
					If *callback And ElapsedMilliseconds() > refresh 
						If Lof(file) ; to avoid division by zero if filesize is 0 bytes
							elapsed=Date()-startTime
							progress=Round(Loc(file)/Lof(file)*100, #PB_Round_Nearest)
							If elapsed ; to avoid division by zero
								speed=Round(Loc(file)/elapsed, #PB_Round_Nearest)
								If speed ; prevent division by zero
									remaining=Round((Lof(file)-Loc(file))/speed, #PB_Round_Nearest)
								EndIf
							EndIf
						EndIf
						If CallFunctionFast(*callback, file$, progress, speed, remaining, elapsed)
							Break
						EndIf
						refresh=ElapsedMilliseconds()+callCallbackEvery
					EndIf
				Wend
				hash=FinishFingerprint(fingerprint)
			EndIf
			FreeMemory(*buffer)
		EndIf
		CloseFile(file)
	EndIf
	ProcedureReturn hash
EndProcedure



  If OpenWindow(0, 0, 0, 400, 160, GetFilePart(#file)+" "+bytecalc(FileSize(#file), 1), #PB_Window_SystemMenu | #PB_Window_ScreenCentered) And CreateGadgetList(WindowID(0))
    ProgressBarGadget(0,  10, 30, 250,  30, 0, 100)
    TextGadget       (3,  10, 10, 250,  20, "")
    TextGadget       (4,  10, 70, 250,  20, "")
    TextGadget       (5, 100,110, 200,  50, "")
    Define md5$=MD5FileFingerprintCallback(#file, @myCallback(), 250)
   MessageRequester("MD5FileFingerprintCallback()", "finished! has code:"+#CRLF$+md5$)
  EndIf
file$
Die Datei, von der der MD5 Hash ermittelt werden soll.

*callback
Adresse des Callbacks. Wenn *callback=0, dann wird die Callbackfunktion deaktiviert. Das Callback muss folgendes Format haben:
myCallback(file.s, progress.l, speed.l, remainingTime.l, elapsedTime.l)
Der Callback kann einen Wert ungleich null zurückgeben, um die Operation abzubrechen.

callCallbackEvery
Zeitabstand in Millisekunden, in der der Callback aufgerufen werden soll. Nützlich, um ein Flackern der Gadgets zu verhindern. Ein Flackern kann z. B. dann entstehen, wenn ein Gadget zu schnell upgedated wird.

bufferSize
Größe in Bytes für den angegeben Buffer, der allokiert wird. Ein größerer Buffer kann eine Verkürzung der Bearbeitungszeit zur Folge haben.

Rückgabewert
Es wird der Hashstring zurückgegeben oder ein Leerstring, falls die Operation fehlschlug.



Edit:
habe mein Problem gefunden. Hatte den Fortschritt als Float-Variable an den Callback übergeben; CallFunctionFast() unterstützt aber keine Floats als Rückgabewert oder Parameter. Das heißt: Viel Spaß beim Benutzen der Prozedur!
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Arachnophobia
Beiträge: 57
Registriert: 03.02.2005 05:57
Wohnort: Berlin
Kontaktdaten:

Beitrag von Arachnophobia »

Vielleicht sollte man erwähnen dass dieser Code erst ab v4.20b funktioniert.
Benutzeravatar
HeX0R
Beiträge: 3054
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win11 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2 + 3
Kontaktdaten:

Re: MD5-Hash mit Callback, Fortschritt, verbleibende Zeit, u

Beitrag von HeX0R »

AND51 hat geschrieben:CallFunctionFast() unterstützt aber keine Floats als Rückgabewert oder Parameter. Das heißt: Viel Spaß beim Benutzen der Prozedur!
Prototype, Prototype, Prototype....
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

> Vielleicht sollte man erwähnen dass dieser Code erst ab v4.20b funktioniert
Habe gerade ein Brett vorm Kopf: Woran liegt das denn? An der Konstanten #PB_Round_Nearest? Wenn ja, dann kannst du auch z. B. Int() benutzen, allerdings wird dann immer abgerundet.

> Prototype
Ja, das habe ich natürlich auch gelesen. Aber ich habe absolut 0 Ahnung davon, wie man die benutzt. Ich brauchte sie auch noch nie. In diesem Fall soll der Benutzer ja auch eine Adresse von einer Callbackprozedur angeben; solche dynamisch auszuführenden Prozeduren können doch nur von CallFunctionFast() aufgerufen werden? Oder irre ich mich da?

Vielen Dank für eure Comments!
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Benutzeravatar
Kiffi
Beiträge: 10714
Registriert: 08.09.2004 08:21
Wohnort: Amphibios 9

Beitrag von Kiffi »

AND51 hat geschrieben:Habe gerade ein Brett vorm Kopf: Woran liegt das denn?
ExamineMD5Fingerprint() & Co gibt's erst ab PB4.2.
(siehe auch mein Ursprungsposting) ;-)

Grüße ... Kiffi
a²+b²=mc²
Benutzeravatar
HeX0R
Beiträge: 3054
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win11 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2 + 3
Kontaktdaten:

Beitrag von HeX0R »

Du definierst einfach deinen Prototyp:

Code: Alles auswählen

Prototype.l myCallback(file.s, progress.l, speed.l, remainingTime.l, elapsedTime.l)
Änderst deinen Prozedurkopf wie folgt:

Code: Alles auswählen

Procedure.s MD5FileFingerprintCallback(file$, callback.myCallback=0, callCallbackEvery=1000, bufferSize=4096)
und den Aufruf änderst du in:

Code: Alles auswählen

callback(file$, progress, speed, remaining, elapsed)
[Edit]
Ach ja, in der If noch das Sternchen vor callback weg.

Thats it.
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

Hallo!

@ Kiffi:
Ach ja, stimmt...! Aber ist ja nicht schlimm, irgendwann ist sowieso die 4.20 aktuell und dann können auch die die Prozedur benutzen, die bisher nicht die BETA 2 verwenden,

@ HeXOR:
Vielen dank für deine Instruktionen! :allright: Leider kann ich sie erst umsetzen, wenn ich meinen PC wiederhabe.

Was macht die Prototype-Zeile? Erstellt sie sowas wie eine Struktur? Kann ich mir die Variable 'callback.myCallback' dann als eine Art strukturierte Variable vorstellen?
So wie dein Beispiel aussieht, scheinen Prototypen doch nicht so kniffelig zu sein, wie ich immer dachte...
Gibt es zum Thema Prototypen irgendwo noch mehr Informationen/Tutorials als nur das, was in der Hilfe steht?
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8812
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Beitrag von NicTheQuick »

Hier der Code mit Prototype für alle, die es nicht erwarten können. :wink:

Code: Alles auswählen

#file="K:\test"



Procedure.s bytecalc(byte.q, NbDecimals.l=0)
   If byte < 1024
      If byte < 0
         ProcedureReturn "0 Byte"
      EndIf
      ProcedureReturn Str(byte)+" Byte"
   ElseIf byte >= 1<<60
      ProcedureReturn StrD(byte/1<<60, NbDecimals)+" EB"
   ElseIf byte >= 1<<50
      ProcedureReturn StrD(byte/1<<50, NbDecimals)+" PB"
   ElseIf byte >= 1<<40
      ProcedureReturn StrD(byte/1<<40, NbDecimals)+" TB"
   ElseIf byte >= 1<<30
      ProcedureReturn StrD(byte/1<<30, NbDecimals)+" GB"
   ElseIf byte >= 1<<20
      ProcedureReturn StrD(byte/1<<20, NbDecimals)+" MB"
   Else
      ProcedureReturn StrD(byte/1024, NbDecimals)+" KB"
   EndIf
EndProcedure


Procedure myCallback(file.s, progress.l, speed.l, remainingTime.l, elapsedTime.l)
   SetGadgetText(3, "Entire progress: "+Str(progress)+"%")
   SetGadgetText(4, "Speed: "+bytecalc(speed)+"/sec ("+Str(speed)+" Byte/s)")
   SetGadgetText(5, FormatDate("Elapsed: %hhh%iim%sss", elapsedTime)+#CRLF$+FormatDate("Remaining: %hhh%iim%sss", remainingTime))
   SetGadgetState(0, Int(progress))
   While WindowEvent() : Wend
   ProcedureReturn 0 ; Return 0 to continue, 1 to abort
EndProcedure

Prototype MD5Callback(file.s, progress.l, speed.l, remainingTime.l, elapsedTime.l)
Procedure.s MD5FileFingerprintCallback(File$, *callback.MD5Callback=0, callCallbackEvery=1000, bufferSize=4096)
   ; Callback format: myCallback(file.s, progress.l, speed.l, remainingTime.l, elapsedTime.l)
   Protected StartTime.l=Date(), file.l=ReadFile(#PB_Any, File$), Hash.s
   If file
      Protected *buffer=AllocateMemory(bufferSize)
      If *buffer
         Protected fingerprint=ExamineMD5Fingerprint(#PB_Any)
         If fingerprint
            Protected progress.l, refresh.l, elapsed.l, speed.l, remaining.l
            While Not Eof(file)
               NextFingerprint(fingerprint, *buffer, ReadData(file, *buffer, bufferSize))
               If *callback And ElapsedMilliseconds() > refresh
                  If Lof(file) ; to avoid division by zero if filesize is 0 bytes
                     elapsed=Date()-StartTime
                     progress=Round(Loc(file)/Lof(file)*100, #PB_Round_Nearest)
                     If elapsed ; to avoid division by zero
                        speed=Round(Loc(file)/elapsed, #PB_Round_Nearest)
                        If speed ; prevent division by zero
                           remaining=Round((Lof(file)-Loc(file))/speed, #PB_Round_Nearest)
                        EndIf
                     EndIf
                  EndIf
                  ;If CallFunctionFast(*callback, File$, progress, speed, remaining, elapsed)
                  If *callback(File$, progress, speed, remaining, elapsed)
                     Break
                  EndIf
                  refresh=ElapsedMilliseconds()+callCallbackEvery
               EndIf
            Wend
            Hash=FinishFingerprint(fingerprint)
         EndIf
         FreeMemory(*buffer)
      EndIf
      CloseFile(file)
   EndIf
   ProcedureReturn Hash
EndProcedure



  If OpenWindow(0, 0, 0, 400, 160, GetFilePart(#file)+" "+bytecalc(FileSize(#file), 1), #PB_Window_SystemMenu | #PB_Window_ScreenCentered) And CreateGadgetList(WindowID(0))
    ProgressBarGadget(0,  10, 30, 250,  30, 0, 100)
    TextGadget       (3,  10, 10, 250,  20, "")
    TextGadget       (4,  10, 70, 250,  20, "")
    TextGadget       (5, 100,110, 200,  50, "")
    Define md5$=MD5FileFingerprintCallback(#file, @myCallback(), 250)
   MessageRequester("MD5FileFingerprintCallback()", "finished! has code:"+#CRLF$+md5$)
  EndIf
Aber wo war da jetzt ein Float?

///Edit:
Irgendwas läuft da noch nicht so richtig:
Bild

Meine Datei: 10.079.961.088 Bytes
Benutzeravatar
PMV
Beiträge: 2765
Registriert: 29.08.2004 13:59
Wohnort: Baden-Württemberg

Beitrag von PMV »

AND51 hat geschrieben:Kann ich mir die Variable 'callback.myCallback' dann als eine Art strukturierte Variable vorstellen?
Ja ... irgend wie schon, es ist zwar (wie ne strukturierte Variable) ein
Pointer auf eine Speicheradresse, aber die "Struktur" definiert eine
Prozedur und kein Strukturelemente an sich.

Es ist dann praktisch eine Pointervariable, die du wie ein Prozedurname
verwenden kannst.

MFG PMV
alte Projekte:
TSE, CWL, Chatsystem, GameMaker, AI-Game DLL, Fileparser, usw. -.-
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

> Hier der Code mit Prototype für alle, die es nicht erwarten können
Vielen Dank!
Sobal ich meinen PC wiederhabe, kann ich den Code auch an meinem Rechner nachvollziehen und werde ihn in das erste Posting setzen.

> Aber wo war da jetzt ein Float?
Den hatte ich wieder rausgenommen, weil das doch nicht mt CallFunctionFast() harmonierte. Ich hatte ursprünglich vor, dem Programmierer zu ermöglichen, den Fortschritt mit Nachkommastellen darstellen zu können, was ja gerade bei großen Dateien vorteilhaft ist.
Solange ich das posting noch nicht updaten kann, wird es noch ein Long bleiben müssen.

> Irgendwas läuft da noch nicht so richtig
Da sagst du mir nichts neues. :-|
Bei mir zeigt der auch Mist an, wenn ich es mit meiner 9,1 GB Datei probiere. Bei einer anderen Datei (1,91 GB) klappt aber alles. Ich weiß einfach nicht woran es liegt. Und da ich im augenblick nicht an meinen PC kann, kann ich leider das Problem nicht weiter berabeiten. Ich hatte erst Vermutungen, dass Loc() oder Lof() nicht mit Quads umgehen können (wenn die Datei größer als 2 GB ist und damit Long-Grenzen überschreitet). Aber das ist offenbar nicht das Problem. Sobald ich kann,w erde ich aber nochmal nachhaken.

> Es ist dann praktisch eine Pointervariable, die du wie ein Prozedurname
verwenden kannst.
Cool! Vielen Dank für die Info! Kann ich die Prototyp-Zeile der Übersichtlichkeit halber auch in die Prozedur mit reinpacken?
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Antworten