Android Question CloudKVS Android Crash

techknight

Well-Known Member
Licensed User
Longtime User
During sync on my Android tablet, I get the Cursor Row, Column 0 error.

I remember reading a tiny cliffnote in the CloudKVS thread that older versions of android will get this error.

Can someone please elaborate what versions? I am running KitKat and it doesn't work.

I am trying to find a good way to deliver resized thumbnails from image and video files stored on B4J to B4A, And show those thumbnails in a listview on B4A.

I tried using an object file with a list of all the serialized bitmaps of each image, That works sometimes but mostly results in OOM Errors. So I moved to CloudKVS and store those in a record, and now a row/column error.

Is there a better way to do this?
 

techknight

Well-Known Member
Licensed User
Longtime User
Tried Android P, I get this error now:

B4X:
android.database.sqlite.SQLiteBlobTooBigException: Row too big to fit into CursorWindow requiredPos=1, totalRows=2

Even tried uninstalling, targetting to API 26 and reinstalling. Same thing.

B4J Everything works perfectly fine! GRRRRRR
 
Last edited:
Upvote 0

techknight

Well-Known Member
Licensed User
Longtime User
Well my thumbnails are serialized into a list that makes the database to about 6Megabytes or so.

All thumbnails in total are around 6MB in size for a blob. But this could vary based on what kind of media this person may have.

The only other thought is create a new "user" called thumbnails and each key/value be 1 thumbnail instead of 1 key/value being a list of all thumbnails. Maybe that will work?
 
Upvote 0

techknight

Well-Known Member
Licensed User
Longtime User
I need to keep track of those keys somehow. Because lets say when I need to refresh the cache, I have to delete every single record from the DB and rebuild. So it would be easier to delete the user as it will kill all the keys with it.

If a person goes into the media folder, deletes or re-arranges everything, I have to rebuild the cache so B4A App knows about it.

In CloudKVS Server, I saw a subroutine in DB called DeleteUSer, but I am not sure how to call it.
 
Last edited:
Upvote 0

techknight

Well-Known Member
Licensed User
Longtime User
Here is how I am currently doing it. (Resulting in the blob error)

B4X:
Type MediaFile(FileName As String, FileType As String, Subdir As String, Duration As String, Thumbnail As String)
Public MediaFiles As List 'All available media files as customtype MediaFile


'Retrieves every file found in the Media folder and stores it for later use.
Public Sub GetAllFilesRecursive As ResumableSub
    MediaFiles.Clear
    Main.ckvs.Put("CMS3K", "MediaFiles", Null) 'Delete old cache DB Record.
    If File.ListFiles(MediaDIR).Size = 0 Then Return False 'No Files Found
    Files.Clear 'Clear our cached files before we begin to recache
    Folders.Clear 'Clear our cached directories before we begin to recache
    ReadMediaDir(MediaDIR, True) 'Get all files and folders, Parse and store only Media Files.
    Wait For (BuildAllMediaFiles) Complete (Result As Boolean)
    Return True
End Sub

'Build the Thumbnails, Duration of video files (if applicable).
Public Sub BuildAllMediaFiles As ResumableSub
    Dim I As Int
    I = 0
    IncompatibleVideoDetected = False
    Main.ckvs.Put("CMS3K", "MediaFiles", Null) 'Remove existing record of media file cache.
    MediaType.InitializeStatic("com.google.common.net.MediaType")
    For Each MF As MediaFile In MediaFiles
        Wait For (BuildMediaFile(MF)) Complete (Result As MediaFile)
        MF = Result
        I = I + 1
        If SubExists(Callback, "FileManager_BuildMediaStatus") = True Then
            CallSub2(Callback, "FileManager_BuildMediaStatus", Array As Int(MediaFiles.Size, I))
        Else
            Log("Unhandled BuildMediaStatus Event Caught")
        End If
    Next
    Main.ckvs.Put("CMS3K", "MediaFiles", MediaFiles) 'Update cache DB record.
    Return True
End Sub

'Get the thumbnail, duration of the media file.
Public Sub BuildMediaFile(MF As MediaFile) As ResumableSub
    If MediaType.RunMethodJO("parse",Array(MF.FileType)).RunMethod("is",Array(MediaType.GetField("ANY_IMAGE_TYPE"))) = True Then 'It is an Image File
        Dim ThumbNailImage As Image
        ThumbNailImage.InitializeSample(MediaDIR & MF.Subdir, MF.FileName, 320, 240)
        MF.Thumbnail = BitmapToBase64(ThumbNailImage) 'This takes alot of String space. Maybe a better way?
    Else If MediaType.RunMethodJO("parse",Array(MF.fileType)).RunMethod("is",Array(MediaType.GetField("ANY_VIDEO_TYPE"))) = True Then 'It is a video file
        Dim rs As ResumableSub = GenerateVideoDetails(MF.FileName, MF.Subdir)
        Wait For (rs) Complete (Result As Object)
        If Result Is Boolean Then 'We returned a fault from GenerateVideoDetails, either a corrupt file or the file was in use.
            MF.Thumbnail = "failed"
            MF.Duration = ""
        Else 'We got a good reply
            Dim Arr() As String = Result
            MF.Duration = Arr(0)
            MF.Thumbnail = Arr(1)
        End If
        If Left(MF.FileType, 5) = "video" And MF.FileType <> "video/mp4" Then 'We have detected an incompatible file type, raise the flag so the operator can convert.
            IncompatibleVideoDetected = True
        End If
    End If
    Return MF
