GetMax - BlitzMax Downloader

Miscellaneous Forums/Blitz Showcase/GetMax - BlitzMax Downloader

GetMax does what its name says: Gets the latest (SVN) version of BlitzMax for you.

Upsides: has a simple GUI, does NOT require an SVN client and is small.

Downsides: rather slow, does not update and has no error handling.

Source available below.



Updated with SebHoll's changes.

Source:
SuperStrict

Framework MaxGUI.Drivers
Import BRL.EventQueue

Import BRL.FileSystem
Import BRL.SocketStream

Import Bah.Base64

AppTitle = "Get BlitzMax"

Function CreateHttpStream:TSocketStream( server:String, port:Int, file:String = "/", auth:String = "" )
	Local stream:TSocketStream=TSocketStream.CreateClient( server, port )
	If Not stream Return Null
	
	stream.WriteLine "GET "+file+" HTTP/1.0"
	stream.WriteLine "Host: "+server
	stream.WriteLine "Authorization: Basic "+auth
	stream.WriteLine ""
	
	Return stream
End Function

Function DLDir(base:String, dir:String, auth:String, text:Int(t:String))
	Local stream:TSocketStream = CreateHttpStream("blitzbasic.com", 81, base+dir, auth)
	PollSystem()
	
	While stream And Not stream.Eof()
		If stream.ReadLine()="" Exit
	Wend
	
	If stream And Not stream.Eof()
		If Not CreateDir( dir, True ) Return
	End If
	
	While stream And Not stream.Eof()
		Local l:String = stream.ReadLine().Trim()
		If l.StartsWith("<li><a")
			Local href:String = l.Split("~q")[1].Replace("%20", " ")
			If href.EndsWith("/")
				If href<>"../" Then DLDir base, dir+href, auth, text
			Else
				DLFile base, dir, href, auth, text
			End If
		End If
	Wend
End Function

Function DLFile(base:String, dir:String, file:String, auth:String, text:Int(t:String))
	If FileType(dir+file) Return
	PollSystem()
	
	Local stream:TSocketStream = CreateHttpStream("blitzbasic.com", 81, base+dir+file, auth)
	Local length%
	
	While stream And Not stream.Eof()
		Local l:String = stream.ReadLine()
		If l.StartsWith("Content-Length") Then length = Int(l.Split(" ")[1])
		If l="" Exit
	Wend
	
	Local out:TStream
	If stream And Not stream.Eof()
		out = WriteFile(dir+file)
		If Not out Return
		text String(dir+file).Replace("%20"," ") + " ("+Int((length/1000.0)+1)+"KB)"
	End If
	
	Local data:Byte[]
	
	While stream And Not stream.Eof()
		Local tmpBytes:Byte[SocketReadAvail(SocketStreamSocket(stream))]
		stream.ReadBytes( tmpBytes, tmpBytes.length )
		data:+tmpBytes
		PollSystem()
	Wend
	
	Assert data.length = length, "File length mismatch!"
	
	If out Then 
		If data Then out.WriteBytes data, data.length
		out.close()
	EndIf
	
End Function

Local win:TGadget = CreateWindow(AppTitle, 100, 100, 400, 300, Null, WINDOW_TITLEBAR|WINDOW_CLIENTCOORDS )

'User details

Local ulabel:TGadget = CreateLabel("Username: ", 10,15,70,25, win)
Local user:TGadget = CreateTextField(80,10,110,25, win)

Local plabel:TGadget = CreateLabel("Password: ", 210,15,70,25, win)
Local pass:TGadget = CreateTextField(280,10,110,25, win, TEXTFIELD_PASSWORD)

'Directory
Local dlabel:TGadget = CreateLabel("Target: ", 10,50,70,25, win)
Local dir:TGadget = CreateTextField(80,45,220,25, win)
Local dirget:TGadget = CreateButton("Browse...", 310,45,80,25, win)

'Platforms

