B4J Question Problem with Resumable Sub that recursively call itself.

max123

Well-Known Member
Licensed User
Longtime User
Hi all,

I've this code in my library (at bottom of post), the entry point here is the MinifyAllFilesOnFolderAsync sub,
the sub should recursively minimize all JS and CSS files in the specified folder SourceDir and save them in DestDir
mantaing the folder structure.

I call it from Main this way:
B4X:
Dim Compressor As YUICompressor
Compressor.Initialize(Me, "Compressor")
Compressor.MinifyAllFilesOnFolderAsync (File.DirTemp, File.DirApp, True)
Wait For Compressor_FolderComplete (Success As Boolean, Item As MinifiedFolderItem)

The MinifyAllFilesOnFolderAsync sub is called, then inside it, the sub MinifyAllFilesOnFolderInternalAsync is called,
a Wait For wait for all files are processed recursively and then the result return should return back to the calling sub (a Wait For in the Main)

It is called with:
B4X:
    MinifyAllFilesOnFolderInternalAsync (SourceDir, DestDir, UseMinPrefix)
    Wait For Compressor_FolderComplete (Success As Boolean, FolderItem As MinifiedFolderItem)

At this point all files are listed in the SourceDir folder, it process some files and recursively search files inside subfolfers and process them if they are JS or CSS, but then at some time the execution flow is not what I expect.

The sub MinifyAllFilesOnFolderInternalAsync call itself recursively if folder is found, but it should only terminate one time and call back the MinifyAllFilesOnFolderAsync to process it's return and then return it back to the Main with CallSubDelayed3 .

Some files are processed, but I never see the COMPLETED log, the sub never return back to calling sub.

Note that MinifyFileToFileAsync called to process a file when found, is also asyncronous sub, and it works well if called from the main.

In the MinifyAllFilesOnFolderInternalAsync I even use File.ListFilesAsync instead of File.ListFiles.

I followed it for 4 hours line by line in debug mode, but no luck.
The For Each f As String In Files should only terminate one time and here the sub should return back the non-Null value, that is a MinifiedFolderItem Type.
Instead it is called more times, even if the sub itself do not return back but continue it's execution, but at the end do not fires the event on the calling sub.

Please someone know what is wrong here ?
Many thanks.

B4X:
#Event: FolderComplete (Success As Boolean, Item As MinifiedFolderItem)

Type MinifiedFolderItem (SourceDir As String, DestDir As String, OriginalSize As Int, MinifiedSize As Int, Compression As Float)
 
Public Sub MinifyAllFilesOnFolderAsync (SourceDir As String, DestDir As String, UseMinPrefix As Boolean) As ResumableSub
    mFolderFilesOriginalSize = 0
    mFolderFilesMinifiedSize = 0
   
