[Tasker Tips & Tricks] dadnammit:
FolderSync is a very powerful backup/synchronization app, but its scheduling is complete CARP! Often folderpairs that are scheduled for syncing will not start at all or will error out inexplicably. The developer hasn't provided any support in years (even for the paid app) despite releasing a sporadic update every now and then. And so users are forced to make do with the functionality as it is and find their own workarounds.

Luckily, it is integrated as a Tasker plugin, so we can kick off sync jobs from Tasker. But if you queue several jobs, you still face problems with them not always starting and also with handling crashes/errors. If you have root and sqlite, however, you can use Tasker to directly poll the sqlite database that FolderSync uses. This way you can check on busy/idle state, job status, and retry jobs with errors. I made a task that does this and wanted to share this solution/recipe.

Below is a code description and attached is an XML project file.

-----------------

The core of the functionality is in the pollFolderSyncDb task show below. It is intended to be called after invoking the FolderSync plugin. It uses the sqlite executable that is installed along with the Titanium Backup app (or set the %sqlite3 var to use a different path). The task should be called with two parameters:

%par1: The name of the FolderSync folderpair to check on, or "." for a global free/idle check.

%par2: (Optional) A timeout in seconds.

The task runs a couple sanity checks to make sure it finds a valid FolderSync sqlite database, and then it does one of two things based on the parameters:

1) If %par1 is ".", then it checks that there are no active jobs. A return value of "1" means it's free/idle. Otherwise it returns "0|SyncInProgress".

2) If %par2 is a valid folderpair name, then it starts querying the database to check on the job. Again "1" is a return value indicating successful completion and various "0|error" return values indicate problems. If no folderpair job appears in the database with that name within 8 seconds, it returns "0|null". Otherwise, it starts querying using an exponential backoff algorithm (configurable via the %max_exp var) for 1:59:20 (just less than two hours).

Possible return values for the pollFolderSyncDb task are:

1 -- Job completed with SyncOK status.

0|fpiderr -- No folderpair found matching the name parameter provided.

0|null -- No recently submitted folderpair job found for the name parameter provided.

0|SyncCancelled -- Job was cancelled in FolderSync (or data connection was turned off during the job).

0|SyncFailed -- Job had an error.

0|SyncInProgress -- Job is still running after the timeout passed.

0|dberr -- Critical error occurred accessing the sqlite database.

0|dbqueryerr -- Critical error occurred during a sqlite query.

0|pollerr -- Something unanticipated happened (a logic bug occurred).

Below is a sample task named "Folder Sync Example" that runs two folderpair sync jobs. First, it checks that FolderSync is not currently running a job. Then it kicks off the first FolderSync job and waits for the result. If needed, it can retry the job (controllable via the %max_attempts var). After the first job finishes, it kicks off the second job, and this can be repeated and so on and so on.

