﻿B4A=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=13.4
@EndOfDesignText@
'===============================================================================================================================
'	I'm Robert Valentino - been on B4X for some time.  
'		Getting ready to turn 75 so if my code isn't up to young guys specs show me a better way and I will convert
'		SORRY to those that don't like code lined up.  I cannot look at code and understand it unless it is.
'			Think it is something to do with my old days of BAL
'
'	To use this class you must register with Koofr (they give 10 gig free storage or the storage you paid for)
'
'	Once you are registered with them do the following to get a App Password
'		1) At you Koofr website Click on your Icon in top right corner and select Preferences
'		2) Your Email Address will be there you are going to need that as your UserName
'		3) Click Password and this will show you where you can change your password AND create App Passwords
'		4) Type in a App name (whatever you want to call it) and Generate
'		5) Koofr will create a password for you and show it to you.  
'			NOTE: 	This is the only time you will have to SEE the password otherwise you wil have to recreate one
'			
'		Your Email and Password are need to be authorized to use these functions
'
'	The DEFAULT Path for everyone is 	"https:/app.koofr.net/dav/Koofr/"		
'
'	Enjoy
'===============================================================================================================================
Sub Class_Globals	
	Type ListFileItem(Name As String, Href As String, IsDir As Boolean, LastModified As Long)
	
    Private BasicAuthorized 			As String 
	
	Private StringUtils					As StringUtils
	Private SaxParser 					As SaxParser	
	
	Private ListFileItems 				As List
	Private ListFilesPending 			As ListFileItem
	
	Private ListFilesInResponse 		As Boolean
	Private ListFilesSeenCollection		As Boolean

	Private ListFiles_DirectoriesOnly	As Boolean
	Private ListFiles_FilesOnly			As Boolean
		
	Private ListFiles_SearchingFor		As String
	
	Private mLastHTTPCode				As Int
End Sub

'----------------------------------------------------------------------------------
'	Need to Pass the UserName 	(is your Email Address that you registered with
'					 UserPWD	(is the Password you got from Koofr App Passwords
'----------------------------------------------------------------------------------
Public 	Sub Initialize(xUserName As String, xUserPWD As String)
			
			SaxParser.Initialize
			
    		BasicAuthorized = $"Basic ${StringUtils.EncodeBase64($"${xUserName}:${xUserPWD}"$.As(String).GetBytes("UTF8")}"$)	
End Sub

#Region CreateCopy
'---------------------------------------------------------------------------------------
'	CreateCopy of List
'		ListToCopy	- A list to copy
'
'		Returns		- A new copy of the list
'---------------------------------------------------------------------------------------
Private Sub CreateCopy(xListToCopy As List) As List
			Dim NewList As List
			
			NewList.Initialize
			NewList.AddAll(xListToCopy)
			
			Return NewList
End Sub
#end region

#Region CreateDirectory
'-----------------------------------------------------------------------------------------------------------
'	CreateDirectory 
'		DirUrl 		- is the complete directory path
'
'		Returns		- Boolean True = Success
'-----------------------------------------------------------------------------------------------------------
Public  Sub CreateDirectory(DirUrl As String) As ResumableSub
			
			Dim Success As Boolean
			
			Dim j 		As HttpJob
			
			j.Initialize("", Me)
			
			j.Download(DirUrl &"/")
			
			j.GetRequest.As(JavaObject).GetFieldJO("builder").RunMethod("method", Array("MKCOL", Null))
			j.GetRequest.SetHeader("Authorization", BasicAuthorized)
		
			Wait For (j) JobDone(j As HttpJob)
			
			Success   		= j.Success
    		mLastHTTPCode 	= j.Response.StatusCode
			
    		Log("CreateDirectory RC: " &mLastHTTPCode)
						
			If 	j.Success Then
			    Log(j.GetString)
			Else
    			Log(j.ErrorMessage)
			End If
			
			j.Release		
			
    		Return Success
End Sub
#end Region