Local blabel:TGadget = CreateLabel("Platform: ", 10,85,70,25, win)
Local binary:TGadget = CreateComboBox(80,80,110,25, win)
AddGadgetItem binary, "Windows (x86)", GADGETITEM_DEFAULT, -1, "", "win32_x86"
AddGadgetItem binary, "Linux (x86)", 0, -1, "", "linux_x86"
AddGadgetItem binary, "Mac OS X (x86)", 0, -1, "", "macos_x86"
AddGadgetItem binary, "Mac OS X (PPC)", 0, -1, "", "macos_ppc"

'MaxGUI?

Local guimod:TGadget = CreateButton("Get MaxGUI", 200,80,110,25, win, BUTTON_CHECKBOX)

'Start

Local start:TGadget = CreateButton("Start", 310,80,80,25, win)
DisableGadget start

'Output Display

Global outputdisp:TGadget = CreateListBox(10,115,380,175, win)


ActivateGadget user

Repeat
	Select WaitEvent()
	Case EVENT_GADGETACTION
		Select EventSource()
			Case start
				Exit
			Case dirget
				SetGadgetText dir, RequestDir("Choose a new BlitzMax directory...")
		EndSelect
		If GadgetText(user) And GadgetText(pass) And GadgetText(dir) Then EnableGadget(start) Else DisableGadget(start)
	Case EVENT_WINDOWCLOSE
		End
	End Select
Forever 

DisableGadget dir
DisableGadget dirget
DisableGadget user
DisableGadget pass
DisableGadget binary
DisableGadget guimod
DisableGadget start

EnableGadget outputdisp

Function ShowText:Int(s:String)
	If s Then AddGadgetItem outputdisp,s,GADGETITEM_DEFAULT
End Function

If Not FileType(GadgetText(dir)) Then CreateDir(GadgetText(dir), True)

If Not ChangeDir(GadgetText(dir))
	Notify "Unable to create directory: "+GadgetText(dir), True
	End
End If

If Not user Or Not pass
	Notify "Username/password required!", True
	End
End If

Local auth:String = GadgetText(user)+":"+GadgetText(pass)
Local c:Byte Ptr = auth.ToCString()
auth = TBase64.Encode(c, auth.length)
MemFree c

DLDir "/svn/blitzmax/dev/main/", "", auth, ShowText
DLDir "/svn/blitzmax/dev/"+String(GadgetItemExtra(binary, SelectedGadgetItem(binary)))+"/", "", auth, ShowText

If ButtonState(guimod)
	If Not FileType(GadgetText(dir)+"/mod/maxgui.mod") Then CreateDir(GadgetText(dir)+"/mod/maxgui.mod", True)
	
	If Not ChangeDir(GadgetText(dir)+"/mod/maxgui.mod")
		Notify "Unable to create directory: "+GadgetText(dir)+"/mod/maxgui.mod", True
		End
	End If
	
	DLDir "/svn/maxgui/dev/maxgui.mod/", "", auth, ShowText
End If

Notify "All done!", False


No offense, but based on the latest issue with EXEs, you might want to release the source now, as I doubt many will touch an EXE right now.

I get your point. EDIT: Source released. Messy but works.

Should be a useful utility for folks ;)

Sounds great.

Would be awesome if such a downloader would be included in the IDE.

Thanks a ton!

Would be awesome if such a downloader would be included in the IDE.

Except of course you'll need to have MinGW installed to use the source.
And it's a lot to download each time you want to check if something has been updated.

But otherwise, it does usefully fill a gap :-)

Nice work!

I've tweaked the UI slightly and swapped the byte-by-byte stream code for the much the faster ReadBytes()/WriteBytes() alternatives:

Otus, feel free to update your original post if you approve.

SuperStrict

Framework MaxGUI.Drivers
Import BRL.EventQueue

Import BRL.FileSystem
Import BRL.SocketStream

Import Bah.Base64

AppTitle = "Get BlitzMax"

