1 | /* |
2 | * Tomdroid |
3 | * Tomboy on Android |
4 | * http://www.launchpad.net/tomdroid |
5 | * |
6 | * Copyright 2009, Olivier Bilodeau <olivier@bottomlesspit.org> |
7 | * Copyright 2009, Benoit Garret <benoit.garret_launchpad@gadz.org> |
8 | * Copyright 2010, Rodja Trappe <mail@rodja.net> |
9 | * |
10 | * This file is part of Tomdroid. |
11 | * |
12 | * Tomdroid is free software: you can redistribute it and/or modify |
13 | * it under the terms of the GNU General Public License as published by |
14 | * the Free Software Foundation, either version 3 of the License, or |
15 | * (at your option) any later version. |
16 | * |
17 | * Tomdroid is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | * GNU General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU General Public License |
23 | * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. |
24 | */ |
25 | package org.tomdroid.sync; |
26 | |
27 | import java.util.ArrayList; |
28 | import java.util.HashMap; |
29 | import java.util.concurrent.ExecutorService; |
30 | import java.util.concurrent.Executors; |
31 | |
32 | import org.tomdroid.Note; |
33 | import org.tomdroid.NoteManager; |
34 | import org.tomdroid.ui.Tomdroid; |
35 | import org.tomdroid.util.ErrorList; |
36 | |
37 | import org.tomdroid.R; |
38 | import android.app.Activity; |
39 | import android.database.Cursor; |
40 | import android.os.Handler; |
41 | import android.os.Message; |
42 | import android.util.Log; |
43 | import android.widget.Toast; |
44 | |
45 | public abstract class SyncService { |
46 | |
47 | private static final String TAG = "SyncService"; |
48 | |
49 | private Activity activity; |
50 | private final ExecutorService pool; |
51 | private final static int poolSize = 1; |
52 | |
53 | private Handler handler; |
54 | |
55 | /** |
56 | * Contains the synchronization errors. These are stored while synchronization occurs |
57 | * and sent to the main UI along with the PARSING_COMPLETE message. |
58 | */ |
59 | private ErrorList syncErrors; |
60 | private int syncProgress = 100; |
61 | |
62 | // handler messages |
63 | public final static int PARSING_COMPLETE = 1; |
64 | public final static int PARSING_FAILED = 2; |
65 | public final static int PARSING_NO_NOTES = 3; |
66 | public final static int NO_INTERNET = 4; |
67 | public final static int NO_SD_CARD = 5; |
68 | public final static int SYNC_PROGRESS = 6; |
69 | |
70 | public SyncService(Activity activity, Handler handler) { |
71 | |
72 | this.activity = activity; |
73 | this.handler = handler; |
74 | pool = Executors.newFixedThreadPool(poolSize); |
75 | } |
76 | |
77 | public void startSynchronization() { |
78 | |
79 | if (syncProgress != 100){ |
80 | Toast.makeText(activity, activity.getString(R.string.messageSyncAlreadyInProgress), Toast.LENGTH_SHORT).show(); |
81 | return; |
82 | } |
83 | |
84 | syncErrors = new ErrorList(); |
85 | sync(); |
86 | } |
87 | |
88 | protected abstract void sync(); |
89 | public abstract boolean needsServer(); |
90 | public abstract boolean needsAuth(); |
91 | |
92 | /** |
93 | * @return An unique identifier, not visible to the user. |
94 | */ |
95 | |
96 | public abstract String getName(); |
97 | |
98 | /** |
99 | * @return An human readable name, used in the preferences to distinguish the different sync services. |
100 | */ |
101 | |
102 | public abstract String getDescription(); |
103 | |
104 | /** |
105 | * Execute code in a separate thread. |
106 | * Use this for blocking and/or cpu intensive operations and thus avoid blocking the UI. |
107 | * |
108 | * @param r The Runner subclass to execute |
109 | */ |
110 | |
111 | protected void execInThread(Runnable r) { |
112 | |
113 | pool.execute(r); |
114 | } |
115 | |
116 | /** |
117 | * Execute code in a separate thread. |
118 | * Any exception thrown by the thread will be added to the error list |
119 | * @param r The runner subclass to execute |
120 | */ |
121 | protected void syncInThread(final Runnable r) { |
122 | Runnable task = new Runnable() { |
123 | public void run() { |
124 | try { |
125 | r.run(); |
126 | } catch(Exception e) { |
127 | sendMessage(PARSING_FAILED, ErrorList.createError("System Error", "system", e)); |
128 | } |
129 | } |
130 | }; |
131 | |
132 | execInThread(task); |
133 | } |
134 | |
135 | /** |
136 | * Insert a note in the content provider. The identifier for the notes is the guid. |
137 | * |
138 | * @param note The note to insert. |
139 | */ |
140 | |
141 | protected void insertNote(Note note) { |
142 | |
143 | NoteManager.putNote(this.activity, note); |
144 | } |
145 | |
146 | /** |
147 | * Delete notes in the content provider. The guids passed identify the notes existing |
148 | * on the remote end (ie. that shouldn't be deleted). |
149 | * |
150 | * @param remoteGuids The notes NOT to delete. |
151 | */ |
152 | |
153 | protected void deleteNotes(ArrayList<String> remoteGuids) { |
154 | |
155 | Cursor localGuids = NoteManager.getGuids(this.activity); |
156 | |
157 | // cursor must not be null and must return more than 0 entry |
158 | if (!(localGuids == null || localGuids.getCount() == 0)) { |
159 | |
160 | String localGuid; |
161 | |
162 | localGuids.moveToFirst(); |
163 | |
164 | do { |
165 | localGuid = localGuids.getString(localGuids.getColumnIndexOrThrow(Note.GUID)); |
166 | |
167 | if(!remoteGuids.contains(localGuid)) { |
168 | int id = localGuids.getInt(localGuids.getColumnIndexOrThrow(Note.ID)); |
169 | NoteManager.deleteNote(this.activity, id); |
170 | } |
171 | |
172 | } while (localGuids.moveToNext()); |
173 | |
174 | } else { |
175 | |
176 | // TODO send an error to the user |
177 | if (Tomdroid.LOGGING_ENABLED) Log.d(TAG, "Cursor returned null or 0 notes"); |
178 | } |
179 | } |
180 | |
181 | /** |
182 | * Send a message to the main UI. |
183 | * |
184 | * @param message The message id to send, the PARSING_* or NO_INTERNET attributes can be used. |
185 | */ |
186 | |
187 | protected void sendMessage(int message) { |
188 | |
189 | if(!sendMessage(message, null)) { |
190 | handler.sendEmptyMessage(message); |
191 | } |
192 | } |
193 | |
194 | protected boolean sendMessage(int message_id, HashMap<String, Object> payload) { |
195 | |
196 | switch(message_id) { |
197 | case PARSING_FAILED: |
198 | syncErrors.add(payload); |
199 | return true; |
200 | case PARSING_COMPLETE: |
201 | Message message = handler.obtainMessage(PARSING_COMPLETE, syncErrors); |
202 | handler.sendMessage(message); |
203 | return true; |
204 | } |
205 | |
206 | return false; |
207 | } |
208 | |
209 | /** |
210 | * Update the synchronization progress |
211 | * |
212 | * @param progress |
213 | */ |
214 | |
215 | protected void setSyncProgress(int progress) { |
216 | synchronized (TAG) { |
217 | Log.v(TAG, "sync progress: " + progress); |
218 | Message progressMessage = new Message(); |
219 | progressMessage.what = SYNC_PROGRESS; |
220 | progressMessage.arg1 = progress; |
221 | progressMessage.arg2 = syncProgress; |
222 | |
223 | handler.sendMessage(progressMessage); |
224 | syncProgress = progress; |
225 | } |
226 | } |
227 | |
228 | protected int getSyncProgress(){ |
229 | synchronized (TAG) { |
230 | return syncProgress; |
231 | } |
232 | } |
233 | |
234 | public boolean isSyncable() { |
235 | return getSyncProgress() == 100; |
236 | } |
237 | } |