{"action":"created","comment":{"url":"https://api.github.com/repos/syncthing/syncthing-android/pulls/comments/34735816","id":34735816,"diff_hunk":"@@ -0,0 +1,90 @@\n+package com.nutomic.syncthingandroid.syncthing;\r\n+\r\n+import android.content.Context;\r\n+import android.content.Intent;\r\n+import android.os.Bundle;\r\n+import android.util.Log;\r\n+import android.widget.Toast;\r\n+\r\n+import com.nutomic.syncthingandroid.util.AsyncState;\r\n+\r\n+import java.util.concurrent.ExecutionException;\r\n+import java.util.concurrent.Executors;\r\n+import java.util.concurrent.ScheduledExecutorService;\r\n+import java.util.concurrent.ScheduledFuture;\r\n+import java.util.concurrent.TimeUnit;\r\n+\r\n+/**\r\n+ * As soon as the WebGui is available this event processor starts polling the event API.\r\n+ * All events are converted to broadcast intents within the base URL com.nutomic.syncthingandroid.event.\r\n+ * The type of the event is appended to the base URL to form the intent name. All parameters are\r\n+ * passed along with the event as a parcelable.\r\n+ * Which events are processed is defined within @see(RestApi.getEvents).\r\n+ */\r\n+public class EventProcessor implements SyncthingService.OnWebGuiAvailableListener {\r\n+\r\n+    private static final String TAG = \"EventProcessor\";\r\n+\r\n+    private static final String EVENT_BASE_URL = \"com.nutomic.syncthingandroid.event\";\r\n+\r\n+    /**\r\n+     * Minimum interval in ms at which the events are polled from syncthing and processed.\r\n+     * This intervall will not wake up the device to save battery power.\r\n+     */\r\n+    public static final int EVENT_UPDATE_INTERVAL = 10000;\r\n+\r\n+    private final ScheduledExecutorService mScheduledExecutor =  Executors.newScheduledThreadPool(1);\r\n+    private ScheduledFuture<?> mEventPollerFuture;\r\n+\r\n+    private final Context mContext;\r\n+    private final RestApi mApi;\r\n+\r\n+    public EventProcessor(Context context, RestApi api) {\r\n+        mContext = context;\r\n+        mApi = api;\r\n+    }\r\n+\r\n+    @Override\r\n+    public void onWebGuiAvailable() {\r\n+        mEventPollerFuture = mScheduledExecutor.scheduleAtFixedRate(new Runnable() {\r\n+\r\n+            private long mLastEventId = 0;\r\n+            private AsyncState currentState = null;\r\n+\r\n+            @Override\r\n+            public void run() {\r\n+                // Only read the events if the last call already returned.\r\n+                if ((currentState == null) || (!currentState.isRunning()))\r\n+                {\r\n+                    Log.d(TAG, \"Reading events starting with id \" + mLastEventId);\r\n+\r\n+                    currentState = mApi.getEvents(mLastEventId, new RestApi.OnReceiveEventListener() {\r\n+                        @Override\r\n+                        public void onEvent(final long id, final String eventType, final Bundle eventData) {\r\n+                            Intent intent = new Intent();\r\n+                            intent.setAction(EVENT_BASE_URL + \".\" + eventType);\r\n+                            intent.replaceExtras(eventData);\r\n+\r\n+                            mContext.sendBroadcast(intent);\r\n+\r\n+                            Log.d(TAG, \"Event broadcast for \" + eventType);\r\n+                        }\r\n+\r\n+                        @Override\r\n+                        public void onDone(long id) {\r\n+                            if (mLastEventId < id) mLastEventId = id;\r\n+                        }\r\n+                    });\r\n+                }\r\n+            }\r\n+        }, EVENT_UPDATE_INTERVAL, EVENT_UPDATE_INTERVAL, TimeUnit.MILLISECONDS);\r","path":"src/main/java/com/nutomic/syncthingandroid/syncthing/EventProcessor.java","position":80,"original_position":80,"commit_id":"3ff31c0a3af5ca90f21006ef0e6e363113a933af","original_commit_id":"3ff31c0a3af5ca90f21006ef0e6e363113a933af","user":{"login":"Nutomic","id":1044450,"avatar_url":"https://avatars.githubusercontent.com/u/1044450?v=3","gravatar_id":"","url":"https://api.github.com/users/Nutomic","html_url":"https://github.com/Nutomic","followers_url":"https://api.github.com/users/Nutomic/followers","following_url":"https://api.github.com/users/Nutomic/following{/other_user}","gists_url":"https://api.github.com/users/Nutomic/gists{/gist_id}","starred_url":"https://api.github.com/users/Nutomic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Nutomic/subscriptions","organizations_url":"https://api.github.com/users/Nutomic/orgs","repos_url":"https://api.github.com/users/Nutomic/repos","events_url":"https://api.github.com/users/Nutomic/events{/privacy}","received_events_url":"https://api.github.com/users/Nutomic/received_events","type":"User","site_admin":false},"body":"Shouldn't `initialDelay` be 0, so it starts immediately?","created_at":"2015-07-15T22:10:41Z","updated_at":"2015-07-15T22:10:41Z","html_url":"https://github.com/syncthing/syncthing-android/pull/431#discussion_r34735816","pull_request_url":"https://api.github.com/repos/syncthing/syncthing-android/pulls/431","_links":{"self":{"href":"https://api.github.com/repos/syncthing/syncthing-android/pulls/comments/34735816"},"html":{"href":"https://github.com/syncthing/syncthing-android/pull/431#discussion_r34735816"},"pull_request":{"href":"https://api.github.com/repos/syncthing/syncthing-android/pulls/431"}}},"pull_request":{"url":"https://api.github.com/repos/syncthing/syncthing-android/pulls/431","id":40056992,"html_url":"https://github.com/syncthing/syncthing-android/pull/431","diff_url":"https://github.com/syncthing/syncthing-android/pull/431.diff","patch_url":"https://github.com/syncthing/syncthing-android/pull/431.patch","issue_url":"https://api.github.com/repos/syncthing/syncthing-android/issues/431","number":431,"state":"open","locked":false,"title":"Asynchronous event API","user":{"login":"FlashSystems","id":10105345,"avatar_url":"https://avatars.githubusercontent.com/u/10105345?v=3","gravatar_id":"","url":"https://api.github.com/users/FlashSystems","html_url":"https://github.com/FlashSystems","followers_url":"https://api.github.com/users/FlashSystems/followers","following_url":"https://api.github.com/users/FlashSystems/following{/other_user}","gists_url":"https://api.github.com/users/FlashSystems/gists{/gist_id}","starred_url":"https://api.github.com/users/FlashSystems/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/FlashSystems/subscriptions","organizations_url":"https://api.github.com/users/FlashSystems/orgs","repos_url":"https://api.github.com/users/FlashSystems/repos","events_url":"https://api.github.com/users/FlashSystems/events{/privacy}","received_events_url":"https://api.github.com/users/FlashSystems/received_events","type":"User","site_admin":false},"body":"I had a go at implementing the event API. It works asynchronous from the other REST calls because synchting is blocking if no events are available. Specific events (currently ItemFinished, FolderCompletion, DeviceConnected, DeviceDiscovered, StateChanged) are converted to broadcast intents with the base URI com.nutomic.syncthingandroid.event. A itemfinished event is sent as a broadcast intent of com.nutomic.syncthingandroid.event.itemfinished. This allows the service to implement a broadcast receiver and act according to the event it got (eg. update the media database, show a message, etc.). More than this, it enables other applications to register for the broadcast intents and act on them (eg. a widget showing the last synced file).\r\n\r\nThis code is meant as a first try and base for discussion. Some things are currently missing:\r\n- If syncthing is restarted, previous events are lost. Before stopping syncthing remaining events should be processed.\r\n- No tests.\r\n- The broadcast intents must be used and converted into useful actions.\r\n- When restarting syncthing the event processor must be correctly restarted also. Currently this is not always working.\r\n\r\nBefore I go any further I'd like to know what you think about this idea. Is converting the events into broadcast intents a way to implement this or does it collide with your design ideas for this app?","created_at":"2015-07-15T18:58:49Z","updated_at":"2015-07-15T22:10:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d6dc0e83cb9a1c1d10acc6b7f3615e5ef81dbdf5","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/syncthing/syncthing-android/pulls/431/commits","review_comments_url":"https://api.github.com/repos/syncthing/syncthing-android/pulls/431/comments","review_comment_url":"https://api.github.com/repos/syncthing/syncthing-android/pulls/comments{/number}","comments_url":"https://api.github.com/repos/syncthing/syncthing-android/issues/431/comments","statuses_url":"https://api.github.com/repos/syncthing/syncthing-android/statuses/3ff31c0a3af5ca90f21006ef0e6e363113a933af","head":{"label":"FlashSystems:master","ref":"master","sha":"3ff31c0a3af5ca90f21006ef0e6e363113a933af","user":{"login":"FlashSystems","id":10105345,"avatar_url":"https://avatars.githubusercontent.com/u/10105345?v=3","gravatar_id":"","url":"https://api.github.com/users/FlashSystems","html_url":"https://github.com/FlashSystems","followers_url":"https://api.github.com/users/FlashSystems/followers","following_url":"https://api.github.com/users/FlashSystems/following{/other_user}","gists_url":"https://api.github.com/users/FlashSystems/gists{/gist_id}","starred_url":"https://api.github.com/users/FlashSystems/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/FlashSystems/subscriptions","organizations_url":"https://api.github.com/users/FlashSystems/orgs","repos_url":"https://api.github.com/users/FlashSystems/repos","events_url":"https://api.github.com/users/FlashSystems/events{/privacy}","received_events_url":"https://api.github.com/users/FlashSystems/received_events","type":"User","site_admin":false},"repo":{"id":39088782,"name":"syncthing-android","full_name":"FlashSystems/syncthing-android","owner":{"login":"FlashSystems","id":10105345,"avatar_url":"https://avatars.githubusercontent.com/u/10105345?v=3","gravatar_id":"","url":"https://api.github.com/users/FlashSystems","html_url":"https://github.com/FlashSystems","followers_url":"https://api.github.com/users/FlashSystems/followers","following_url":"https://api.github.com/users/FlashSystems/following{/other_user}","gists_url":"https://api.github.com/users/FlashSystems/gists{/gist_id}","starred_url":"https://api.github.com/users/FlashSystems/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/FlashSystems/subscriptions","organizations_url":"https://api.github.com/users/FlashSystems/orgs","repos_url":"https://api.github.com/users/FlashSystems/repos","events_url":"https://api.github.com/users/FlashSystems/events{/privacy}","received_events_url":"https://api.github.com/users/FlashSystems/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/FlashSystems/syncthing-android","description":"Wrapper of syncthing for Android.","fork":true,"url":"https://api.github.com/repos/FlashSystems/syncthing-android","forks_url":"https://api.github.com/repos/FlashSystems/syncthing-android/forks","keys_url":"https://api.github.com/repos/FlashSystems/syncthing-android/keys{/key_id}","collaborators_url":"https://api.github.com/repos/FlashSystems/syncthing-android/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/FlashSystems/syncthing-android/teams","hooks_url":"https://api.github.com/repos/FlashSystems/syncthing-android/hooks","issue_events_url":"https://api.github.com/repos/FlashSystems/syncthing-android/issues/events{/number}","events_url":"https://api.github.com/repos/FlashSystems/syncthing-android/events","assignees_url":"https://api.github.com/repos/FlashSystems/syncthing-android/assignees{/user}","branches_url":"https://api.github.com/repos/FlashSystems/syncthing-android/branches{/branch}","tags_url":"https://api.github.com/repos/FlashSystems/syncthing-android/tags","blobs_url":"https://api.github.com/repos/FlashSystems/syncthing-android/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/FlashSystems/syncthing-android/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/FlashSystems/syncthing-android/git/refs{/sha}","trees_url":"https://api.github.com/repos/FlashSystems/syncthing-android/git/trees{/sha}","statuses_url":"https://api.github.com/repos/FlashSystems/syncthing-android/statuses/{sha}","languages_url":"https://api.github.com/repos/FlashSystems/syncthing-android/languages","stargazers_url":"https://api.github.com/repos/FlashSystems/syncthing-android/stargazers","contributors_url":"https://api.github.com/repos/FlashSystems/syncthing-android/contributors","subscribers_url":"https://api.github.com/repos/FlashSystems/syncthing-android/subscribers","subscription_url":"https://api.github.com/repos/FlashSystems/syncthing-android/subscription","commits_url":"https://api.github.com/repos/FlashSystems/syncthing-android/commits{/sha}","git_commits_url":"https://api.github.com/repos/FlashSystems/syncthing-android/git/commits{/sha}","comments_url":"https://api.github.com/repos/FlashSystems/syncthing-android/comments{/number}","issue_comment_url":"https://api.github.com/repos/FlashSystems/syncthing-android/issues/comments{/number}","contents_url":"https://api.github.com/repos/FlashSystems/syncthing-android/contents/{+path}","compare_url":"https://api.github.com/repos/FlashSystems/syncthing-android/compare/{base}...{head}","merges_url":"https://api.github.com/repos/FlashSystems/syncthing-android/merges","archive_url":"https://api.github.com/repos/FlashSystems/syncthing-android/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/FlashSystems/syncthing-android/downloads","issues_url":"https://api.github.com/repos/FlashSystems/syncthing-android/issues{/number}","pulls_url":"https://api.github.com/repos/FlashSystems/syncthing-android/pulls{/number}","milestones_url":"https://api.github.com/repos/FlashSystems/syncthing-android/milestones{/number}","notifications_url":"https://api.github.com/repos/FlashSystems/syncthing-android/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/FlashSystems/syncthing-android/labels{/name}","releases_url":"https://api.github.com/repos/FlashSystems/syncthing-android/releases{/id}","created_at":"2015-07-14T17:05:21Z","updated_at":"2015-07-14T17:05:21Z","pushed_at":"2015-07-15T18:40:57Z","git_url":"git://github.com/FlashSystems/syncthing-android.git","ssh_url":"git@github.com:FlashSystems/syncthing-android.git","clone_url":"https://github.com/FlashSystems/syncthing-android.git","svn_url":"https://github.com/FlashSystems/syncthing-android","homepage":"https://syncthing.net/","size":6002,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"syncthing:master","ref":"master","sha":"fcf650652737683bd587182965c51b3e0b98ff10","user":{"login":"syncthing","id":7628018,"avatar_url":"https://avatars.githubusercontent.com/u/7628018?v=3","gravatar_id":"","url":"https://api.github.com/users/syncthing","html_url":"https://github.com/syncthing","followers_url":"https://api.github.com/users/syncthing/followers","following_url":"https://api.github.com/users/syncthing/following{/other_user}","gists_url":"https://api.github.com/users/syncthing/gists{/gist_id}","starred_url":"https://api.github.com/users/syncthing/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/syncthing/subscriptions","organizations_url":"https://api.github.com/users/syncthing/orgs","repos_url":"https://api.github.com/users/syncthing/repos","events_url":"https://api.github.com/users/syncthing/events{/privacy}","received_events_url":"https://api.github.com/users/syncthing/received_events","type":"Organization","site_admin":false},"repo":{"id":17089728,"name":"syncthing-android","full_name":"syncthing/syncthing-android","owner":{"login":"syncthing","id":7628018,"avatar_url":"https://avatars.githubusercontent.com/u/7628018?v=3","gravatar_id":"","url":"https://api.github.com/users/syncthing","html_url":"https://github.com/syncthing","followers_url":"https://api.github.com/users/syncthing/followers","following_url":"https://api.github.com/users/syncthing/following{/other_user}","gists_url":"https://api.github.com/users/syncthing/gists{/gist_id}","starred_url":"https://api.github.com/users/syncthing/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/syncthing/subscriptions","organizations_url":"https://api.github.com/users/syncthing/orgs","repos_url":"https://api.github.com/users/syncthing/repos","events_url":"https://api.github.com/users/syncthing/events{/privacy}","received_events_url":"https://api.github.com/users/syncthing/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/syncthing/syncthing-android","description":"Wrapper of syncthing for Android.","fork":false,"url":"https://api.github.com/repos/syncthing/syncthing-android","forks_url":"https://api.github.com/repos/syncthing/syncthing-android/forks","keys_url":"https://api.github.com/repos/syncthing/syncthing-android/keys{/key_id}","collaborators_url":"https://api.github.com/repos/syncthing/syncthing-android/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/syncthing/syncthing-android/teams","hooks_url":"https://api.github.com/repos/syncthing/syncthing-android/hooks","issue_events_url":"https://api.github.com/repos/syncthing/syncthing-android/issues/events{/number}","events_url":"https://api.github.com/repos/syncthing/syncthing-android/events","assignees_url":"https://api.github.com/repos/syncthing/syncthing-android/assignees{/user}","branches_url":"https://api.github.com/repos/syncthing/syncthing-android/branches{/branch}","tags_url":"https://api.github.com/repos/syncthing/syncthing-android/tags","blobs_url":"https://api.github.com/repos/syncthing/syncthing-android/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/syncthing/syncthing-android/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/syncthing/syncthing-android/git/refs{/sha}","trees_url":"https://api.github.com/repos/syncthing/syncthing-android/git/trees{/sha}","statuses_url":"https://api.github.com/repos/syncthing/syncthing-android/statuses/{sha}","languages_url":"https://api.github.com/repos/syncthing/syncthing-android/languages","stargazers_url":"https://api.github.com/repos/syncthing/syncthing-android/stargazers","contributors_url":"https://api.github.com/repos/syncthing/syncthing-android/contributors","subscribers_url":"https://api.github.com/repos/syncthing/syncthing-android/subscribers","subscription_url":"https://api.github.com/repos/syncthing/syncthing-android/subscription","commits_url":"https://api.github.com/repos/syncthing/syncthing-android/commits{/sha}","git_commits_url":"https://api.github.com/repos/syncthing/syncthing-android/git/commits{/sha}","comments_url":"https://api.github.com/repos/syncthing/syncthing-android/comments{/number}","issue_comment_url":"https://api.github.com/repos/syncthing/syncthing-android/issues/comments{/number}","contents_url":"https://api.github.com/repos/syncthing/syncthing-android/contents/{+path}","compare_url":"https://api.github.com/repos/syncthing/syncthing-android/compare/{base}...{head}","merges_url":"https://api.github.com/repos/syncthing/syncthing-android/merges","archive_url":"https://api.github.com/repos/syncthing/syncthing-android/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/syncthing/syncthing-android/downloads","issues_url":"https://api.github.com/repos/syncthing/syncthing-android/issues{/number}","pulls_url":"https://api.github.com/repos/syncthing/syncthing-android/pulls{/number}","milestones_url":"https://api.github.com/repos/syncthing/syncthing-android/milestones{/number}","notifications_url":"https://api.github.com/repos/syncthing/syncthing-android/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/syncthing/syncthing-android/labels{/name}","releases_url":"https://api.github.com/repos/syncthing/syncthing-android/releases{/id}","created_at":"2014-02-22T17:15:19Z","updated_at":"2015-07-11T04:07:43Z","pushed_at":"2015-07-15T18:58:50Z","git_url":"git://github.com/syncthing/syncthing-android.git","ssh_url":"git@github.com:syncthing/syncthing-android.git","clone_url":"https://github.com/syncthing/syncthing-android.git","svn_url":"https://github.com/syncthing/syncthing-android","homepage":"https://syncthing.net/","size":6002,"stargazers_count":348,"watchers_count":348,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":53,"mirror_url":null,"open_issues_count":41,"forks":53,"open_issues":41,"watchers":348,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/syncthing/syncthing-android/pulls/431"},"html":{"href":"https://github.com/syncthing/syncthing-android/pull/431"},"issue":{"href":"https://api.github.com/repos/syncthing/syncthing-android/issues/431"},"comments":{"href":"https://api.github.com/repos/syncthing/syncthing-android/issues/431/comments"},"review_comments":{"href":"https://api.github.com/repos/syncthing/syncthing-android/pulls/431/comments"},"review_comment":{"href":"https://api.github.com/repos/syncthing/syncthing-android/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/syncthing/syncthing-android/pulls/431/commits"},"statuses":{"href":"https://api.github.com/repos/syncthing/syncthing-android/statuses/3ff31c0a3af5ca90f21006ef0e6e363113a933af"}}}}