Function CreateHttpStream:TSocketStream( server:String, port:Int, file:String = "/", user:String = "", pass:String = "" )
	Local stream:TSocketStream=TSocketStream.CreateClient( server, port )
	If Not stream Return Null
	
	stream.WriteLine "GET "+file+" HTTP/1.0"
	stream.WriteLine "Host: "+server
	
	If user And pass
		Local auth:String = user+":"+pass
		Local c:Byte Ptr = auth.ToCString()
		
		auth = TBase64.Encode(c, auth.length)
		
		stream.WriteLine "Authorization: Basic "+auth
		MemFree c
	End If
	
	stream.WriteLine ""
	
	Return stream
End Function

Function CreateHttpHeadStream:TSocketStream( server:String, port:Int, file:String = "/", user:String = "", pass:String = "" )
	Local stream:TSocketStream=TSocketStream.CreateClient( server, port )
	If Not stream Return Null
	
	stream.WriteLine "HEAD "+file+" HTTP/1.0"
	stream.WriteLine "Host: "+server
	
	If user And pass
		Local auth:String = user+":"+pass
		Local c:Byte Ptr = auth.ToCString()
		
		auth = TBase64.Encode(c, auth.length)
		
		stream.WriteLine "Authorization: Basic "+auth
		MemFree c
	End If
	
	stream.WriteLine ""
	
	Return stream
End Function


Function DLDir(base:String, dir:String, user:String, pass:String, text:Int(t:String))
	Local stream:TSocketStream = CreateHttpStream("blitzbasic.com", 81, base+dir, user, pass)
	PollSystem()
	
	While stream And Not stream.Eof()
		If stream.ReadLine()="" Exit
	Wend
	
	If stream And Not stream.Eof()
		If Not CreateDir( dir, True ) Return
		text dir.Replace("%20", " ")
	End If
	
	While stream And Not stream.Eof()
		Local l:String = stream.ReadLine().Trim()
		If l.StartsWith("<li><a")
			Local href:String = l.Split("~q")[1]
			If href.EndsWith("/")
				If href<>"../" Then DLDir base, dir+href, user, pass, text
			Else
				DLFile base, dir, href, user, pass, text
			End If
		End If
	Wend
End Function

Function DLFile(base:String, dir:String, file:String, user:String, pass:String, text:Int(t:String))
	If FileType(dir+file) Return
	PollSystem()
	
	Local stream:TSocketStream = CreateHttpHeadStream("blitzbasic.com", 81, base+dir+file, user, pass)
	Local length:Int
	
	While stream And Not stream.Eof()
		Local l:String = stream.ReadLine()
		If l.StartsWith("Content-Length") Then length = Int(l.Split(" ")[1])
		If l="" Exit
	Wend
	
	If FileSize(dir+file) = length Return
	
	stream = CreateHttpStream("blitzbasic.com", 81, base+dir+file, user, pass)
	
	While stream And Not stream.Eof()
		Local l:String = stream.ReadLine()
		If l="" Exit
	Wend
	
	Local out:TStream
	If stream And Not stream.Eof()
		out = WriteFile(dir+file)
		If Not out Return
		text String(dir+file).Replace("%20"," ") + " ("+Int((length/1000.0)+1)+"KB)"
	End If
	
	Local data:Byte[]
	
	While stream And Not stream.Eof()
		Local tmpBytes:Byte[SocketReadAvail(SocketStreamSocket(stream))]
		stream.ReadBytes( tmpBytes, tmpBytes.length )
		data:+tmpBytes
		PollSystem()
	Wend
	
	Assert data.length = length, "File length mismatch!"
	
	If out Then 
		If data Then out.WriteBytes data, data.length
		out.close()
	EndIf
	
End Function

Local win:TGadget = CreateWindow(AppTitle, 100, 100, 400, 300, Null, WINDOW_TITLEBAR|WINDOW_CLIENTCOORDS )

'User details