#Region DeleteItem
'-----------------------------------------------------------------------------------------------------------
'	DeleteItem - File or Directory (Directory MUST be Empty)
'		DirFileUrl	- is the complete file path
'
'		Returns		- Boolean True = Success
'-----------------------------------------------------------------------------------------------------------
Public  Sub DeleteItem(DirFileUrl As String) As ResumableSub
    		' DirUrl = full WebDAV path (file or folder)
    		' Returns 0 if success, -1 if failed
			
			Dim Success As Boolean
			
    		Dim j 		As HttpJob
			
    		j.Initialize("", Me)

    		j.Download(DirFileUrl)
			j.GetRequest.As(JavaObject).GetFieldJO("builder").RunMethod("method", Array("DELETE", Null))			
			j.GetRequest.SetHeader("Authorization", BasicAuthorized)

    		Wait For (j) JobDone(j As HttpJob)
			
    		mLastHTTPCode 	= j.Response.StatusCode
			Success   		= j.Success
			
    		Log("DeleteItem HTTP RC: " &mLastHTTPCode)
			
    		If 	j.Success Then
        		Log("Deleted OK: " & DirFileUrl)
    		Else
        		Log("Delete failed: " & DirFileUrl & " | " & j.ErrorMessage)
			End If
			
        	j.Release
        	
			Return Success
End Sub
#end Region

#Region DownloadFile
'-----------------------------------------------------------------------------------------------------------
'	DownloadFile - download a file
'		FileToDownload 	- Complete url to file to retrieve
'		LocalPath		- Path Where to save file
'		LocalFile		- FileName to be saved
'
'		Returns			- Boolean True = Success
'-----------------------------------------------------------------------------------------------------------
Public  Sub DownloadFile(xFileToDownload As String, xLocalPath As String, xLocalFile As String) As ResumableSub
	
			Log($"Downloadfile:${xFileToDownload}  to LocalFile:${xLocalFile}"$)

			Dim Success As Boolean			
	
    		Dim job 	As HttpJob
			
    		job.Initialize("download", Me)
			
    		' remoteHref is the full WebDAV path, e.g. "https://app.koofr.net/dav/Koofr/MyFolder/myfile.txt"
			
    		job.Download(xFileToDownload)
    		job.GetRequest.SetHeader("Authorization", BasicAuthorized)
    		job.Tag = xLocalFile
	
			Wait For (job) JobDone(job As HttpJob)
			
			Success	  		= job.Success
			mLastHTTPCode 	= job.Response.StatusCode
	
    		If  job.Success Then
        		If 	job.JobName = "download" Then
            		Dim out As OutputStream = File.OpenOutput(xLocalPath, job.Tag, False)
					
            		File.Copy2(job.GetInputStream, out)
					
            		out.Close
					
            		Log("Downloaded to: " & File.Combine(xLocalPath, job.Tag))
        		End If
    		Else
        		Log("Error: " & job.ErrorMessage)
    		End If
	
    		job.Release
	
			Return Success
End Sub
#end Region

#Region GetLastCode
'-----------------------------------------------------------------------------------------------------------
'	GetLastCode - returns the last HTTP Code
'-----------------------------------------------------------------------------------------------------------
Public  Sub GetLastHTTPCode As Int
			Return mLastHTTPCode
End Sub
#end region

#Region GetMimeType
'------------------------------------------------------------------------------
'	GetMimeType - From passed file name
'		FileName	- A file name with extension
'
'		Returns		- the mime type if possible
'------------------------------------------------------------------------------
Private Sub GetMimeType(xLocalFile As String) As String
    		Dim ext As String = ""
			
    		Dim i As Int = xLocalFile.LastIndexOf(".")
			
    		If  i > -1 Then 
				ext = xLocalFile.SubString(i + 1).ToLowerCase
			End If

    		Dim jo As JavaObject
			
    		jo.InitializeStatic("android.webkit.MimeTypeMap")
			
    		Dim singleton 	As JavaObject = jo.RunMethod("getSingleton", Null)
    		Dim mime 		As String = singleton.RunMethod("getMimeTypeFromExtension", Array(ext))

    		If mime = Null Then 
				mime = "application/octet-stream"
			End If
			
    		Return mime
End Sub

#end Region

