Partial Sync
This commit is contained in:
@@ -761,7 +761,7 @@ class BillsListViewActivity :
|
|||||||
val proj = withContext(Dispatchers.IO) { db.getProject(selectedProjectId) }
|
val proj = withContext(Dispatchers.IO) { db.getProject(selectedProjectId) }
|
||||||
if (proj != null && !proj.isLocal) {
|
if (proj != null && !proj.isLocal) {
|
||||||
db.cowspentServerSyncHelper.addCallbackPull(syncCallBack)
|
db.cowspentServerSyncHelper.addCallbackPull(syncCallBack)
|
||||||
db.cowspentServerSyncHelper.scheduleSync(false, selectedProjectId)
|
db.cowspentServerSyncHelper.scheduleSync(false, selectedProjectId, manual)
|
||||||
} else viewModel.isRefreshing = false
|
} else viewModel.isRefreshing = false
|
||||||
}
|
}
|
||||||
} else viewModel.isRefreshing = false
|
} else viewModel.isRefreshing = false
|
||||||
|
|||||||
@@ -93,14 +93,14 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
callbacksPull.add(callback)
|
callbacksPull.add(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scheduleSync(onlyLocalChanges: Boolean, projId: Long): SyncTask? {
|
fun scheduleSync(onlyLocalChanges: Boolean, projId: Long, forceFullSync: Boolean = false): SyncTask? {
|
||||||
Log.d(TAG, "Sync requested (${if (onlyLocalChanges) "onlyLocalChanges" else "full"}; ${if (syncActive) "sync active" else "sync NOT active"}) ...")
|
Log.d(TAG, "Sync requested (${if (onlyLocalChanges) "onlyLocalChanges" else "full"}; ${if (syncActive) "sync active" else "sync NOT active"}; forceFullSync=$forceFullSync) ...")
|
||||||
updateNetworkStatus()
|
updateNetworkStatus()
|
||||||
if (isSyncPossible && (!syncActive || onlyLocalChanges)) {
|
if (isSyncPossible && (!syncActive || onlyLocalChanges)) {
|
||||||
val project = dbHelper.getProject(projId)
|
val project = dbHelper.getProject(projId)
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
Log.d(TAG, "... starting now")
|
Log.d(TAG, "... starting now")
|
||||||
val syncTask = SyncTask(onlyLocalChanges, project)
|
val syncTask = SyncTask(onlyLocalChanges, project, forceFullSync)
|
||||||
syncTask.addCallbacks(callbacksPush)
|
syncTask.addCallbacks(callbacksPush)
|
||||||
callbacksPush = ArrayList()
|
callbacksPush = ArrayList()
|
||||||
if (!onlyLocalChanges) {
|
if (!onlyLocalChanges) {
|
||||||
@@ -138,7 +138,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class SyncTask(private val onlyLocalChanges: Boolean, private val project: DBProject) {
|
inner class SyncTask(private val onlyLocalChanges: Boolean, private val project: DBProject, private val forceFullSync: Boolean = false) {
|
||||||
private val callbacks: MutableList<ICallback> = ArrayList()
|
private val callbacks: MutableList<ICallback> = ArrayList()
|
||||||
private var nextcloudClient: NextcloudClient? = null
|
private var nextcloudClient: NextcloudClient? = null
|
||||||
private var client: VersatileProjectSyncClient? = null
|
private var client: VersatileProjectSyncClient? = null
|
||||||
@@ -726,23 +726,59 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
val paymentModesRemoteIdToId = dbPaymentModes.associate { it.remoteId to it.id }.toMutableMap()
|
val paymentModesRemoteIdToId = dbPaymentModes.associate { it.remoteId to it.id }.toMutableMap()
|
||||||
dbPaymentModes.filter { it.remoteId < 0 }.forEach { paymentModesRemoteIdToId[it.remoteId] = it.id }
|
dbPaymentModes.filter { it.remoteId < 0 }.forEach { paymentModesRemoteIdToId[it.remoteId] = it.id }
|
||||||
|
|
||||||
val billsResponse = client!!.getBills(project)
|
|
||||||
val isIHM = project.type == ProjectType.IHATEMONEY
|
val isIHM = project.type == ProjectType.IHATEMONEY
|
||||||
val serverSyncTimestamp = if (isIHM) 0L else billsResponse.syncTimestamp
|
var serverSyncTimestamp = project.lastSyncedTimestamp
|
||||||
val remoteBills: List<DBBill> = if (isIHM) {
|
val remoteBills = mutableListOf<DBBill>()
|
||||||
billsResponse.getBillsIHM(project.id, memberRemoteIdToId, categoriesRemoteIdToId, paymentModesRemoteIdToId)
|
val remoteAllBillIds = mutableListOf<Long>()
|
||||||
} else {
|
|
||||||
billsResponse.getBillsCospend(project.id, memberRemoteIdToId, categoriesRemoteIdToId, paymentModesRemoteIdToId)
|
val localBills = dbHelper.getBillsOfProject(project.id)
|
||||||
|
val localBillsByRemoteId = localBills.associateBy { it.remoteId }
|
||||||
|
|
||||||
|
if (project.type == ProjectType.COSPEND && !forceFullSync && localBills.isNotEmpty()) {
|
||||||
|
Log.d(TAG, "Starting partial sync for project ${project.remoteId}")
|
||||||
|
var offset = 0
|
||||||
|
val limit = 50
|
||||||
|
var allMatched = false
|
||||||
|
while (!allMatched) {
|
||||||
|
val partialResponse = client!!.getBills(project, offset, limit, true, 0)
|
||||||
|
val partialRemoteBills = partialResponse.getBillsCospend(project.id, memberRemoteIdToId, categoriesRemoteIdToId, paymentModesRemoteIdToId)
|
||||||
|
if (partialRemoteBills.isEmpty()) break
|
||||||
|
|
||||||
|
remoteBills.addAll(partialRemoteBills)
|
||||||
|
|
||||||
|
var frameMatched = true
|
||||||
|
for (rb in partialRemoteBills) {
|
||||||
|
val lb = localBillsByRemoteId[rb.remoteId]
|
||||||
|
if (lb == null || hasChanged(lb, rb)) {
|
||||||
|
frameMatched = false
|
||||||
|
break
|
||||||
}
|
}
|
||||||
val remoteAllBillIds: List<Long> = if (isIHM) {
|
}
|
||||||
remoteBills.map { it.remoteId }
|
|
||||||
|
if (offset == 0 && partialResponse.syncTimestamp > 0) {
|
||||||
|
serverSyncTimestamp = partialResponse.syncTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameMatched) {
|
||||||
|
allMatched = true
|
||||||
} else {
|
} else {
|
||||||
billsResponse.allBillIds
|
offset += limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Starting full sync for project ${project.remoteId}")
|
||||||
|
val billsResponse = client!!.getBills(project)
|
||||||
|
serverSyncTimestamp = if (isIHM) 0L else billsResponse.syncTimestamp
|
||||||
|
if (isIHM) {
|
||||||
|
remoteBills.addAll(billsResponse.getBillsIHM(project.id, memberRemoteIdToId, categoriesRemoteIdToId, paymentModesRemoteIdToId))
|
||||||
|
remoteAllBillIds.addAll(remoteBills.map { it.remoteId })
|
||||||
|
} else {
|
||||||
|
remoteBills.addAll(billsResponse.getBillsCospend(project.id, memberRemoteIdToId, categoriesRemoteIdToId, paymentModesRemoteIdToId))
|
||||||
|
remoteAllBillIds.addAll(billsResponse.allBillIds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val remoteBillsByRemoteId = remoteBills.associateBy { it.remoteId }
|
val remoteBillsByRemoteId = remoteBills.associateBy { it.remoteId }
|
||||||
val localBills = dbHelper.getBillsOfProject(project.id)
|
|
||||||
val localBillsByRemoteId = localBills.associateBy { it.remoteId }
|
|
||||||
|
|
||||||
for (remoteBill in remoteBills) {
|
for (remoteBill in remoteBills) {
|
||||||
if (!localBillsByRemoteId.containsKey(remoteBill.remoteId)) {
|
if (!localBillsByRemoteId.containsKey(remoteBill.remoteId)) {
|
||||||
@@ -783,7 +819,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.type == ProjectType.COSPEND || project.type == ProjectType.IHATEMONEY) {
|
if (remoteAllBillIds.isNotEmpty() && (project.type == ProjectType.COSPEND || project.type == ProjectType.IHATEMONEY)) {
|
||||||
for (localBill in localBills) {
|
for (localBill in localBills) {
|
||||||
if (!remoteAllBillIds.contains(localBill.remoteId)) {
|
if (!remoteAllBillIds.contains(localBill.remoteId)) {
|
||||||
dbHelper.deleteBill(localBill.id)
|
dbHelper.deleteBill(localBill.id)
|
||||||
@@ -792,7 +828,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
Log.d(TAG, "Delete local bill : $localBill")
|
Log.d(TAG, "Delete local bill : $localBill")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (remoteAllBillIds.isNotEmpty()) {
|
||||||
for (localBill in localBills) {
|
for (localBill in localBills) {
|
||||||
if (!remoteBillsByRemoteId.containsKey(localBill.remoteId)) {
|
if (!remoteBillsByRemoteId.containsKey(localBill.remoteId)) {
|
||||||
dbHelper.deleteBill(localBill.id)
|
dbHelper.deleteBill(localBill.id)
|
||||||
|
|||||||
@@ -679,22 +679,66 @@ class VersatileProjectSyncClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class, IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
@Throws(JSONException::class, IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
fun getBills(project: DBProject): ServerResponse.BillsResponse {
|
fun getBills(
|
||||||
|
project: DBProject,
|
||||||
|
offset: Int? = null,
|
||||||
|
limit: Int? = null,
|
||||||
|
reverse: Boolean? = null,
|
||||||
|
deleted: Int? = null
|
||||||
|
): ServerResponse.BillsResponse {
|
||||||
var target: String
|
var target: String
|
||||||
var username: String?
|
var username: String?
|
||||||
var password: String?
|
var password: String?
|
||||||
var bearerToken: String?
|
var bearerToken: String?
|
||||||
var useOcsApiRequest: Boolean
|
var useOcsApiRequest: Boolean
|
||||||
|
|
||||||
|
val paramKeys: MutableList<String> = ArrayList()
|
||||||
|
val paramValues: MutableList<String> = ArrayList()
|
||||||
|
|
||||||
if (ProjectType.COSPEND == project.type) {
|
if (ProjectType.COSPEND == project.type) {
|
||||||
val tsLastSync = project.lastSyncedTimestamp
|
val tsLastSync = project.lastSyncedTimestamp
|
||||||
|
if (offset == null) {
|
||||||
|
if (cospendVersionGT161) {
|
||||||
|
paramKeys.add("lastChanged")
|
||||||
|
} else {
|
||||||
|
paramKeys.add("lastchanged")
|
||||||
|
}
|
||||||
|
paramValues.add(tsLastSync.toString())
|
||||||
|
} else {
|
||||||
|
paramKeys.add("offset")
|
||||||
|
paramValues.add(offset.toString())
|
||||||
|
if (limit != null) {
|
||||||
|
paramKeys.add("limit")
|
||||||
|
paramValues.add(limit.toString())
|
||||||
|
}
|
||||||
|
if (reverse != null) {
|
||||||
|
paramKeys.add("reverse")
|
||||||
|
paramValues.add(reverse.toString())
|
||||||
|
}
|
||||||
|
if (deleted != null) {
|
||||||
|
paramKeys.add("deleted")
|
||||||
|
paramValues.add(deleted.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (canAccessProjectWithNCLogin(project)) {
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
username = this.username
|
username = this.username
|
||||||
password = this.password
|
password = this.password
|
||||||
target = if (cospendVersionGT161)
|
|
||||||
project.getRequestBaseUrl(true) + "/api/v1/projects/" + project.remoteId + "/bills?lastChanged=" + tsLastSync
|
|
||||||
else
|
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills?lastchanged=" + tsLastSync
|
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
val baseUrl = project.getRequestBaseUrl(useOcsApiRequest)
|
||||||
|
target = if (useOcsApiRequest)
|
||||||
|
"$baseUrl/api/v1/projects/${project.remoteId}/bills"
|
||||||
|
else
|
||||||
|
"$baseUrl/api-priv/projects/${project.remoteId}/bills"
|
||||||
|
|
||||||
|
if (paramKeys.isNotEmpty()) {
|
||||||
|
target += "?"
|
||||||
|
for (i in paramKeys.indices) {
|
||||||
|
if (i > 0) target += "&"
|
||||||
|
target += "${paramKeys[i]}=${paramValues[i]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ServerResponse.BillsResponse(
|
return ServerResponse.BillsResponse(
|
||||||
requestServer(
|
requestServer(
|
||||||
target, METHOD_GET, null, null,
|
target, METHOD_GET, null, null,
|
||||||
@@ -703,14 +747,6 @@ class VersatileProjectSyncClient(
|
|||||||
useOcsApiRequest
|
useOcsApiRequest
|
||||||
)
|
)
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
val paramKeys: MutableList<String> = ArrayList()
|
|
||||||
val paramValues: MutableList<String> = ArrayList()
|
|
||||||
if (cospendVersionGT161) {
|
|
||||||
paramKeys.add("lastChanged")
|
|
||||||
} else {
|
|
||||||
paramKeys.add("lastchanged")
|
|
||||||
}
|
|
||||||
paramValues.add(tsLastSync.toString())
|
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills"
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills"
|
||||||
ServerResponse.BillsResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, paramKeys, paramValues, true), true)
|
ServerResponse.BillsResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, paramKeys, paramValues, true), true)
|
||||||
@@ -720,10 +756,20 @@ class VersatileProjectSyncClient(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
target = if (cospendVersionGT161)
|
val baseUrl = project.getRequestBaseUrl(useOcsApiRequest)
|
||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastChanged=" + tsLastSync
|
target = if (useOcsApiRequest)
|
||||||
|
"$baseUrl/api/v1/public/projects/${project.remoteId}/${getEncodedPassword(project.password)}/bills"
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/apiv2/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastchanged=" + tsLastSync
|
"$baseUrl/apiv2/projects/${project.remoteId}/${getEncodedPassword(project.password)}/bills"
|
||||||
|
|
||||||
|
if (paramKeys.isNotEmpty()) {
|
||||||
|
target += "?"
|
||||||
|
for (i in paramKeys.indices) {
|
||||||
|
if (i > 0) target += "&"
|
||||||
|
target += "${paramKeys[i]}=${paramValues[i]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ServerResponse.BillsResponse(
|
return ServerResponse.BillsResponse(
|
||||||
requestServer(
|
requestServer(
|
||||||
target, METHOD_GET, null, null,
|
target, METHOD_GET, null, null,
|
||||||
|
|||||||
Reference in New Issue
Block a user