Local ulabel:TGadget = CreateLabel("Username: ", 10,15,70,25, win)
Local user:TGadget = CreateTextField(80,10,110,25, win)

Local plabel:TGadget = CreateLabel("Password: ", 210,15,70,25, win)
Local pass:TGadget = CreateTextField(280,10,110,25, win, TEXTFIELD_PASSWORD)

'Directory
Local dlabel:TGadget = CreateLabel("Target: ", 10,50,70,25, win)
Local dir:TGadget = CreateTextField(80,45,220,25, win)
Local dirget:TGadget = CreateButton("Browse...", 310,45,80,25, win)

'Platforms

Local blabel:TGadget = CreateLabel("Platform: ", 10,85,70,25, win)
Local binary:TGadget = CreateComboBox(80,80,110,25, win)
AddGadgetItem binary, "Windows (x86)", GADGETITEM_DEFAULT, -1, "", "win32_x86"
AddGadgetItem binary, "Linux (x86)", 0, -1, "", "linux_x86"
AddGadgetItem binary, "Mac OS X (x86)", 0, -1, "", "macos_x86"
AddGadgetItem binary, "Mac OS X (PPC)", 0, -1, "", "macos_ppc"

'MaxGUI?

Local guimod:TGadget = CreateButton("Get MaxGUI", 200,80,110,25, win, BUTTON_CHECKBOX)

'Start

Local start:TGadget = CreateButton("Start", 310,80,80,25, win)
DisableGadget start

'Output Display

Global outputdisp:TGadget = CreateListBox(10,115,380,175, win)


ActivateGadget user

Repeat
	Select WaitEvent()
	Case EVENT_GADGETACTION
		Select EventSource()
			Case start
				Exit
			Case dirget
				SetGadgetText dir, RequestDir("Choose a new BlitzMax directory...")
		EndSelect
		If GadgetText(user) And GadgetText(pass) And GadgetText(dir) Then EnableGadget(start) Else DisableGadget(start)
	Case EVENT_WINDOWCLOSE
		End
	End Select
Forever 

DisableGadget dir
DisableGadget dirget
DisableGadget user
DisableGadget pass
DisableGadget binary
DisableGadget guimod
DisableGadget start

EnableGadget outputdisp

Function ShowText:Int(s:String)
	If s Then AddGadgetItem outputdisp,s,GADGETITEM_DEFAULT
End Function

If Not FileType(GadgetText(dir)) Then CreateDir(GadgetText(dir), True)

If Not ChangeDir(GadgetText(dir))
	Notify "Unable to create directory: "+GadgetText(dir), True
	End
End If

DLDir "/svn/blitzmax/dev/main/", "", GadgetText(user), GadgetText(pass), ShowText
DLDir "/svn/blitzmax/dev/"+String(GadgetItemExtra(binary, SelectedGadgetItem(binary)))+"/", "", GadgetText(user), GadgetText(pass), ShowText

If ButtonState(guimod)
	If Not FileType(GadgetText(dir)+"/mod/maxgui.mod") Then CreateDir(GadgetText(dir)+"/mod/maxgui.mod", True)
	
	If Not ChangeDir(GadgetText(dir)+"/mod/maxgui.mod")
		Notify "Unable to create directory: "+GadgetText(dir)+"/mod/maxgui.mod", True
		End
	End If
	
	DLDir "/svn/maxgui/dev/maxgui.mod/", "", GadgetText(user), GadgetText(pass), ShowText
End If

Notify "All done!", False


Nice work!

I've tweaked the UI slightly and swapped the byte-by-byte stream code for the much the faster ReadBytes()/WriteBytes() alternatives:

Otus, feel free to update your original post if you approve.


Thanks, I updated with your changes and a couple of other issues.

Nice work. Makes life a little easier than using SVN all we need now for it to do is update only the new or changed files. I will test this on the Mac and Ubuntu.

Edit: Ubuntu's ok. But for some reason it will only work on a Mac within the IDE in debug any ideas? (I'm not a big Mac OS X user)