'''    Wait For (MinifyAllFilesOnFolderInternalAsync (SourceDir, DestDir, UseMinPrefix)) Complete (FolderItem As MinifiedFolderItem)
    MinifyAllFilesOnFolderInternalAsync (SourceDir, DestDir, UseMinPrefix)
    Wait For Compressor_FolderComplete (Success As Boolean, FolderItem As MinifiedFolderItem)
   
    Log("======================== COMPLETED ========================")

    If Success Then
        FolderItem.SourceDir = SourceDir
        FolderItem.DestDir = DestDir
        FolderItem.OriginalSize = mFolderFilesOriginalSize
        FolderItem.MinifiedSize = mFolderFilesMinifiedSize
        FolderItem.Compression = MapFloat(mFolderFilesMinifiedSize, 0, mFolderFilesOriginalSize, 100, 0)
        If SubExists(mModule, mEventName & "_FolderComplete") Then
            CallSubDelayed3(mModule, mEventName & "_FolderComplete", True, FolderItem)
        End If
    Else
        If SubExists(mModule, mEventName & "_FolderComplete") Then
            CallSubDelayed3(mModule, mEventName & "_FolderComplete", False, Null)
        End If
    End If
   
    Return True
End Sub

Private Sub MinifyAllFilesOnFolderInternalAsync (SourceDir As String, DestDir As String, UseMinPrefix As Boolean) As ResumableSub
   
    ' Add error handling for file existence check
    If File.Exists(SourceDir, "") = False Or File.IsDirectory(SourceDir, "") = False Then
        Log("The SourceDir folder does not exist: " & SourceDir)
        CallSubDelayed3(Me, mEventName & "_FolderComplete", False, Null)
        Return True ' Ensure proper termination
    End If
   
    If DestDir = "" Then DestDir = SourceDir
   
    Log(" ")
    Log("CURRENT SOURCE FOLDER: " & SourceDir)
    Log("  CURRENT DEST FOLDER: " & DestDir)
    Log(" ")
   
'    Dim Files As List = File.ListFiles(SourceDir)

    ' Handle asynchronous file listing
    Wait For (File.ListFilesAsync(SourceDir)) Complete (Success As Boolean, Files As List)
   
    If Success Then
        For Each f As String In Files
       
            If File.IsDirectory(SourceDir, f) = False Then
                Log("FOUND FILE: " & File.Combine(SourceDir, f))
           
                Dim fName As String
           
                ' Touch .js and .css files only, ignore all other extentions
           
                If f.EndsWith(".js") Then
                    If UseMinPrefix Then
                        If f.ToLowerCase.Contains(".min") = False Then
                            fName = f.SubString2(0, f.IndexOf(".js")) & ".min.js"
                        Else
                            Log("JS File already minified. Skip it: " & File.Combine(SourceDir, f))
                            Continue
                        End If
                    Else
                        fName = f
                    End If
                    Log(" ") : Log(">>>>>>>>> PROCESS JS FILE: " & File.Combine(SourceDir, f))
                    Log("Minified JS file name: " & fName)
                    mInternalOperation = True
                    '''                    Wait For (MinifyFileToFileAsync (SourceDir, f, DestDir, fName)) Complete (Success As Boolean)
                    MinifyFileToFileAsync (SourceDir, f, DestDir, fName)
                    Wait For Compressor_FileComplete (Success As Boolean, jsItem As MinifiedFileItem)
                    Log("JS Returned from MinifyFileToFileAsync")
                    If Success Then
                        mFolderFilesOriginalSize = mFolderFilesOriginalSize + jsItem.OriginalSize
                        mFolderFilesMinifiedSize = mFolderFilesMinifiedSize + jsItem.MinifiedSize
                    Else
                        CallSubDelayed3(Me, mEventName & "_FolderComplete", False, Null)
                    End If
                Else If f.EndsWith(".css") Then
                    If UseMinPrefix Then
                        If f.ToLowerCase.Contains(".min") = False Then
                            fName = f.SubString2(0, f.IndexOf(".css")) & ".min.css"
                        Else
                            Log("CSS File already minified. Skip it: " & File.Combine(SourceDir, f))
                            Continue
                        End If
                    Else
                        fName = f
                    End If
                    Log(" ") : Log(">>>>>>>>> PROCESS CSS FILE: " & File.Combine(SourceDir, f))
                    Log("Minified CSS file name: " & fName)
                    mInternalOperation = True
                    '''                    Wait For (MinifyFileToFileAsync (SourceDir, f, DestDir, fName)) Complete (Success As Boolean)
                    MinifyFileToFileAsync (SourceDir, f, DestDir, fName)
                    Wait For Compressor_FileComplete (Success As Boolean, cssItem As MinifiedFileItem)
                    Log("CSS Returned from MinifyFileToFileAsync")
                    If Success Then
                        mFolderFilesOriginalSize = mFolderFilesOriginalSize + cssItem.OriginalSize
                        mFolderFilesMinifiedSize = mFolderFilesMinifiedSize + cssItem.MinifiedSize
                    Else
'                        If SubExists(Me, mEventName & "_FolderComplete") Then
                        CallSubDelayed3(Me, mEventName & "_FolderComplete", False, Null)
                        Return True
'                        End If                       
                    End If
                End If
            Else
                Log("FOUND FOLDER: " & File.Combine(SourceDir,f))
               
                ' Recursive call to process subfolders
                ''' Wait For (MinifyAllFilesOnFolderInternalAsync (File.Combine(SourceDir, f), File.Combine(DestDir, f), UseMinPrefix)) Complete (FolderItem As MinifiedFolderItem) ' << RECURSIVELY CALL ITSELF
                MinifyAllFilesOnFolderInternalAsync (File.Combine(SourceDir, f), File.Combine(DestDir, f), UseMinPrefix)
                Wait For Compressor_FolderComplete (Success As Boolean, FolderItem As MinifiedFolderItem) ' << RECURSIVELY CALL ITSELF
                Log("Returned from MinifyAllFilesOnFolderInternalAsync")
                                   
                If Success = False Then
                    CallSubDelayed3(Me, mEventName & "_FolderComplete", False, Null)
                    Return True ' Ensure proper termination
                End If
                                           
'                Continue  ' Should this be used to skip next iteration ???
            End If
        Next
    Else
        Log("ERROR. CANNOT LIST FILES")
        '        Return Null
        CallSubDelayed3(Me, mEventName & "_FolderComplete", False, Null)
        Return True ' Ensure proper termination
    End If
   
    ' Call FolderComplete event only once after processing all files and subfolders
    Dim FolderItem As MinifiedFolderItem
    FolderItem.Initialize
   
    '    Sleep(1)
    Log(" ") : Log ("FOLDER MINIFICATION END. SHOULD ONLY CALLED AT END WHEN PROCESSED RECURSIVELY FOLDERS, SUBFOLDERS AND FILES ONE BY ONE WITH For Each")  ' <<<<< THIS IS CALLED MORE TIMES BUT SHOULD ONLY CALLED ONE TIME
    CallSubDelayed3(Me, mEventName & "_FolderComplete", True, FolderItem)
   
    Return True ' Ensure proper termination
End Sub
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Here my last. I added TraverseFolders4.
The only difference is that it use a global List instead of returning back the List as argument.
 

Attachments

  • Screenshot 2024-05-11 130727.png
    Screenshot 2024-05-11 130727.png
    127.6 KB · Views: 10
  • TraverseFolders_20240511_v4.zip
    17.4 KB · Views: 13
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Can I just use a global List without return it back from the ProcessFile and then from TraverseAsyncInternal ?
Of course! The code was provided to show what can be done. You are free to adapt as you want (as you have done above).
 
Upvote 0
Top