B4A Library AppUpdating - automate apps updating from a webserver

Status
Not open for further replies.
Important note: since all the changes made to the Android OS the original code is to be considered outdated. Thanks to @Yayou49 you may find at post#290 a version of the class usable on Android 6+ versions.

Note: version 1.25 breaks existing code so, please, read changelog in post #60 below.


Hi all,

this is my first contributing lib to this great NG.
How do I dare to publish such a low-grade stuff in an area where generally you may find real masterpieces of programming? Well, to be honest, I hope in your help to upgrade my work to a status of "ready-made solution" for those who prefer to run their own webservers where to publish apps.
Secondly, I was asked (both privately and publicly) to overcome my legitimate embarassment and go on with the publication. So, here we are: AppUpdating 1.18!

Note: any valid code fragment comes from some other thread, while all the bad stuff is entirely mine ;-)

Please find attached both the library as is (yes, it works out of the box) and its source code.

On a following post I'm gonna describe the lib in greater detail and show how to use it.

Umberto

AppUpdating version history

* version 1.30 (read notes on post #228)
  • compiled with okHttp 1.01 and okHttpUtils 2.20
  • removed files for version 1.25

* version 1.26 (read notes on post #84)
  • added property WebChangeLog to read optional app's version changelog data
  • better management of versioning in the info file
  • removed files for version 1.18 to avoid confusion (still available on request)
* version 1.25
* version 1.18
  • initial release
 

Attachments

  • AppUpdating_126_ex.zip
    28.3 KB · Views: 1,090
  • AppUpdating_126.zip
    11.3 KB · Views: 1,113
  • AppUpdating_126_src.zip
    10.8 KB · Views: 878
  • AppUpdating_130.zip
    11.4 KB · Views: 1,116
  • AppUpdating_130_src.zip
    15.7 KB · Views: 1,020
  • AppUpdating_130_ex.zip
    33.4 KB · Views: 1,220
Last edited:

Ricardo Bunschoten

Active Member
Licensed User
Longtime User
I need some help with this library.

I have made a program that downloads a zipfile and extracts it
It uses the Class Module HttpJob

B4X:
'HttpUtils2 version 2.01
'Class module
Sub Class_Globals
    Public JobName As String
    Public Success As Boolean
    Public Username, Password As String
    Public ErrorMessage As String
    Private target As Object
    Private taskId As String
    Private req As HttpRequest
    Public Tag As Object
End Sub

'Initializes the Job.
'Name - The job's name. Note that the name doesn't need to be unique.
'TargetModule - The activity or service that will handle the JobDone event.
Public Sub Initialize (Name As String, TargetModule As Object)
    JobName = Name
    target = TargetModule
End Sub
'Sends a POST request with the given data as the post data.
Public Sub PostString(Link As String, Text As String)
    PostBytes(Link, Text.GetBytes("UTF8"))
End Sub

'Public Sub PutString(Link As String, Text As String)
'    PutBytes(Link, Text.GetBytes("UTF8"))
'End Sub
'
'Public Sub PutBytes(Link As String, Data() As Byte)
'    mLink = Link
'    req.InitializePut2(Link, Data)
'    CallSubDelayed2(HttpUtils2Service, "SubmitJob", Me)
'End Sub

'Sends a POST request with the given string as the post data
Public Sub PostBytes(Link As String, Data() As Byte)
    req.InitializePost2(Link, Data)
    CallSubDelayed2(HttpUtils2Service, "SubmitJob", Me)
End Sub

'Sends a POST request with the given file as the post data.
'This method doesn't work with assets files.
Public Sub PostFile(Link As String, Dir As String, FileName As String)
    Dim length As Int
    If Dir = File.DirAssets Then
        Log("Cannot send files from the assets folder.")
        Return
    End If
    length = File.Size(Dir, FileName)
    Dim In As InputStream
    In = File.OpenInput(Dir, FileName)
    If length < 1000000 Then '1mb
        'There are advantages for sending the file as bytes array. It allows the Http library to resend the data
        'if it failed in the first time.
        Dim out As OutputStream
        out.InitializeToBytesArray(length)
        File.Copy2(In, out)
        PostBytes(Link, out.ToBytesArray)
    Else
        req.InitializePost(Link, In, length)
        CallSubDelayed2(HttpUtils2Service, "SubmitJob", Me)  
    End If
End Sub
'Submits a HTTP GET request.
'Consider using Download2 if the parameters should be escaped.
Public Sub Download(Link As String)
    req.InitializeGet(Link)
    CallSubDelayed2(HttpUtils2Service, "SubmitJob", Me)
End Sub
'Submits a HTTP GET request.
'Encodes illegal parameter characters.
'<code>Example:
'job.Download2("http://www.example.com", _
'    Array As String("key1", "value1", "key2", "value2"))</code>
Public Sub Download2(Link As String, Parameters() As String)
    Dim sb As StringBuilder
    sb.Initialize
    sb.Append(Link)
    If Parameters.Length > 0 Then sb.Append("?")
    Dim su As StringUtils
    For i = 0 To Parameters.Length - 1 Step 2
        If i > 0 Then sb.Append("&")
        sb.Append(su.EncodeUrl(Parameters(i), "UTF8")).Append("=")
        sb.Append(su.EncodeUrl(Parameters(i + 1), "UTF8"))
    Next
    req.InitializeGet(sb.ToString)
    CallSubDelayed2(HttpUtils2Service, "SubmitJob", Me)      
End Sub

'Called by the service to get the request
Public Sub GetRequest As HttpRequest
    Return req
End Sub

'Called by the service when job completes
Public Sub Complete (id As Int)
    taskId = id
    CallSubDelayed2(target, "JobDone", Me)
End Sub

'Should be called to free resources held by this job.
Public Sub Release
    File.Delete(HttpUtils2Service.TempFolder, taskId)
End Sub

'Returns the response as a string encoded with UTF8.
Public Sub GetString As String
    Return GetString2("UTF8")
End Sub

'Returns the response as a string.
Public Sub GetString2(Encoding As String) As String
    Dim tr As TextReader
    tr.Initialize2(File.OpenInput(HttpUtils2Service.TempFolder, taskId), Encoding)
    Dim res As String
    res = tr.ReadAll
    tr.Close
    Return res
End Sub

'Returns the response as a bitmap
Public Sub GetBitmap As Bitmap
    Dim b As Bitmap
    b = LoadBitmap(HttpUtils2Service.TempFolder, taskId)
    Return b
End Sub

Public Sub GetInputStream As InputStream
    Dim In As InputStream
    In = File.OpenInput(HttpUtils2Service.TempFolder, taskId)
    Return In
End Sub

It uses the service module Httputils2Service
B4X:
#Region Module Attributes
    #StartAtBoot: False
#End Region

'Modified version of HttpUtils2
'Service module
Sub Process_Globals
    Private hc As HttpClient
    Private TaskIdToJob As Map
    Public TempFolder As String
    Private taskCounter As Int
End Sub

Sub Service_Create
    TempFolder = File.DirInternalCache
    hc.Initialize("hc")
    TaskIdToJob.Initialize
End Sub

Sub Service_Start (StartingIntent As Intent)
  
End Sub

Sub Service_Destroy

End Sub

Public Sub SubmitJob(job As HttpJob) As Int
    taskCounter = taskCounter + 1
    TaskIdToJob.Put(taskCounter, job)
    If job.Username <> "" And job.Password <> "" Then
        hc.ExecuteCredentials(job.GetRequest, taskCounter, job.Username, job.Password)
    Else
        hc.Execute(job.GetRequest, taskCounter)
    End If
    Return taskCounter
End Sub

Sub hc_ResponseSuccess (Response As HttpResponse, TaskId As Int)
    ' ********** Modified code *************
    Dim cs As CountingOutputStream
    cs.Initialize(File.OpenOutput(TempFolder, TaskId, False))
    Dim j As HttpJob = TaskIdToJob.Get(TaskId)
    Dim jt As JobTag = j.Tag
    jt.CountingStream = cs
    jt.Total = Response.ContentLength
    If jt.Data.url = "" Then
        Log("Job cancelled before downloaded started")
        cs.Close
    End If
    Response.GetAsynchronously("response", cs , _
        True, TaskId)
    '**************************************
End Sub

Sub Response_StreamFinish (Success As Boolean, TaskId As Int)
    If Success Then
        CompleteJob(TaskId, Success, "")
    Else
        CompleteJob(TaskId, Success, LastException.Message)
    End If
End Sub

Sub hc_ResponseError (Response As HttpResponse, Reason As String, StatusCode As Int, TaskId As Int)
    If Response <> Null Then
        Try
            Log(Response.GetString("UTF8"))
        Catch
            Log("Failed to read error message.")
        End Try
        Response.Release
    End If
    CompleteJob(TaskId, False, Reason)
End Sub

Sub CompleteJob(TaskId As Int, success As Boolean, errorMessage As String)
    Dim job As HttpJob
    job = TaskIdToJob.Get(TaskId)
    TaskIdToJob.Remove(TaskId)
    job.success = success
    job.errorMessage = errorMessage
    job.Complete(TaskId)
End Sub

And it uses the servicemodule DownloadService

B4X:
#Region  Service Attributes
    #StartAtBoot: False
#End Region

Sub Process_Globals
    Private jobs As Map
    Private timer1 As Timer
    Type DownloadData (url As String, Target As Object, EventName As String)
    Type JobTag (Data As DownloadData,  _
        CountingStream As CountingOutputStream, Total As Long)
    Private pw As PhoneWakeState
End Sub
Sub Service_Create
    jobs.Initialize
    timer1.Initialize("timer1", 1000)
End Sub

Sub Service_Start (StartingIntent As Intent)

End Sub

Sub Service_Destroy

End Sub

Private Sub StartTimer (Target As Object)
    Dim n As Notification
    n.Initialize
    n.Icon = "icon"
    n.Vibrate = False
    n.Sound = False
    n.Light = False
    n.SetInfo("Downloading file...", "", Target)
    Service.StartForeground(1, n)
    timer1.Enabled = True
    pw.PartialLock
End Sub

Private Sub EndTimer
    Service.StopForeground(1)
    timer1.Enabled = False
    pw.ReleasePartialLock
End Sub

Public Sub StartDownload(data As DownloadData)
    If jobs.ContainsKey(data.url) Then
        Log("Ignoring duplicate request.")
        Return
    End If
    Dim J As HttpJob
    J.Initialize(data.url, Me)
    Dim tag As JobTag
    tag.Initialize
    tag.data = data
    J.tag = tag
    jobs.Put(data.url, J)
    J.Download(data.url)
    If timer1.Enabled = False Then StartTimer(data.Target)
End Sub

Public Sub CancelDownload(url As String)
    If jobs.ContainsKey(url) = False Then
        Log("Ignoring cancel request.")
        Return
    End If
    Dim job As HttpJob = jobs.Get(url)
    Dim jt As JobTag = job.Tag
    If jt.CountingStream.IsInitialized Then
        jt.CountingStream.Close
    Else
        jt.Data.url = ""
    End If
End Sub

Sub timer1_tick
    For Each job As HttpJob In jobs.Values
        Dim jt As JobTag = job.Tag
        If jt.CountingStream.IsInitialized Then
            CallSub3(jt.Data.Target, jt.Data.EventName & "_Progress", _
                jt.CountingStream.Count, jt.Total)
        End If
    Next
End Sub

Sub JobDone(job As HttpJob)
    jobs.Remove(job.JobName)
    Dim jt As JobTag = job.Tag
    If jobs.Size = 0 Then EndTimer
    If job.Success Then
        CallSubDelayed3(jt.Data.Target, jt.Data.EventName & "_Progress", _
                jt.CountingStream.Count, jt.Total)
        CallSubDelayed2(jt.Data.Target, jt.Data.EventName & "_Complete", _
                job)
    Else
        Log(job.ErrorMessage)
        CallSubDelayed2(jt.Data.Target, jt.Data.EventName & "_Complete", _
                job)
    End If
End Sub

These i have found on this forum

Now i made a button to check if a newer version is there online

B4X:
Sub Update_installer_click
      apkupdt.Initialize(Me,"UpdateCheck")               
    'this is yor app's package name (see "Project/Package name")
    apkupdt.PackageName = "com.mypackage.here"
    'this is the complete path to the text file holding the newer version number
    apkupdt.NewVerTxt = "my http link to changelog.info"
    'this is the complete path to your newer apk
    apkupdt.NewVerApk = "my link to the new apk"
      'this function starts checking for any newer version of the current app
    apkupdt.UpdateApk                                                   

End Sub

If i compile it i get this error

B4X:
B4A version: 5.02 (1)
Parsing code.    Error
Error parsing program.
Error description: HttpUtils2Service is declared twice. You should either remove the library reference or the code module.

What am i doing wrong ???
 

udg

Expert
Licensed User
Longtime User
Hi Ricardo,

please read posts #7 to #10 for a complete answer.
A short one is: remove service module Httputils2Service from your code.

Let me know if I can be of any further help.
 

Ricardo Bunschoten

Active Member
Licensed User
Longtime User
I removed the module but when i now compile

i get this error

B4A version: 5.02 (1)
Parsing code. (0.01s)
Compiling code. Error
Error compiling program.
Error description: Array expected.
Occurred on line: 57
J.Download(data.url)
Word: (

This in Download service.

if the lib has the same http2utilservice function i assume it should work right ?
 

PoleStar

Member
Licensed User
Longtime User
Hi
When I want to install this app, show this error:
 

Attachments

  • error.PNG
    error.PNG
    25.9 KB · Views: 156

udg

Expert
Licensed User
Longtime User
Hi Ricardo,

yes, everything should work as is. The lib was compiled with B4A 4.30, using HttpUtils2 2.01 (plus Phone 2.26 and Reflection 2.40).
Would you like to recompile it on your system just to be sure the lib and your code are aligned to the same code base?

Another point is that I can't find in your code as posted above where StartDownload is called so I find it difficult to follow the calling stack. Anyway this doesn't relate to the compiling error.

udg
 

udg

Expert
Licensed User
Longtime User
Hi Pantea,

I see you tried it on a Genymotion's instance. I never tested it that way, so I'm of little help here. Sorry.
From your exeperience with other similar code, is it supposed to work? Or an emulator can't fully run code based on HttpUtils2, Phone and Reflection libs accessing a remote server for some data?
Please post your findings here so to be of help to others. Thank you.

udg
 

Ricardo Bunschoten

Active Member
Licensed User
Longtime User
Hi Ricardo,

yes, everything should work as is. The lib was compiled with B4A 4.30, using HttpUtils2 2.01 (plus Phone 2.26 and Reflection 2.40).
Would you like to recompile it on your system just to be sure the lib and your code are aligned to the same code base?

Another point is that I can't find in your code as posted above where StartDownload is called so I find it difficult to follow the calling stack. Anyway this doesn't relate to the compiling error.

udg


Can i send the code in a personal message to you ?
 

udg

Expert
Licensed User
Longtime User
Can i send the code in a personal message to you ?
Replied by private conversation.
 

Don Roberts

Member
Licensed User
Hi,
Just started using the updater... Loving it so far.
1 tiny issue some of my beta testers have mentioned and the condition is the same on my phone,
the app doesn't auto start after the update...
Is this normal behavior or have I possibly done something wrong.
I added the code to the manifest editor.
using the do all in one click method with a splash screen.

Try
apkupdt.SetAndStartSplashScreen(Activity,LoadBitmap(File.DirAssets, "update.jpg"))
Catch
Log(LastException)
End Try
Try
apkupdt.UpdateApk
Catch
Msgbox(LastException,"Update Error")
End Try

My phone: Samsung Galaxy I437
I always choose to use the Android Installer.

Thanks

Don
 

opus

Active Member
Licensed User
Longtime User
Not sure what you mean, IMHO the user is firstly asked wether to update. If he/she selects the update it will be installed ( if "unknown Sources" are checked) and after that a messagebox-thing comes up stating the successfull installation, which can be answered with OK (apk will not be started) or Start (apk will be started).
In which situatiopn are you missing an automatic start?
 

Don Roberts

Member
Licensed User
Sorry I reread and I wasn't clear on the issue... The apk downloads... It installs... All works as it should. I was wondering since the update was initiated from within the application, I assumed after the update installed the application would restart with the new version.
 

Don Roberts

Member
Licensed User
HI,

Just moved my Updates into a protected folder on my server (Unprotected Folder Worked Great)... Standard .httaccess
I added the credentials and changed the location folders to the new folder but cannot connect.
Code:

apkupdt.SetAndStartSplashScreen(Activity,LoadBitmap(File.DirAssets, "update.jpg"))
apkupdt.setCredentials("bb4asystem","TvVKQN)!,8Uz")
apkupdt.UpdateApk

upload_2015-12-21_14-39-20.png
 

udg

Expert
Licensed User
Longtime User
Hi @Don Roberts ,
on my test webserver I set the .htaccess file for the protected folder as follows:
AuthType Basic
AuthName "Restricted Access"
AuthUserFile /home/a1234567/public_html/p_apk/.htpasswd
Require user test
where p_apk is the protected folder. As you can see, it checks the ".htpasswd" file for user "test" and its password (in encrypted format).
That setup worked for me while using code similar to yours. BTW, error -100 is generated when HttpUtils in its JobDone sub returns that Job.Success is False.
Let me know if the above is of any help or if I can further assist you.

udg
 

Don Roberts

Member
Licensed User
HI,

Just moved my Updates into a protected folder on my server (Unprotected Folder Worked Great)... Standard .httaccess
I added the credentials and changed the location folders to the new folder but cannot connect.
Code:

apkupdt.SetAndStartSplashScreen(Activity,LoadBitmap(File.DirAssets, "update.jpg"))
apkupdt.setCredentials("bb4asystem","TvVKQN)!,8Uz")
apkupdt.UpdateApk
 

Don Roberts

Member
Licensed User
Doing a line at a time change then test
I changed the file so the AuthName "Restricted Access" was the same as yours leaving the rest the same as:
AuthUserFile "/home/obxball/.htpasswds/public_html/android/bb4a/prot/passwd"
require valid-user

and now it is working... Is AuthName supposed to be just "Restricted Access"?
 

Don Roberts

Member
Licensed User
I have found something else I think...
I was testing what would happen if the .info file was missing (Either or of the files was the final test).
The program throws a Java-script error on my server (Continue script YES/NO).
If you select No to stop the script the program closes abruptly but can be started back up.
All works fine.
If you select yes to continue running the script it doesn't appear to do anything. It stayed on my splash
screen like 10 minutes, I then closed the program.
I re-launched the program and any further attempts to check for updates throw a Java NullPointer exception error
Using: Msgbox(LastException,"Update Error")...for like 10 more minutes (I closed the app each time the error showed),
After the 10 additional minutes it seemed to worked fine again.

It would be nice to be able to reset apkupdt... and or cancel the Http job after an error... or be able to set a reasonable
timeout value...

Donation sent.
 

udg

Expert
Licensed User
Longtime User
Hi Don,

I checked my code about the "Restricted Access" "forced" value but found no reference to it. Googling for the definition of AuthName it appears to be a prompt for the password so I guess it's not used at all (I mean, not used even by the underlying HttpJob object).
What calls for my attention is the "Require user" statement. My only tests were done against a single user so I had "Require user test", while I see you grant access to the protected folder to a few "authorized" users.

What happens behind the scene? With SetCredentials I simply save user/password values in internal var, then when an HttpJob is to be called I use those value as below:
B4X:
 Dim jobapk As HttpJob
 jobapk.Initialize("JobApkDownload", Me)
 jobapk.Username = sUserName
 jobapk.Password = sUPassword
 jobapk.Download(sNewVerApk) 'e.g.: jobapk.Download("http://umbetest.web44.net/p_apk/myapp.apk")

So I think we should put together a very simple test program that uses just those few lines to try to access your protected folder and see what happens. I can't recall right now if we have somewhere on the forum HttpJob source (probably yes) to dig in its inner workings and understand what may cause the failure.

It would be nice to be able to reset apkupdt... and or cancel the Http job after an error... or be able to set a reasonable
timeout value...
That makes for a good addition. If I'm not wrong there are already a few examples about cancelling an httpjob...

As you may have read a few posts above, I'm planning for a new release where to take in account for all the nice suggestions I received since last update.
It should be a "Late Xmas edition" since probably I will work on it during the next vacations.

udg
 

Don Roberts

Member
Licensed User
That is strange... I originally had it as "sytemuser" it didn't like that. (it was the first and only line I changed).
This may have more to do with what the server wants to see than what the lib wants to see.

[I did notice though on my server anyway, when I went back in through the control panel and changed the value
to Restricted Access and protected again... The 'system edited' .htaccess file had the lines in no particular order.
I opened the file in my ftp app and re-wrote in notepad to the same order as the original before testing again]

The original was ordered correctly... Not sure if that may mean something with someone else later on, if the lib or
the server wants that info in that particular order or if that even matters.

You are correct I have several users assigned to my system with access to some or all of the server.
So I have the possibility that any of those users could be granted access rights at any time to any folder.
I would think valid-user might only come into play if you have more than one user (most of my users are accounts
for server database access not HTTP Protected Folder access).

Your lib didn't seem to have an problem running the request valid-user. All my users report all is working great.
The control panel on my server called AuthName a "Common Name" for the folder. I created and protected a new folder
on my server then did some HTTP access on it, you are correct. When accessing a protected folder, when the login box
comes up the 'Title' is the Authname_value has Restricted Access Login Required.
So it does not appear you would need to set it in the lib.​

*Of Note. I just edited my .htaccess file and removed the AuthName Line completely. The program throws a Java-script error
on my server (Continue script YES/NO). Added the line back in and all works great again. My guess is the server requires that
file to those 4 lines.

Hope this helps.

Don
 
Status
Not open for further replies.
Top