Task: Folder Sync Example (120)
A1: Notify [ Title:Folder Sync Text: Icon:hd_collections_collection Number:0 Permanent:eek:n Priority:3 ]
A2: Variable Set [ Name:%max_attempts To:3 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A3: Perform Task [ Name:pollFolderSyncDb Priority:%priority Parameter 1 (%par1):. Parameter 2 (%par2):7160 Return Value Variable:%ready Stop:eek:ff ]
A4: If [ %ready neq 1 ]
A5: Notify [ Title:Folder Sync Error Text:Not ready. Icon:hd_alerts_and_states_warning Number:0 Permanent:eek:ff Priority:3 ]
A6: Goto [ Type:Action Label Number:1 Label:end ]
A7: End If
<NB: first job>
A8: Variable Set [ Name:%attempt To:0 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
<sdcard0:DCIM>
A9: Variable Set [ Name:%attempt To:%attempt + 1 Recurse Variables:eek:ff Do Maths:eek:n Append:eek:ff ]
A10: FolderSync [ Configuration:sdcard0:DCIM;;;SYNC Timeout (Seconds):0 Continue Task After Error:eek:n ]
A11: Perform Task [ Name:pollFolderSyncDb Priority:%priority Parameter 1 (%par1):sdcard0:DCIM Parameter 2 (%par2): Return Value Variable:%result Stop:eek:ff ]
A12: Notify [ Title:Folder Sync Error Text:sdcard0:DCIM failed (%result) Icon:hd_alerts_and_states_warning Number:0 Permanent:eek:ff Priority:3 ] If [ %result neq 1 ]
A13: Goto [ Type:Action Label Number:1 Label:sdcard0:DCIM ] If [ %attempt < %max_attempts & %result eq 0|null |+ %result eq 0|SyncCancelled |+ %result eq 0|SyncFailed ]
A14: Goto [ Type:Action Label Number:1 Label:end ] If [ %result neq 1 & %result neq 0|null & %result neq 0|SyncCancelled & %result neq 0|SyncFailed ]
<NB: second job>
A15: Variable Set [ Name:%attempt To:0 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
<sdcard0:Documents>
A16: Variable Set [ Name:%attempt To:%attempt + 1 Recurse Variables:eek:ff Do Maths:eek:n Append:eek:ff ]
A17: FolderSync [ Configuration:sdcard0:Documents;;;SYNC Timeout (Seconds):0 Continue Task After Error:eek:n ]
A18: Perform Task [ Name:pollFolderSyncDb Priority:%priority Parameter 1 (%par1):sdcard0:Documents Parameter 2 (%par2): Return Value Variable:%result Stop:eek:ff ]
A19: Notify [ Title:Folder Sync Error Text:sdcard0:Documents failed (%result) Icon:hd_alerts_and_states_warning Number:0 Permanent:eek:ff Priority:3 ] If [ %result neq 1 ]
A20: Goto [ Type:Action Label Number:1 Label:sdcard0:Documents ] If [ %attempt < %max_attempts & %result eq 0|null |+ %result eq 0|SyncCancelled |+ %result eq 0|SyncFailed ]
A21: Goto [ Type:Action Label Number:1 Label:end ] If [ %result neq 1 & %result neq 0|null & %result neq 0|SyncCancelled & %result neq 0|SyncFailed ]
<end>
A22: Notify Cancel [ Title:Folder Sync Warn Not Exist:eek:ff ]

Task: pollFolderSyncDb (163)
A1: Variable Set [ Name:%fpid To:. Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A2: Variable Set [ Name:%timeout To:0 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A3: If [ %par1 Set & %par1 neq \%par1 ]
A4: Variable Set [ Name:%fpid To:%par1 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A5: Stop [ With Error:eek:n Task: ] If [ %fpid ~R ['"`;] ]
<NB: 1:59:20>
A6: Variable Set [ Name:%timeout To:7160 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A7: End If
A8: Variable Set [ Name:%timeout To:%par2 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %par2 Set & %par2 neq \%par2 ]
A9: Variable Set [ Name:%now To:%TIMES Recurse Variables:eek:ff Do Maths:eek:n Append:eek:ff ]
A10: Variable Set [ Name:%max_exp To:7 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A11: Variable Set [ Name:%sqlite3 To:/data/data/com.keramidas.TitaniumBackup/files/sqlite3 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A12: Variable Set [ Name:%dbfile To:/data/data/dk.tacit.android.foldersync.full/databases/foldersync.db Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A13: Test File [ Type:Size Data:%sqlite3 Store Result In:%size Use Root:eek:n Continue Task After Error:eek:n ]
A14: Variable Set [ Name:%dberr To:Sqlite3 binary not found. Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %size !Set ]
A15: If [ %dberr !Set ]
A16: Variable Clear [ Name:%size Pattern Matching:eek:ff ]
A17: Test File [ Type:Size Data:%dbfile Store Result In:%size Use Root:eek:n Continue Task After Error:eek:n ]
A18: Variable Set [ Name:%dberr To:Sqlite3 database not found. Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %size !Set ]
A19: End If
A20: If [ %dberr !Set ]
A21: Run Shell [ Command:%sqlite3 %dbfile "select sql from sqlite_master where type = 'table' and tbl_name = 'synclogs'" Timeout (Seconds):10 Use Root:eek:n Store Output In:%dbreturn Store Errors In:%dberr Store Result In: Continue Task After Error:eek:n ]
A22: Variable Set [ Name:%dberr To:Expected synclogs table not found (%dbreturn). Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %dberr !Set & %dbreturn neq CREATE TABLE `synclogs` (`actions` TEXT , `createdDate` VARCHAR NOT NULL , `dataTransferred` BIGINT , `endSyncTime` VARCHAR , `errors` TEXT , `filesChecked` INTEGER , `filesDeleted` INTEGER , `filesSynced` INTEGER , `folderPair_id` INTEGER NOT NULL , `id` INTEGER PRIMARY KEY AUTOINCREMENT , `message` VARCHAR , `status` VARCHAR ) ]
A23: End If
A24: If [ %dberr !Set ]
A25: Run Shell [ Command:%sqlite3 %dbfile "select sql from sqlite_master where type = 'table' and tbl_name = 'folderpairs'" Timeout (Seconds):10 Use Root:eek:n Store Output In:%dbreturn Store Errors In:%dberr Store Result In: Continue Task After Error:eek:n ]
A26: Variable Set [ Name:%dberr To:Expected folderpairs table not found (%dbreturn). Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %dberr !Set & %dbreturn neq CREATE TABLE "folderpairs" (`account_id` INTEGER , `account_id_primary` INTEGER , `active` SMALLINT , `advancedSyncDefinition` BLOB , `allowedNetworks` VARCHAR , `backupSchemePattern` VARCHAR , `batteryThreshold` INTEGER , `checkFileSizes` SMALLINT , `cleanEmptyFolders` SMALLINT , `createdDate` VARCHAR , `currentStatus` VARCHAR , `deleteFilesAfterSync` SMALLINT , `disallowedNetworks` VARCHAR , `excludeSyncAll` SMALLINT , `fileMasks` VARCHAR , `groupName` VARCHAR , `hasPendingChanges` SMALLINT , `id` INTEGER PRIMARY KEY AUTOINCREMENT , `ignoreNetworkState` SMALLINT , `instantSync` SMALLINT , `lastRun` VARCHAR , `name` VARCHAR NOT NULL , `notificationEmail` VARCHAR , `notifyOnChanges` SMALLINT , `notifyOnError` SMALLINT , `notifyOnSuccess` SMALLINT , `notifyOnSync` SMALLINT , `onlySyncChanged` SMALLINT , `onlySyncWhileCharging` SMALLINT , `preserveTargetFolder` SMALLINT , `remoteFolder` VARCHAR NOT NULL , `remoteFolderReadable` VARCHAR , `rescanMediaLibrary` SMALLINT , `retrySyncOnFail` SMALLINT , `sdFolder` VARCHAR NOT NULL , `sdFolderReadable` VARCHAR , `sortIndex` INTEGER , `syncAsHotspot` SMALLINT , `syncHiddenFiles` SMALLINT , `syncInterval` VARCHAR , `syncRuleConflict` VARCHAR , `syncRuleReplaceFile` VARCHAR , `syncSubFolders` SMALLINT , `syncType` VARCHAR , `turnOnWifi` SMALLINT , `use2G` SMALLINT , `use3G` SMALLINT , `useBackupScheme` SMALLINT , `useMd5Checksum` SMALLINT , `useMultiThreadedSync` SMALLINT , `useOtherInternet` SMALLINT , `useRoaming` SMALLINT , `useTempFiles` SMALLINT , `useWifi` SMALLINT ) ]
A27: End If
A28: If [ %dberr Set ]
A29: Notify [ Title:Folder Sync Error Text:Expected table not found. Icon:hd_alerts_and_states_warning Number:0 Permanent:eek:ff Priority:3 ]
A30: Return [ Value:0|dberr Stop:eek:n ]
A31: End If
A32: If [ %fpid neq . ]
A33: Run Shell [ Command:%sqlite3 %dbfile "select id from folderpairs where name = '%fpid'" Timeout (Seconds):10 Use Root:eek:n Store Output In:%dbreturn Store Errors In:%dberr Store Result In: Continue Task After Error:eek:n ]
A34: Variable Set [ Name:%dberr To:Folderpair not found (%fpid). Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %dberr !Set & %dbreturn !~R ^[0-9]+$ ]
A35: If [ %dberr Set ]
A36: Notify [ Title:Folder Sync Error Text:Folderpair not found. Icon:hd_alerts_and_states_warning Number:0 Permanent:eek:ff Priority:3 ]
A37: Return [ Value:0|fpiderr Stop:eek:n ]
A38: Else
A39: Variable Set [ Name:%fpid To:%dbreturn Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A40: End If
A41: End If
A42: If [ %fpid eq . ]
A43: Variable Set [ Name:%poll_query To:%sqlite3 %dbfile "select id, folderPair_id, createdDate, status from synclogs where status = 'SyncInProgress' limit 1" Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A44: Else
A45: Variable Set [ Name:%poll_query To:%sqlite3 %dbfile "select id, folderPair_id, createdDate, status from synclogs where folderPair_id = %fpid order by id desc limit 1" Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A46: End If
A47: Variable Set [ Name:%first_run To:1 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A48: Variable Set [ Name:%exp To:3 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
<whileLoop>
A49: If [ %TIMES < %now + %timeout + 1 | %first_run eq 1 ]
A50: If [ %fpid neq . | %first_run eq 0 ]
A51: Wait [ MS:0 Seconds:2 ^ %exp Minutes:0 Hours:0 Days:0 ]
A52: Variable Set [ Name:%exp To:%exp + 1 Recurse Variables:eek:ff Do Maths:eek:n Append:eek:ff ] If [ %exp < %max_exp ]
A53: End If
A54: Variable Set [ Name:%first_run To:0 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A55: Run Shell [ Command:%poll_query Timeout (Seconds):10 Use Root:eek:n Store Output In:%dbreturn Store Errors In:%dberr Store Result In: Continue Task After Error:eek:n ]
A56: Variable Set [ Name:%poll_ok To:0|dbqueryerr Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %dberr Set ]
A57: If [ %fpid eq . ]
<NB: no active row returned>
A58: Variable Set [ Name:%poll_ok To:1 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %poll_ok !Set & %dbreturn !Set ]
A59: Else
<NB: no fresh row returned>
A60: Variable Set [ Name:%poll_ok To:0|null Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %poll_ok !Set & %dbreturn !Set ]
A61: If [ %poll_ok !Set ]
A62: Variable Split [ Name:%dbreturn Splitter:| Delete Base:eek:ff ]
A63: If [ %dbreturn(#) eq 4 ]
A64: Variable Set [ Name:%status To:%dbreturn(4) Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A65: Variable Set [ Name:%timestamp To:%dbreturn(3) Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A66: Variable Search Replace [ Variable:%timestamp Search:\.[0-9]+$ Ignore Case:eek:ff Multi-Line:eek:ff One Match Only:eek:ff Store Matches In: Replace Matches:eek:n Replace With: ]
A67: Variable Convert [ Name:%timestamp Function:Date Time to Seconds Store Result In: ]
A68: Variable Set [ Name:%age To:%now - %timestamp Recurse Variables:eek:ff Do Maths:eek:n Append:eek:ff ]
A69: Else
A70: Variable Set [ Name:%status To:dbreturnerr Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ]
A71: Variable Clear [ Name:%age Pattern Matching:eek:ff ]
A72: End If
<NB: row found is not fresh>
A73: Variable Set [ Name:%poll_ok To:0|null Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %poll_ok !Set & %age Set & %age > 8 ]
A74: Variable Set [ Name:%poll_ok To:1 Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %poll_ok !Set & %status eq SyncOK ]
<NB: SyncCancelled (cancelled? or connection turned off) or SyncFailed (error)>
A75: Variable Set [ Name:%poll_ok To:0|%status Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %poll_ok !Set & %status neq SyncInProgress ]
A76: End If
A77: End If
A78: Goto [ Type:Action Label Number:1 Label:whileLoop ] If [ %poll_ok !Set ]
<endWhileLoop>
A79: End If
A80: If [ %dbreturn Set & %fpid eq . |+ %status eq SyncInProgress ]
A81: Variable Set [ Name:%poll_ok To:0|SyncInProgress Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %poll_ok !Set ]
A82: Else
<NB: unexpected logic error>
A83: Variable Set [ Name:%poll_ok To:0|pollerr Recurse Variables:eek:ff Do Maths:eek:ff Append:eek:ff ] If [ %poll_ok !Set ]
A84: End If
A85: Return [ Value:%poll_ok Stop:eek:n ]