mirror of
https://github.com/etesync/android
synced 2025-01-11 00:01:12 +00:00
Collections refresh
This commit is contained in:
parent
fc29988dc6
commit
af71ed8bc5
@ -7,6 +7,7 @@ import java.io.IOException;
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.ui.setup.DavResourceFinder;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
@ -8,16 +8,45 @@
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Service;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.UrlUtils;
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.dav4android.exception.NotFoundException;
|
||||
import at.bitfire.dav4android.property.AddressbookDescription;
|
||||
import at.bitfire.dav4android.property.AddressbookHomeSet;
|
||||
import at.bitfire.dav4android.property.CalendarDescription;
|
||||
import at.bitfire.dav4android.property.CalendarHomeSet;
|
||||
import at.bitfire.dav4android.property.DisplayName;
|
||||
import at.bitfire.dav4android.property.ResourceType;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB.*;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class DavService extends Service {
|
||||
|
||||
public static final String
|
||||
@ -85,23 +114,177 @@ public class DavService extends Service {
|
||||
*/
|
||||
|
||||
private class RefreshCollections implements Runnable {
|
||||
final long serviceId;
|
||||
final long service;
|
||||
final OpenHelper dbHelper;
|
||||
SQLiteDatabase db;
|
||||
|
||||
RefreshCollections(long davServiceId) {
|
||||
this.serviceId = davServiceId;
|
||||
this.service = davServiceId;
|
||||
dbHelper = new OpenHelper(DavService.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Constants.log.debug("RefreshCollections.Runner STARTING {}", serviceId);
|
||||
try {
|
||||
Thread.currentThread().sleep(10000);
|
||||
} catch (InterruptedException e) {
|
||||
db = dbHelper.getWritableDatabase();
|
||||
db.beginTransactionNonExclusive();
|
||||
|
||||
String serviceType = serviceType();
|
||||
Constants.log.info("[DavService {}] Refreshing {} collections", service, serviceType);
|
||||
|
||||
// create authenticating OkHttpClient (credentials taken from account settings)
|
||||
OkHttpClient httpClient = httpClient();
|
||||
|
||||
// refresh home sets
|
||||
Set<HttpUrl> homeSets = readHomeSets();
|
||||
HttpUrl principal = readPrincipal();
|
||||
if (principal != null) {
|
||||
Constants.log.debug("[DavService {}] Querying principal for home sets", service);
|
||||
DavResource dav = new DavResource(null, httpClient, principal);
|
||||
if (Services.SERVICE_CARDDAV.equals(serviceType)) {
|
||||
dav.propfind(0, AddressbookHomeSet.NAME);
|
||||
AddressbookHomeSet addressbookHomeSet = (AddressbookHomeSet)dav.properties.get(AddressbookHomeSet.NAME);
|
||||
if (addressbookHomeSet != null)
|
||||
for (String href : addressbookHomeSet.hrefs)
|
||||
homeSets.add(UrlUtils.withTrailingSlash(dav.location.resolve(href)));
|
||||
} else if (Services.SERVICE_CALDAV.equals(serviceType)) {
|
||||
dav.propfind(0, CalendarHomeSet.NAME);
|
||||
CalendarHomeSet calendarHomeSet = (CalendarHomeSet)dav.properties.get(CalendarHomeSet.NAME);
|
||||
if (calendarHomeSet != null)
|
||||
for (String href : calendarHomeSet.hrefs)
|
||||
homeSets.add(UrlUtils.withTrailingSlash(dav.location.resolve(href)));
|
||||
}
|
||||
}
|
||||
saveHomeSets(homeSets);
|
||||
|
||||
// refresh collections in home sets
|
||||
Map<HttpUrl, CollectionInfo> collections = readCollections();
|
||||
for (Iterator<HttpUrl> iterator = homeSets.iterator(); iterator.hasNext();) {
|
||||
HttpUrl homeSet = iterator.next();
|
||||
Constants.log.debug("[DavService {}] Listing home set {}", service, homeSet);
|
||||
|
||||
DavResource dav = new DavResource(null, httpClient, homeSet);
|
||||
try {
|
||||
dav.propfind(1, CollectionInfo.DAV_PROPERTIES);
|
||||
for (DavResource member : dav.members) {
|
||||
CollectionInfo info = CollectionInfo.fromDavResource(member);
|
||||
info.confirmed = true;
|
||||
Constants.log.debug("[DavService {}] Found collection {}", service, info);
|
||||
|
||||
if ((serviceType.equals(Services.SERVICE_CARDDAV) && info.type == CollectionInfo.Type.ADDRESS_BOOK) ||
|
||||
(serviceType.equals(Services.SERVICE_CALDAV) && info.type == CollectionInfo.Type.CALENDAR))
|
||||
collections.put(member.location, info);
|
||||
}
|
||||
} catch(NotFoundException e) {
|
||||
// 404, remove home set
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// check/refresh unconfirmed collections
|
||||
for (Iterator<Map.Entry<HttpUrl, CollectionInfo>> iterator = collections.entrySet().iterator(); iterator.hasNext();) {
|
||||
Map.Entry<HttpUrl, CollectionInfo> entry = iterator.next();
|
||||
HttpUrl url = entry.getKey();
|
||||
CollectionInfo info = entry.getValue();
|
||||
|
||||
if (!info.confirmed)
|
||||
try {
|
||||
DavResource dav = new DavResource(null, httpClient, url);
|
||||
dav.propfind(0, CollectionInfo.DAV_PROPERTIES);
|
||||
info = CollectionInfo.fromDavResource(dav);
|
||||
info.confirmed = true;
|
||||
|
||||
// remove unusable collections
|
||||
if ((serviceType.equals(Services.SERVICE_CARDDAV) && info.type != CollectionInfo.Type.ADDRESS_BOOK) ||
|
||||
(serviceType.equals(Services.SERVICE_CALDAV) && info.type != CollectionInfo.Type.CALENDAR))
|
||||
iterator.remove();
|
||||
} catch(NotFoundException e) {
|
||||
// 404, remove collection
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
saveCollections(collections.values());
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} catch (SQLiteException | IOException|HttpException|DavException e) {
|
||||
Constants.log.error("Couldn't refresh collection list", e);
|
||||
} finally {
|
||||
Constants.log.debug("RefreshCollections.Runner FINISHED {}", serviceId);
|
||||
runningRefresh.remove(serviceId);
|
||||
db.endTransaction();
|
||||
dbHelper.close();
|
||||
|
||||
runningRefresh.remove(service);
|
||||
for (RefreshingStatusListener listener : refreshingStatusListeners)
|
||||
listener.onDavRefreshStatusChanged(serviceId, false);
|
||||
listener.onDavRefreshStatusChanged(service, false);
|
||||
}
|
||||
}
|
||||
|
||||
private String serviceType() {
|
||||
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[]{Services.SERVICE}, Services.ID + "=?", new String[]{String.valueOf(service)}, null, null, null);
|
||||
if (cursor.moveToNext())
|
||||
return cursor.getString(0);
|
||||
else
|
||||
throw new IllegalArgumentException("Service not found");
|
||||
}
|
||||
|
||||
private OkHttpClient httpClient() {
|
||||
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[]{Services.ACCOUNT_NAME}, Services.ID + "=?", new String[]{String.valueOf(service)}, null, null, null);
|
||||
if (cursor.moveToNext()) {
|
||||
Account account = new Account(cursor.getString(0), Constants.ACCOUNT_TYPE);
|
||||
AccountSettings settings = new AccountSettings(DavService.this, account);
|
||||
|
||||
OkHttpClient httpClient = HttpClient.create(DavService.this);
|
||||
httpClient = HttpClient.addAuthentication(httpClient, settings.username(), settings.password(), settings.preemptiveAuth());
|
||||
return httpClient;
|
||||
} else
|
||||
throw new IllegalArgumentException("Service not found");
|
||||
}
|
||||
|
||||
private HttpUrl readPrincipal() {
|
||||
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[]{Services.PRINCIPAL}, Services.ID + "=?", new String[]{String.valueOf(service)}, null, null, null);
|
||||
if (cursor.moveToNext()) {
|
||||
String principal = cursor.getString(0);
|
||||
if (principal != null)
|
||||
return HttpUrl.parse(cursor.getString(0));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Set<HttpUrl> readHomeSets() {
|
||||
Set<HttpUrl> homeSets = new LinkedHashSet<>();
|
||||
@Cleanup Cursor cursor = db.query(HomeSets._TABLE, new String[]{HomeSets.URL}, HomeSets.SERVICE_ID + "=?", new String[]{String.valueOf(service)}, null, null, null);
|
||||
while (cursor.moveToNext())
|
||||
homeSets.add(HttpUrl.parse(cursor.getString(0)));
|
||||
return homeSets;
|
||||
}
|
||||
|
||||
private void saveHomeSets(Set<HttpUrl> homeSets) {
|
||||
db.delete(HomeSets._TABLE, HomeSets.SERVICE_ID + "=?", new String[]{String.valueOf(service)});
|
||||
for (HttpUrl homeSet : homeSets) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(HomeSets.SERVICE_ID, service);
|
||||
values.put(HomeSets.URL, homeSet.toString());
|
||||
db.insertOrThrow(HomeSets._TABLE, null, values);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<HttpUrl, CollectionInfo> readCollections() {
|
||||
Map<HttpUrl, CollectionInfo> collections = new LinkedHashMap<>();
|
||||
@Cleanup Cursor cursor = db.query(Collections._TABLE, Collections._COLUMNS, Collections.SERVICE_ID + "=?", new String[]{String.valueOf(service)}, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(cursor, values);
|
||||
collections.put(HttpUrl.parse(values.getAsString(Collections.URL)), CollectionInfo.fromDB(values));
|
||||
}
|
||||
return collections;
|
||||
}
|
||||
|
||||
private void saveCollections(Iterable<CollectionInfo> collections) {
|
||||
db.delete(Collections._TABLE, HomeSets.SERVICE_ID + "=?", new String[]{String.valueOf(service)});
|
||||
for (CollectionInfo collection : collections) {
|
||||
ContentValues values = collection.toDB();
|
||||
Constants.log.debug("Saving collection: {}", values);
|
||||
values.put(Collections.SERVICE_ID, service);
|
||||
db.insertWithOnConflict(Collections._TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,14 +8,11 @@
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import at.bitfire.davdroid.resource.DavResourceFinder;
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.CookieJar;
|
||||
import okhttp3.HttpUrl;
|
||||
|
127
app/src/main/java/at/bitfire/davdroid/model/CollectionInfo.java
Normal file
127
app/src/main/java/at/bitfire/davdroid/model/CollectionInfo.java
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.Property;
|
||||
import at.bitfire.dav4android.property.AddressbookDescription;
|
||||
import at.bitfire.dav4android.property.CalendarColor;
|
||||
import at.bitfire.dav4android.property.CalendarDescription;
|
||||
import at.bitfire.dav4android.property.CurrentUserPrivilegeSet;
|
||||
import at.bitfire.dav4android.property.DisplayName;
|
||||
import at.bitfire.dav4android.property.ResourceType;
|
||||
import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
|
||||
import lombok.ToString;
|
||||
import okhttp3.HttpUrl;
|
||||
import at.bitfire.davdroid.model.ServiceDB.*;
|
||||
|
||||
@ToString
|
||||
public class CollectionInfo {
|
||||
public long id;
|
||||
|
||||
public enum Type {
|
||||
ADDRESS_BOOK,
|
||||
CALENDAR
|
||||
};
|
||||
public Type type;
|
||||
|
||||
public String url;
|
||||
|
||||
public boolean readOnly;
|
||||
public String displayName, description;
|
||||
public Integer color;
|
||||
|
||||
public Boolean supportsVEVENT;
|
||||
public Boolean supportsVTODO;
|
||||
|
||||
// non-persistent properties
|
||||
public boolean confirmed;
|
||||
|
||||
|
||||
public static final Property.Name[] DAV_PROPERTIES = {
|
||||
ResourceType.NAME,
|
||||
CurrentUserPrivilegeSet.NAME,
|
||||
DisplayName.NAME,
|
||||
AddressbookDescription.NAME, CalendarDescription.NAME,
|
||||
CalendarColor.NAME, SupportedCalendarComponentSet.NAME
|
||||
};
|
||||
|
||||
public static CollectionInfo fromDavResource(DavResource dav) {
|
||||
CollectionInfo info = new CollectionInfo();
|
||||
info.url = dav.location.toString();
|
||||
|
||||
ResourceType type = (ResourceType)dav.properties.get(ResourceType.NAME);
|
||||
if (type != null) {
|
||||
if (type.types.contains(ResourceType.ADDRESSBOOK))
|
||||
info.type = Type.ADDRESS_BOOK;
|
||||
else if (type.types.contains(ResourceType.CALENDAR))
|
||||
info.type = Type.CALENDAR;
|
||||
}
|
||||
|
||||
boolean readOnly = false;
|
||||
CurrentUserPrivilegeSet privilegeSet = (CurrentUserPrivilegeSet)dav.properties.get(CurrentUserPrivilegeSet.NAME);
|
||||
if (privilegeSet != null)
|
||||
readOnly = !privilegeSet.mayWriteContent;
|
||||
|
||||
DisplayName displayName = (DisplayName)dav.properties.get(DisplayName.NAME);
|
||||
if (displayName != null && !displayName.displayName.isEmpty())
|
||||
info.displayName = displayName.displayName;
|
||||
|
||||
if (info.type == Type.ADDRESS_BOOK) {
|
||||
AddressbookDescription addressbookDescription = (AddressbookDescription)dav.properties.get(AddressbookDescription.NAME);
|
||||
if (addressbookDescription != null)
|
||||
info.description = addressbookDescription.description;
|
||||
|
||||
} else if (info.type == Type.CALENDAR) {
|
||||
CalendarDescription calendarDescription = (CalendarDescription)dav.properties.get(CalendarDescription.NAME);
|
||||
if (calendarDescription != null)
|
||||
info.description = calendarDescription.description;
|
||||
|
||||
CalendarColor calendarColor = (CalendarColor)dav.properties.get(CalendarColor.NAME);
|
||||
if (calendarColor != null)
|
||||
info.color = calendarColor.color;
|
||||
|
||||
info.supportsVEVENT = info.supportsVTODO = true;
|
||||
SupportedCalendarComponentSet supportedCalendarComponentSet = (SupportedCalendarComponentSet)dav.properties.get(SupportedCalendarComponentSet.NAME);
|
||||
if (supportedCalendarComponentSet != null) {
|
||||
info.supportsVEVENT = supportedCalendarComponentSet.supportsEvents;
|
||||
info.supportsVTODO = supportedCalendarComponentSet.supportsTasks;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
public static CollectionInfo fromDB(ContentValues values) {
|
||||
CollectionInfo info = new CollectionInfo();
|
||||
info.id = values.getAsLong(Collections.ID);
|
||||
info.url = values.getAsString(Collections.URL);
|
||||
info.displayName = values.getAsString(Collections.DISPLAY_NAME);
|
||||
info.description = values.getAsString(Collections.DESCRIPTION);
|
||||
info.color = values.getAsInteger(Collections.COLOR);
|
||||
info.supportsVEVENT = values.getAsBoolean(Collections.SUPPORTS_VEVENT);
|
||||
info.supportsVTODO = values.getAsBoolean(Collections.SUPPORTS_VTODO);
|
||||
return info;
|
||||
}
|
||||
|
||||
public ContentValues toDB() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Collections.URL, url);
|
||||
values.put(Collections.DISPLAY_NAME, displayName);
|
||||
values.put(Collections.DESCRIPTION, description);
|
||||
values.put(Collections.COLOR, color);
|
||||
values.put(Collections.SUPPORTS_VEVENT, supportsVEVENT);
|
||||
values.put(Collections.SUPPORTS_VTODO, supportsVTODO);
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
@ -6,15 +6,16 @@
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
package at.bitfire.davdroid.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Build;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.resource.DavResourceFinder;
|
||||
import at.bitfire.davdroid.ui.setup.DavResourceFinder;
|
||||
|
||||
public class ServiceDB {
|
||||
|
||||
@ -22,10 +23,10 @@ public class ServiceDB {
|
||||
public static final String
|
||||
_TABLE = "services",
|
||||
ID = "_id",
|
||||
ACCOUNT_NAME = "account_name",
|
||||
ACCOUNT_NAME = "accountName",
|
||||
SERVICE = "service",
|
||||
PRINCIPAL = "principal",
|
||||
LAST_REFRESH = "last_refresh";
|
||||
LAST_REFRESH = "lastRefresh";
|
||||
|
||||
// allowed values for SERVICE column
|
||||
public static final String
|
||||
@ -37,7 +38,7 @@ public class ServiceDB {
|
||||
public static final String
|
||||
_TABLE = "homesets",
|
||||
ID = "_id",
|
||||
SERVICE_ID = "service_id",
|
||||
SERVICE_ID = "serviceID",
|
||||
URL = "url";
|
||||
}
|
||||
|
||||
@ -45,17 +46,17 @@ public class ServiceDB {
|
||||
public static final String
|
||||
_TABLE = "collections",
|
||||
ID = "_id",
|
||||
SERVICE_ID = "service_id",
|
||||
SERVICE_ID = "serviceID",
|
||||
URL = "url",
|
||||
DISPLAY_NAME = "display_name",
|
||||
DESCRIPTION = "description";
|
||||
DISPLAY_NAME = "displayName",
|
||||
DESCRIPTION = "description",
|
||||
COLOR = "color",
|
||||
SUPPORTS_VEVENT = "supportsVEVENT",
|
||||
SUPPORTS_VTODO = "supportsVTODO";
|
||||
|
||||
public static ContentValues fromCollection(DavResourceFinder.Configuration.Collection collection) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DISPLAY_NAME, collection.getDisplayName());
|
||||
values.put(DESCRIPTION, collection.getDescription());
|
||||
return values;
|
||||
}
|
||||
public static String[] _COLUMNS = new String[] {
|
||||
ID, SERVICE_ID, URL, DISPLAY_NAME, DESCRIPTION, COLOR, SUPPORTS_VEVENT, SUPPORTS_VTODO
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -67,6 +68,14 @@ public class ServiceDB {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
db.setForeignKeyConstraintsEnabled(true);
|
||||
else
|
||||
db.execSQL("PRAGMA foreign_keys=ON;");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
Constants.log.info("Creating services database");
|
||||
@ -78,20 +87,26 @@ public class ServiceDB {
|
||||
Services.PRINCIPAL + " TEXT NULL, " +
|
||||
Services.LAST_REFRESH + " INTEGER NULL" +
|
||||
")");
|
||||
db.execSQL("CREATE UNIQUE INDEX services_account ON " + Services._TABLE + " (" + Services.ACCOUNT_NAME + "," + Services.SERVICE + ")");
|
||||
|
||||
db.execSQL("CREATE TABLE " + HomeSets._TABLE + "(" +
|
||||
HomeSets.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
HomeSets.SERVICE_ID + " INTEGER NOT NULL," +
|
||||
HomeSets.SERVICE_ID + " INTEGER NOT NULL REFERENCES " + Services._TABLE +" ON DELETE CASCADE," +
|
||||
HomeSets.URL + " TEXT NOT NULL" +
|
||||
")");
|
||||
db.execSQL("CREATE UNIQUE INDEX homesets_service_url ON " + HomeSets._TABLE + "(" + HomeSets.SERVICE_ID + "," + HomeSets.URL + ")");
|
||||
|
||||
db.execSQL("CREATE TABLE " + Collections._TABLE + "(" +
|
||||
Collections.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
Collections.SERVICE_ID + " INTEGER NOT NULL," +
|
||||
Collections.SERVICE_ID + " INTEGER NOT NULL REFERENCES " + Services._TABLE +" ON DELETE CASCADE," +
|
||||
Collections.URL + " TEXT NOT NULL," +
|
||||
Collections.DISPLAY_NAME + " TEXT NULL," +
|
||||
Collections.DESCRIPTION + " TEXT NULL" +
|
||||
Collections.DESCRIPTION + " TEXT NULL," +
|
||||
Collections.COLOR + " INTEGER NULL," +
|
||||
Collections.SUPPORTS_VEVENT + " INTEGER NULL," +
|
||||
Collections.SUPPORTS_VTODO + " INTEGER NULL" +
|
||||
")");
|
||||
db.execSQL("CREATE UNIQUE INDEX collections_service_url ON " + Collections._TABLE + "(" + Collections.SERVICE_ID + "," + Collections.URL + ")");
|
||||
}
|
||||
|
||||
@Override
|
@ -15,36 +15,42 @@ import android.accounts.AccountManagerFuture;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.LoaderManager;
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
import android.content.ServiceConnection;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.DavService;
|
||||
import at.bitfire.davdroid.syncadapter.ServiceDB.*;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import at.bitfire.davdroid.model.ServiceDB.OpenHelper;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Services;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenuItemClickListener, ServiceConnection, DavService.RefreshingStatusListener, LoaderManager.LoaderCallbacks<AccountActivity.AccountInfo> {
|
||||
@ -59,7 +65,7 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final String accountName = getIntent().getStringExtra(EXTRA_ACCOUNT_NAME);
|
||||
accountName = getIntent().getStringExtra(EXTRA_ACCOUNT_NAME);
|
||||
if (accountName == null)
|
||||
// invalid account name
|
||||
finish();
|
||||
@ -125,13 +131,13 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
|
||||
case R.id.refresh_address_books:
|
||||
Intent intent = new Intent(this, DavService.class);
|
||||
intent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
|
||||
intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.cardDavService);
|
||||
intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.carddav.id);
|
||||
startService(intent);
|
||||
break;
|
||||
case R.id.refresh_calendars:
|
||||
intent = new Intent(this, DavService.class);
|
||||
intent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
|
||||
intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.calDavService);
|
||||
intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.caldav.id);
|
||||
startService(intent);
|
||||
break;
|
||||
}
|
||||
@ -162,11 +168,14 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
|
||||
/* LOADERS AND LOADED DATA */
|
||||
|
||||
public static class AccountInfo {
|
||||
Long cardDavService;
|
||||
boolean cardDavRefreshing;
|
||||
ServiceInfo carddav, caldav;
|
||||
|
||||
Long calDavService;
|
||||
boolean calDavRefreshing;
|
||||
public static class ServiceInfo {
|
||||
long id;
|
||||
boolean refreshing;
|
||||
|
||||
List<CollectionInfo> collections;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -179,16 +188,28 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
|
||||
accountInfo = info;
|
||||
|
||||
CardView card = (CardView)findViewById(R.id.carddav);
|
||||
if (info.cardDavService != null) {
|
||||
if (info.carddav != null) {
|
||||
ProgressBar progress = (ProgressBar)findViewById(R.id.carddav_refreshing);
|
||||
progress.setVisibility(info.cardDavRefreshing ? View.VISIBLE : View.GONE);
|
||||
progress.setVisibility(info.carddav.refreshing ? View.VISIBLE : View.GONE);
|
||||
|
||||
ListView list = (ListView)findViewById(R.id.address_books);
|
||||
List<String> names = new LinkedList<>();
|
||||
for (CollectionInfo addrBook : info.carddav.collections)
|
||||
names.add(addrBook.displayName != null ? addrBook.displayName : addrBook.url);
|
||||
list.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_checked, android.R.id.text1, names));
|
||||
} else
|
||||
card.setVisibility(View.GONE);
|
||||
|
||||
card = (CardView)findViewById(R.id.caldav);
|
||||
if (info.calDavService != null) {
|
||||
if (info.caldav != null) {
|
||||
ProgressBar progress = (ProgressBar)findViewById(R.id.caldav_refreshing);
|
||||
progress.setVisibility(info.calDavRefreshing ? View.VISIBLE : View.GONE);
|
||||
progress.setVisibility(info.caldav.refreshing ? View.VISIBLE : View.GONE);
|
||||
|
||||
ListView list = (ListView)findViewById(R.id.calendars);
|
||||
List<String> names = new LinkedList<>();
|
||||
for (CollectionInfo calendar : info.caldav.collections)
|
||||
names.add(calendar.displayName != null ? calendar.displayName : calendar.url);
|
||||
list.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_checked, android.R.id.text1, names));
|
||||
} else
|
||||
card.setVisibility(View.GONE);
|
||||
}
|
||||
@ -251,11 +272,16 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
|
||||
|
||||
String service = cursor.getString(1);
|
||||
if (Services.SERVICE_CARDDAV.equals(service)) {
|
||||
info.cardDavService = id;
|
||||
info.cardDavRefreshing = davService.isRefreshing(id);
|
||||
info.carddav = new AccountInfo.ServiceInfo();
|
||||
info.carddav.id = id;
|
||||
info.carddav.refreshing = davService.isRefreshing(id);
|
||||
info.carddav.collections = readCollections(db, id);
|
||||
|
||||
} else if (Services.SERVICE_CALDAV.equals(service)) {
|
||||
info.calDavService = id;
|
||||
info.calDavRefreshing = davService.isRefreshing(id);
|
||||
info.caldav = new AccountInfo.ServiceInfo();
|
||||
info.caldav.id = id;
|
||||
info.caldav.refreshing = davService.isRefreshing(id);
|
||||
info.caldav.collections = readCollections(db, id);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@ -263,6 +289,17 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private List<CollectionInfo> readCollections(SQLiteDatabase db, long service) {
|
||||
List<CollectionInfo> collections = new LinkedList<>();
|
||||
@Cleanup Cursor cursor = db.query(Collections._TABLE, Collections._COLUMNS, Collections.SERVICE_ID + "=?", new String[]{String.valueOf(service)}, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(cursor, values);
|
||||
collections.add(CollectionInfo.fromDB(values));
|
||||
}
|
||||
return collections;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -11,14 +11,11 @@ package at.bitfire.davdroid.ui;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.OnAccountsUpdateListener;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
@ -41,8 +38,8 @@ import java.util.List;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.DavService;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.syncadapter.ServiceDB.OpenHelper;
|
||||
import at.bitfire.davdroid.syncadapter.ServiceDB.Services;
|
||||
import at.bitfire.davdroid.model.ServiceDB.OpenHelper;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Services;
|
||||
import lombok.Cleanup;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
|
@ -11,6 +11,7 @@ package at.bitfire.davdroid.ui.setup;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.Snackbar;
|
||||
@ -24,10 +25,11 @@ import android.widget.EditText;
|
||||
import java.util.Map;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.DavService;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.DavResourceFinder;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||
import at.bitfire.davdroid.syncadapter.ServiceDB.*;
|
||||
import at.bitfire.davdroid.model.ServiceDB.*;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
@ -56,7 +58,11 @@ public class AccountDetailsFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
DavResourceFinder.Configuration config = (DavResourceFinder.Configuration)getArguments().getSerializable(KEY_CONFIG);
|
||||
|
||||
final EditText editName = (EditText)v.findViewById(R.id.account_name);
|
||||
editName.setText(config.userName);
|
||||
|
||||
Button btnCreate = (Button)v.findViewById(R.id.create_account);
|
||||
btnCreate.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -92,11 +98,20 @@ public class AccountDetailsFragment extends Fragment {
|
||||
SQLiteDatabase db = dbHelper.getWritableDatabase();
|
||||
db.beginTransactionNonExclusive();
|
||||
try {
|
||||
if (config.cardDAV != null)
|
||||
insertService(db, accountName, Services.SERVICE_CARDDAV, config.cardDAV);
|
||||
Intent refreshIntent = new Intent(getActivity(), DavService.class);
|
||||
refreshIntent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
|
||||
|
||||
if (config.calDAV != null)
|
||||
insertService(db, accountName, Services.SERVICE_CALDAV, config.calDAV);
|
||||
if (config.cardDAV != null) {
|
||||
long id = insertService(db, accountName, Services.SERVICE_CARDDAV, config.cardDAV);
|
||||
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id);
|
||||
getActivity().startService(refreshIntent);
|
||||
}
|
||||
|
||||
if (config.calDAV != null) {
|
||||
long id = insertService(db, accountName, Services.SERVICE_CALDAV, config.calDAV);
|
||||
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id);
|
||||
getActivity().startService(refreshIntent);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
@ -106,7 +121,7 @@ public class AccountDetailsFragment extends Fragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void insertService(SQLiteDatabase db, String accountName, String service, DavResourceFinder.Configuration.ServiceInfo info) {
|
||||
protected long insertService(SQLiteDatabase db, String accountName, String service, DavResourceFinder.Configuration.ServiceInfo info) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
// insert service
|
||||
@ -125,12 +140,13 @@ public class AccountDetailsFragment extends Fragment {
|
||||
}
|
||||
|
||||
// insert collections
|
||||
for (Map.Entry<HttpUrl, DavResourceFinder.Configuration.Collection> entry : info.getCollections().entrySet()) {
|
||||
values = Collections.fromCollection(entry.getValue());
|
||||
for (CollectionInfo collection : info.getCollections().values()) {
|
||||
values = collection.toDB();
|
||||
values.put(Collections.SERVICE_ID, serviceID);
|
||||
values.put(Collections.URL, entry.getKey().toString());
|
||||
db.insertOrThrow(Collections._TABLE, null, values);
|
||||
}
|
||||
|
||||
return serviceID;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid.resource;
|
||||
package at.bitfire.davdroid.ui.setup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
@ -45,7 +45,7 @@ import at.bitfire.dav4android.property.ResourceType;
|
||||
import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.log.StringLogger;
|
||||
import at.bitfire.davdroid.ui.setup.LoginCredentialsFragment;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
@ -194,14 +194,14 @@ public class DavResourceFinder {
|
||||
if (resourceType != null && resourceType.types.contains(ResourceType.ADDRESSBOOK)) {
|
||||
dav.location = UrlUtils.withTrailingSlash(dav.location);
|
||||
log.info("Found address book at " + dav.location);
|
||||
config.collections.put(dav.location, collectionInfo(dav, Configuration.Collection.Type.ADDRESS_BOOK));
|
||||
config.collections.put(dav.location, CollectionInfo.fromDavResource(dav));
|
||||
}
|
||||
|
||||
// Does the collection refer to address book homesets?
|
||||
AddressbookHomeSet homeSets = (AddressbookHomeSet)dav.properties.get(AddressbookHomeSet.NAME);
|
||||
if (homeSets != null)
|
||||
for (String href : homeSets.hrefs)
|
||||
config.homeSets.add(dav.location.resolve(href));
|
||||
config.homeSets.add(UrlUtils.withTrailingSlash(dav.location.resolve(href)));
|
||||
}
|
||||
|
||||
protected void rememberIfCalendarOrHomeset(@NonNull DavResource dav, @NonNull Configuration.ServiceInfo config) {
|
||||
@ -210,77 +210,14 @@ public class DavResourceFinder {
|
||||
if (resourceType != null && resourceType.types.contains(ResourceType.CALENDAR)) {
|
||||
dav.location = UrlUtils.withTrailingSlash(dav.location);
|
||||
log.info("Found calendar collection at " + dav.location);
|
||||
|
||||
boolean supportsEvents = true, supportsTasks = true;
|
||||
SupportedCalendarComponentSet supportedCalendarComponentSet = (SupportedCalendarComponentSet)dav.properties.get(SupportedCalendarComponentSet.NAME);
|
||||
if (supportedCalendarComponentSet != null) {
|
||||
supportsEvents = supportedCalendarComponentSet.supportsEvents;
|
||||
supportsTasks = supportedCalendarComponentSet.supportsTasks;
|
||||
}
|
||||
if (supportsEvents || supportsTasks) {
|
||||
Configuration.Collection info = collectionInfo(dav, Configuration.Collection.Type.CALENDAR);
|
||||
info.supportsEvents = supportsEvents;
|
||||
info.supportsTasks = supportsTasks;
|
||||
config.collections.put(dav.location, info);
|
||||
}
|
||||
config.collections.put(dav.location, CollectionInfo.fromDavResource(dav));
|
||||
}
|
||||
|
||||
// Does the collection refer to calendar homesets?
|
||||
CalendarHomeSet homeSets = (CalendarHomeSet)dav.properties.get(CalendarHomeSet.NAME);
|
||||
if (homeSets != null)
|
||||
for (String href : homeSets.hrefs)
|
||||
config.homeSets.add(dav.location.resolve(href));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a #{@link at.bitfire.davdroid.resource.DavResourceFinder.Configuration.Collection} from a given
|
||||
* #{@link DavResource}. Uses these DAV properties:
|
||||
* <ul>
|
||||
* <li>calendars: current-user-properties, current-user-privilege-set, displayname, calendar-description, calendar-color</li>
|
||||
* <li>address books: current-user-properties, current-user-privilege-set, displayname, addressbook-description</li>
|
||||
* </ul>. Make sure you have queried these properties from the DavResource.
|
||||
* @param dav DavResource to take the resource info from
|
||||
* @param type must be ADDRESS_BOOK or CALENDAR
|
||||
* @return ResourceInfo which represents the DavResource
|
||||
*/
|
||||
protected Configuration.Collection collectionInfo(DavResource dav, Configuration.Collection.Type type) {
|
||||
boolean readOnly = false;
|
||||
CurrentUserPrivilegeSet privilegeSet = (CurrentUserPrivilegeSet)dav.properties.get(CurrentUserPrivilegeSet.NAME);
|
||||
if (privilegeSet != null)
|
||||
readOnly = !privilegeSet.mayWriteContent;
|
||||
|
||||
String title = null;
|
||||
DisplayName displayName = (DisplayName)dav.properties.get(DisplayName.NAME);
|
||||
if (displayName != null)
|
||||
title = displayName.displayName;
|
||||
if (TextUtils.isEmpty(title))
|
||||
title = UrlUtils.lastSegment(dav.location);
|
||||
|
||||
String description = null;
|
||||
Integer color = null;
|
||||
if (type == Configuration.Collection.Type.ADDRESS_BOOK) {
|
||||
AddressbookDescription addressbookDescription = (AddressbookDescription)dav.properties.get(AddressbookDescription.NAME);
|
||||
if (addressbookDescription != null)
|
||||
description = addressbookDescription.description;
|
||||
} else if (type == Configuration.Collection.Type.CALENDAR) {
|
||||
CalendarDescription calendarDescription = (CalendarDescription)dav.properties.get(CalendarDescription.NAME);
|
||||
if (calendarDescription != null)
|
||||
description = calendarDescription.description;
|
||||
|
||||
CalendarColor calendarColor = (CalendarColor)dav.properties.get(CalendarColor.NAME);
|
||||
if (calendarColor != null)
|
||||
color = calendarColor.color;
|
||||
}
|
||||
|
||||
Configuration.Collection collection = new Configuration.Collection(
|
||||
type,
|
||||
readOnly,
|
||||
title,
|
||||
description,
|
||||
color
|
||||
);
|
||||
|
||||
return collection;
|
||||
config.homeSets.add(UrlUtils.withTrailingSlash(dav.location.resolve(href)));
|
||||
}
|
||||
|
||||
|
||||
@ -432,27 +369,7 @@ public class DavResourceFinder {
|
||||
final Set<HttpUrl> homeSets = new HashSet<>();
|
||||
|
||||
@Getter
|
||||
final Map<HttpUrl, Collection> collections = new HashMap<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Collection implements Serializable {
|
||||
public enum Type {
|
||||
ADDRESS_BOOK,
|
||||
CALENDAR
|
||||
}
|
||||
|
||||
final Type type;
|
||||
final boolean readOnly;
|
||||
|
||||
final String displayName, description;
|
||||
final Integer color;
|
||||
|
||||
/**
|
||||
* full VTIMEZONE definition (not the TZ ID)
|
||||
*/
|
||||
boolean supportsEvents, supportsTasks;
|
||||
String timezone;
|
||||
final Map<HttpUrl, CollectionInfo> collections = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,7 @@ import java.io.StringReader;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.DavResourceFinder;
|
||||
import at.bitfire.davdroid.resource.DavResourceFinder.Configuration;
|
||||
import at.bitfire.davdroid.ui.setup.DavResourceFinder.Configuration;
|
||||
import at.bitfire.davdroid.ui.DebugInfoActivity;
|
||||
import lombok.Cleanup;
|
||||
|
||||
|
@ -46,6 +46,11 @@
|
||||
android:visibility="gone"
|
||||
android:indeterminate="true"/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/address_books"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
@ -78,6 +83,11 @@
|
||||
android:visibility="gone"
|
||||
android:indeterminate="true"/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/calendars"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
|
Loading…
Reference in New Issue
Block a user