End Sub

'Generates the video thumbnail and video duration from FFMpeg
Private Sub GenerateVideoDetails(FileName As String, FilePath As String) As ResumableSub
    Dim sh1 As Shell
    If File.Exists(File.DirTemp, "thumb.png") = True Then File.Delete(File.DirTemp, "thumb.png") 'Remove the file
    sh1.Initialize("sh1", "ffmpeg", Array As String("-i", MediaDIR & FilePath & "/" & FileName, "-vf", "thumbnail,scale=320:240", "-frames:v", "1", File.DirTemp & "/" & "thumb.png")) 'Grab the duration and thumbnail from the media file
    sh1.WorkingDirectory = File.DirApp
    sh1.Run(10000)
    Wait For sh1_ProcessCompleted (Success As Boolean, ExitCode As Int, StdOut As String, StdErr As String)
    If Success = False Then
        Return False
    Else
        Try 'Parse Duration from Response Text. ffmpeg outputs on STDErr for some reason. So we will use a sum of both stdout and stderr.
            Dim StdOutErr As String = StdOut & StdErr
            Dim Response() As String = Regex.Split("Duration:", StdOutErr)
            Dim Duration() As String = Regex.Split(",", Response(1))
        Catch 'Failed to parse the correct duration information. Maybe passed a bad file?
            Return False
        End Try
        Dim bmp As Image
        bmp.Initialize(File.DirTemp, "thumb.png")
        Return Array As String(Duration(0), BitmapToBase64(bmp))
    End If
End Sub

Private Sub ReadMediaDir(folder As String, recursive As Boolean)
    Dim lst As List = File.ListFiles(folder)
    MediaType.InitializeStatic("com.google.common.net.MediaType")
    For i = 0 To lst.Size - 1
        If File.IsDirectory(folder,lst.Get(i)) Then
            Dim v As String
            v = folder & "/" & lst.Get(i)
            Folders.Add(v.SubString(MediaDIR.Length+1))
            If recursive Then
                ReadMediaDir(v,recursive) 'Very similar to "goto" Re-runs the same sub inside the sub.
            End If
        Else
            Dim fileType As String = asJO(Me).RunMethod("getType",Array(folder & "/" & lst.Get(i))) 'Get the MIME Type of the file
            If MediaType.RunMethodJO("parse",Array(fileType)).RunMethod("is",Array(MediaType.GetField("ANY_IMAGE_TYPE"))) = True Or _
            MediaType.RunMethodJO("parse",Array(fileType)).RunMethod("is",Array(MediaType.GetField("ANY_VIDEO_TYPE"))) = True Then 'If the file is a usable video or image file we parse it further
                Dim MF As MediaFile
                MF.Initialize
                MF.FileName = lst.Get(i)
                MF.FileType = fileType
                MF.Subdir = folder.SubString(MediaDIR.Length)
                MediaFiles.Add(MF)
                Main.ckvs.Put("CMS3K", "MediaFiles", MediaFiles) 'Update DB Record
            End If
            Files.Add(folder & "/" & lst.Get(i))
        End If
    Next
End Sub

Keep in mind, this was all working perfectly between B4J <-> B4A without CloudKVS, and using MQTT. Problem was, all the thumbnails were being stored into the MediaFiles list on the B4A app as well which would generate OOM errors, especially when using WriteB4XObject to store it away for later "offline" use.

So I figured moving the MediaFiles list into a DB would solve those problems. NOPE, just created a whole set of new ones :-(

Any files that I wanted to view the full version or, or preview the playback on the B4A App, I use FTP to download that. sending a full binary copy over MQTT was not going over well.
 
Last edited:
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
storing images in a database was never a good solution.
Store the filename in the DB. store the image itself on your server.

In your app you need to download the image from your server if needed after you get the filename from the database.
 
Upvote 0

techknight

Well-Known Member
Licensed User
Longtime User
Which is 100% impractical in my mind when you pop up a list of available files with the thumbnails of each file in the list. there could be hundreds of files!

This is why I need a synched thumbs.db

The files have 0 use to the user on the B4A app except to display a thumbnail and its filename and information. (Unless the user wants to play a video file, in that case it gets fetched from FTP)
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
The point is that the SQLite cursor size is limited to 1-2 MB.
there could be hundreds of files!
which your app only needs to download one time (when not available on the device as file). Any further appstart they are available and don´t need to get downloaded. Or only the missing ones.

If you have hundrets thumbnails the download should go fast as thumbnails usually are small files. And, as said, your just need to download them once.

Just my 2 cent
 
Upvote 0

techknight

Well-Known Member
Licensed User
Longtime User
Downloading once only goes so far because if someone changes the media files around on the server, the process has to start over again.

Which means writing a comparison subroutine. I just flush and resync all, its easier.

I'll figure something out. I just am not sure what at this point.
 
Upvote 0
Top