Download File

Monkey Forums/Monkey Code/Download File

Pharmhaus(Posted 2014) [#1]
I tried to use both streams and httprequest to download a file but they both got kinda stuck in the process or misinterpreted the file length resulting in corrupted files.
maybe it's just me, but it all felt so unintuitive that i had to fix things a little bit.
anyway, if someone else is looking for a way to download files, here it is :)




EdzUp(Posted 2014) [#2]
excellent just what I was looking for :D


nikoniko(Posted 2014) [#3]
I am sorry - where is update() method?


Pharmhaus(Posted 2014) [#4]
It uses the async socket methods and is updated by calling UpdateAsyncEvents() in you main loop (wont work without).
This has the advantage that you application can do other things while donwloading (Reading the header is not async but shouldnt cause much of an overhead).
You can detect changes in progress by providing a "onUpdate:IOnDownloadUpdate" for the "New" method.
In the above example this is the HappyApp itself. Whenever a progress in download has been made the "DownloadUpdate" Method of HappyApp is called automatically.
This is the mainloop in the above example:
While D.Finished = False
	UpdateAsyncEvents()
Wend



nikoniko(Posted 2014) [#5]
I see, you don't use mojo

Your code is working if change path to store file

		Local s:FileStream = FileStream.Open("monkey://data/test.gif", "w")



EdzUp(Posted 2014) [#6]
I made some changes to the code for a project I am working on, seeing as levels are gonna be a few thousand bytes it works fine :)

Heres the code:
'
'	Updater.monkey - Originally coded by Pharmhaus, modified by EdzUp
'	Programmed by Pharmhaus, modified by: Ed 'EdzUp' Upton
'

#If TARGET<>"glfw" And TARGET<>"android" And TARGET<>"ios" And TARGET<>"stdcpp"
	#Error "Invalid target!"
#Endif

Strict

Import brl.filestream
Import brl.tcpstream

Class UpdaterClass Implements IOnDownloadUpdate
	Method Check:String( url:String )		
		Local D:= New AsyncDownload( url, Self, 1024 ) ' Valid link
		
		While D.Finished = False
			UpdateAsyncEvents()
		Wend
		
		Print "finished... writing file"
		
		Local buf:DataBuffer = D.File
		
		If buf Then
			Return( buf.PeekString( 0 ) )
		Endif		
		
		Return( "" )
	End	

	Method Download:Void( url:String, targetfile:String )		
		Local D:= New AsyncDownload( url, Self, 1024 ) ' Valid link
		
		While D.Finished = False
			UpdateAsyncEvents()
		Wend
		
		Print "finished... writing file"
		
		Local buf:DataBuffer = D.File
		
		If buf Then		
			Local s:FileStream = FileStream.Open( targetfile, "w" )
			s.WriteAll(buf, 0, buf.Length)
			s.Close()
		Endif
		
	End	

	Method DownloadUpdate:Void( D:AsyncDownload )
		Print "Totalsize:" + D.Size
		Print "Percentage: " + D.Percentage + "%"
		Print "Remaining Bytes: " + D.Remaining
	
		If D.Finished
			Print "Download completed!"
		Endif
		
		If D.Alive = False Then
			Print "Download failed..."
		Endif
		
		If D.Remaining = 0
			Print "No bytes remaining"
		Endif
		
		Print "~r~n"
	End
End

Global Update:UpdaterClass = New UpdaterClass

Interface IOnDownloadUpdate
	Method DownloadUpdate:Void(D:AsyncDownload)
End

Class AsyncDownload Implements IOnReceiveComplete, IAsyncEventSource
	Private
	
	'///// Dataconnection
	Field socket:Socket
	Field stream:TcpStream
	Field Buffer:DataBuffer
	Field chunksize:Int		' how much to download per round
	
	'///// Download Info
	Field host:String		' name of the host
	Field file:String		' path of the file
	Field filesize:Int		' number of bytes to download
	Field downloaded:Int 	' Number of bytes already download
	
	'///// Callback
	Field Callback:IOnDownloadUpdate
	
	Public
	
	Method UpdateAsyncEvents:Void()
		RemoveAsyncEventSource(Self)
		If Callback Then Callback.DownloadUpdate(Self)
		If filesize = -1 Then
			filesize = 0
		EndIf
	End Method

	
	Method New(url:String, onUpdate:IOnDownloadUpdate, chunksize:int = 1024)
		'Fix the URL so that everything will work properly
		url = url.Replace("http://", "")
		Local in:String = "."
		Local slashPos:Int = url.Find("/")
		
		'Initialize Connection
		socket = New Socket
		stream = New TcpStream(socket)
		Self.chunksize = chunksize
		Callback = onUpdate
		
		'seperate the file from the address
		If slashPos <> - 1
			host = url[0 .. slashPos]
			file = url[slashPos ..]
		Else
			filesize = -1
			stream.Close()
			socket = Null
			AddAsyncEventSource(Self)
			Return
		EndIf
		
		'connect to the address
	    If not (stream.Connect(host, 80)) Then
			filesize = -1
			stream.Close()
			socket = Null
			AddAsyncEventSource(Self)
			Return
		Endif
		
		'Send a request to Download the File....
		stream.WriteLine "GET " + file + " HTTP/1.0"
	    stream.WriteLine "Host: " + host
	    stream.WriteLine ""
		
		'Wait until the Server sends us all the information about the file, like size
		While in <> ""
			in = stream.ReadLine()
			If in.Find("Content-Length:") <> - 1 Then
				filesize = Int(in[15 ..].Trim())
			EndIf
		Wend
		
		Buffer = New DataBuffer(filesize)
		
		'Start the async part...
		DownloadChunk()
	End
	
	Method DownloadChunk:Void()
		If (filesize - downloaded) > chunksize Then
			socket.ReceiveAllAsync(Buffer, downloaded, chunksize, Self)
		Else
			socket.ReceiveAllAsync(Buffer, downloaded, (filesize - downloaded), Self)
		Endif
	End
	
	Method OnReceiveComplete:Void(data:DataBuffer, offset:Int, count:Int, source:Socket)
		downloaded += count
		If Callback Then Callback.DownloadUpdate(Self)
		If downloaded < filesize Then
			DownloadChunk()
		Else
			stream.Close()
		Endif
	End
	
	Method Size:Int() Property
		Return filesize
	End
	
	Method Remaining:Int() Property
		Return (filesize - downloaded)
	End
	
	Method Percentage:Int() Property
		If filesize <= 0 Then
			Return 0
		Else
			Return Float(downloaded) / Float(filesize) * 100
		Endif
	End
	
	Method Finished:Bool() Property
		Return (filesize = downloaded)
	End
	
	Method File:DataBuffer() Property
		Return Buffer
	End
	
	Method Alive:Bool() Property
		Return (socket <> Null)
	End
End


Basically you can use:
Update.Check( url ) to return a string which contains a version number for example on my website I have version.txt that holds 0.1b(alpha) this is returned by the function
If its different from the game I can call Update.Download( url, target file) to download the files :)

All of this can be added by simply just including the file as a import and calling the Update methods :).

I would like to thank Pharmhaus for the original code with a little adjustment it works perfectly for what I want it for :)