#Region ListFiles and All the sub routines needed
'-----------------------------------------------------------------------------------------------------------
'	ListFiles - list the files in a directory
'		PrimaryPath 	- complete url for where all the files live
'		SearchFor		- directory to add on to PrimaryPath
'		DirectoriesOnly	- True if you just want the directories and not all the other files
'		FilesOnly		- True if you just want the files that are not directories
'
'		Returns	List of files found
'-----------------------------------------------------------------------------------------------------------
Public  Sub ListFiles(xPrimaryPath As String, xSearchFor As String, xDirectoriesOnly As Boolean, xFilesOnly As Boolean) As ResumableSub

			Dim BaseUrl		As String = ""
			
    		' --- Parse result ---
    		SaxParser.Initialize
			
    		ListFileItems.Initialize
			ListFileItems.Clear
			
			If  xSearchFor.Length > 0 Then
				BaseUrl = $"${BaseUrl}${xSearchFor}/"$
			End If
	
			ListFiles_SearchingFor	  = BaseUrl
			ListFiles_DirectoriesOnly = xDirectoriesOnly
			ListFiles_FilesOnly		  = xFilesOnly
	
			Dim ActualUrl	As String = $"${xPrimaryPath}${BaseUrl}"$
    
		    ' --- HttpJob with PROPFIND ---
    		Dim j As HttpJob
    		j.Initialize("", Me)
    
			' --- Define the XML body for PROPFIND ---
			Dim xmlBody As String = "<?xml version=""1.0"" encoding=""utf-8""?>" & _
    								"<D:propfind xmlns:D=""DAV:""><D:allprop/></D:propfind>"	
									
			' --- Create RequestBody (needed by OkHttp) ---
			Dim mt As JavaObject
			
			mt.InitializeStatic("okhttp3.MediaType")
			Dim xmlType As Object = mt.RunMethod("parse", Array("application/xml; charset=utf-8"))
	
			Dim rb As JavaObject
			rb.InitializeStatic("okhttp3.RequestBody")
			Dim body As Object = rb.RunMethod("create", Array(xmlType, xmlBody.GetBytes("UTF8")))

			j.Download(ActualUrl)
			j.GetRequest.As(JavaObject).GetFieldJO("builder").RunMethod("method", Array("PROPFIND", body))
			j.GetRequest.SetHeader("Authorization", BasicAuthorized)
			j.GetRequest.SetHeader("Depth", "1")	
'			j.GetRequest.SetHeader("Depth", "infinity")
	
    		' --- Wait for result ---
    		Wait For (j) JobDone(j As HttpJob)
	
    		mLastHTTPCode = j.Response.StatusCode
			
    		Log("ListFiles HTTP RC: " &mLastHTTPCode)
    
    		If 	j.Success = False Then
        		Log("Error listing files: " & j.ErrorMessage)
        		j.Release
        		Return CreateCopy(ListFileItems)
    		End If
    
    		Dim res As String = j.GetString
			
    		If 	res.Length = 0 Then
        		Log("Empty response")
        		j.Release
        		Return CreateCopy(ListFileItems)
    		End If
    
    
    		Dim BytesToParse() 	As Byte = res.GetBytes("UTF8")
    		Dim WhatToParse		As JavaObject 
			
			WhatToParse.InitializeNewInstance("java.io.ByteArrayInputStream", Array(BytesToParse))
    
    		Try
        		SaxParser.Parse(WhatToParse, "ListFiles_Parser")
    		Catch
        		Log("Parse error: " & LastException)

        		If 	ListFileItems.Size <= 0  Then 
					j. Release
					
					Return CreateCopy(ListFileItems)
				End If
    		End Try

#if DebugX   
		    ' --- Use results ---
    		For Each LFI As ListFileItem In ListFileItems
        		If  LFI.Href.EndsWith("/") = False Then ' skip self if needed
            		Log($"Name=${LFI.Name}, Dir=${LFI.IsDir}, Href=${LFI.Href}  LastModified:${DateTime.Date(LFI.LastModified)} ${DateTime.Time(LFI.LastModified)}"$)
        		End If
    		Next
#end if    
    		j.Release

			Return CreateCopy(ListFileItems)
End Sub

' ===== SAX handlers =====
Private Sub ListFiles_Parser_StartElement(Uri As String, Name As String, Attributes As Attributes)
    		Name = Name.Trim
			
    		If 	Name.EqualsIgnoreCase("response") Then
        		ListFilesInResponse = True
        		' reset fields for the new response
        		ListFilesPending.Name 	= ""
        		ListFilesPending.Href 	= ""
        		ListFilesPending.IsDir 	= False
        		ListFilesSeenCollection = False
    		Else If ListFilesInResponse And Name.EqualsIgnoreCase("collection") Then
        			' <resourcetype><collection/></resourcetype>
        			ListFilesSeenCollection = True
    		End If
End Sub

Private Sub ListFiles_Parser_EndElement(Uri As String, Name As String, Text As StringBuilder)
    		Name = Name.Trim
			
    		Dim t As String = Text.ToString.Trim
			
