1+ /*
2+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
3+ */
4+
5+ package at.bitfire.davdroid.push
6+
7+ import androidx.annotation.VisibleForTesting
8+ import at.bitfire.dav4jvm.XmlReader
9+ import at.bitfire.dav4jvm.XmlUtils
10+ import at.bitfire.davdroid.db.Collection.Companion.TYPE_ADDRESSBOOK
11+ import at.bitfire.davdroid.repository.AccountRepository
12+ import at.bitfire.davdroid.repository.DavCollectionRepository
13+ import at.bitfire.davdroid.repository.DavServiceRepository
14+ import at.bitfire.davdroid.sync.SyncDataType
15+ import at.bitfire.davdroid.sync.TasksAppManager
16+ import at.bitfire.davdroid.sync.worker.SyncWorkerManager
17+ import dagger.Lazy
18+ import org.unifiedpush.android.connector.data.PushMessage
19+ import org.xmlpull.v1.XmlPullParserException
20+ import java.io.StringReader
21+ import java.util.logging.Level
22+ import java.util.logging.Logger
23+ import javax.inject.Inject
24+ import at.bitfire.dav4jvm.property.push.PushMessage as DavPushMessage
25+
26+ /* *
27+ * Handles incoming WebDAV-Push messages.
28+ */
29+ class PushMessageHandler @Inject constructor(
30+ private val accountRepository : AccountRepository ,
31+ private val collectionRepository : DavCollectionRepository ,
32+ private val logger : Logger ,
33+ private val serviceRepository : DavServiceRepository ,
34+ private val syncWorkerManager : SyncWorkerManager ,
35+ private val tasksAppManager : Lazy <TasksAppManager >
36+ ) {
37+
38+ suspend fun processMessage (message : PushMessage , instance : String ) {
39+ if (! message.decrypted) {
40+ logger.severe(" Received a push message that could not be decrypted." )
41+ return
42+ }
43+ val messageXml = message.content.toString(Charsets .UTF_8 )
44+ logger.log(Level .INFO , " Received push message" , messageXml)
45+
46+ // parse push notification
47+ val topic = parse(messageXml)
48+
49+ // sync affected collection
50+ if (topic != null ) {
51+ logger.info(" Got push notification for topic $topic " )
52+
53+ // Sync all authorities of account that the collection belongs to
54+ // Later: only sync affected collection and authorities
55+ collectionRepository.getSyncableByTopic(topic)?.let { collection ->
56+ serviceRepository.getAsync(collection.serviceId)?.let { service ->
57+ val syncDataTypes = mutableSetOf<SyncDataType >()
58+ // If the type is an address book, add the contacts type
59+ if (collection.type == TYPE_ADDRESSBOOK )
60+ syncDataTypes + = SyncDataType .CONTACTS
61+
62+ // If the collection supports events, add the events type
63+ if (collection.supportsVEVENT != false )
64+ syncDataTypes + = SyncDataType .EVENTS
65+
66+ // If the collection supports tasks, make sure there's a provider installed,
67+ // and add the tasks type
68+ if (collection.supportsVJOURNAL != false || collection.supportsVTODO != false )
69+ if (tasksAppManager.get().currentProvider() != null )
70+ syncDataTypes + = SyncDataType .TASKS
71+
72+ // Schedule sync for all the types identified
73+ val account = accountRepository.fromName(service.accountName)
74+ for (syncDataType in syncDataTypes)
75+ syncWorkerManager.enqueueOneTime(account, syncDataType, fromPush = true )
76+ }
77+ }
78+
79+ } else {
80+ // fallback when no known topic is present (shouldn't happen)
81+ val service = instance.toLongOrNull()?.let { serviceRepository.get(it) }
82+ if (service != null ) {
83+ logger.warning(" Got push message without topic and service, syncing all accounts" )
84+ val account = accountRepository.fromName(service.accountName)
85+ syncWorkerManager.enqueueOneTimeAllAuthorities(account, fromPush = true )
86+
87+ } else {
88+ logger.warning(" Got push message without topic, syncing all accounts" )
89+ for (account in accountRepository.getAll())
90+ syncWorkerManager.enqueueOneTimeAllAuthorities(account, fromPush = true )
91+ }
92+ }
93+ }
94+
95+ /* *
96+ * Parses a WebDAV-Push message and returns the `topic` that the message is about.
97+ *
98+ * @return topic of the modified collection, or `null` if the topic couldn't be determined
99+ */
100+ @VisibleForTesting
101+ internal fun parse (message : String ): String? {
102+ var topic: String? = null
103+
104+ val parser = XmlUtils .newPullParser()
105+ try {
106+ parser.setInput(StringReader (message))
107+
108+ XmlReader (parser).processTag(DavPushMessage .NAME ) {
109+ val pushMessage = DavPushMessage .Factory .create(parser)
110+ topic = pushMessage.topic?.topic
111+ }
112+ } catch (e: XmlPullParserException ) {
113+ logger.log(Level .WARNING , " Couldn't parse push message" , e)
114+ }
115+
116+ return topic
117+ }
118+
119+ }
0 commit comments