' 		  Log($"EndElement: ${Name} -> '${t}'"$)

    		If 	ListFilesInResponse Then
        		If 	Name.EqualsIgnoreCase("href") Then
            		ListFilesPending.Href = t
        		Else If Name.EqualsIgnoreCase("displayname") Then
            			ListFilesPending.Name = t
				Else If Name.EqualsIgnoreCase("getlastmodified") Then
            			ListFilesPending.LastModified = Parse_HttpDate(t)
        		Else If Name.EqualsIgnoreCase("response") Then
            			' finished one response — create a NEW DavItem copy and add it
            			ListFilesPending.IsDir = ListFilesSeenCollection
			
            			If  ListFileItems.IsInitialized = False Then 
							ListFileItems.Initialize
						End If

						If  ListFiles_SearchingFor.Length > 0 And ListFilesPending.Href.EndsWith(ListFiles_SearchingFor) Then
	            			' reset flags for next response
    	        			ListFilesInResponse 			= False
        	    			ListFilesSeenCollection 		= False
							ListFilesPending.LastModified 	= 0
	            			ListFilesPending.Name 			= ""
    	        			ListFilesPending.Href 			= ""
        	    			ListFilesPending.IsDir 			= False
							Return	
						End If
			
            			Dim LFI As ListFileItem
						
            			LFI.Name 			= ListFilesPending.Name
            			LFI.Href 			= ListFilesPending.Href
            			LFI.IsDir 			= ListFilesPending.IsDir
						LFI.LastModified 	= ListFilesPending.LastModified
						
						If  (ListFiles_DirectoriesOnly And LFI.IsDir) 							Or _
							(ListFiles_FilesOnly And LFI.IsDir = False) 						Or	_
							(ListFiles_DirectoriesOnly = False And ListFiles_FilesOnly = False) Then
	            			ListFileItems.Add(LFI) 	' add NEW copy
						End If
						
'			            Log($"Added: Name='${p.Name}' Href='${p.Href}' IsDir=${p.IsDir}"$)

			            ' reset flags for next response
            			ListFilesInResponse 			= False
            			ListFilesSeenCollection 		= False
						
						ListFilesPending.LastModified 	= 0
						
            			ListFilesPending.Name 			= ""
            			ListFilesPending.Href 			= ""
            			ListFilesPending.IsDir 			= False
        		End If
    		End If
End Sub
' ===== End SAX handlers =====

Private Sub Parse_HttpDate(httpDate As String) As Long
	
			Dim OldFormat As String = DateTime.DateFormat
			Dim NewFormat As String = "EEE, dd MMM yyyy HH:mm:ss Z"
	
			DateTime.DateFormat = NewFormat
	
			Dim DT As Long = DateTime.DateParse(httpDate) '<- Error in this line
		
			DateTime.DateFormat = OldFormat

    		Return DT
End Sub
#end Region

Public	Sub UploadFile(xLocalPath As String, xLocalFile As String, xFileToUpload As String) As ResumableSub
    
    		Log($"UploadFile: ${File.Combine(xLocalPath, xLocalFile)} to RemoteFile: ${xFileToUpload}"$)

    		Dim Success As Boolean
    
    		Dim job As HttpJob
    
    		job.Initialize("upload", Me)
    
    		' Read the file into a byte array
    		Dim fileBytes() As Byte = File.ReadBytes(xLocalPath, xLocalFile)
    
    		' Set the request method to PUT and post the byte array
    		job.PutBytes(xFileToUpload, fileBytes)

'------------------------------------------------------------------------------------------
'			Could not get PostFile to work, kept getting Not Found and I am not sure 
'				What was Not Found
'
'			job.PostFile(xFileToUpload, xLocalPath, xLocalFile)
'------------------------------------------------------------------------------------------

		    job.GetRequest.SetHeader("Content-Type", GetMimeType(xLocalFile))
			
		    ' Set the Authorization header
    		job.GetRequest.SetHeader("Authorization", BasicAuthorized)
			job.GetRequest.Timeout = 60000 ' 60 seconds    
			
    		Wait For (job) JobDone(job As HttpJob)
    
    		Success 		= job.Success
    		mLastHTTPCode 	= job.Response.StatusCode
    
    		If 	job.Success Then
        		Log("Uploaded successfully: " &xFileToUpload)
    		Else
        		Log("Error: " &job.ErrorMessage)
        		Log("Status Code: " &job.Response.StatusCode)
    		End If
    
    		job.Release
    
    		Return Success
End Sub

