Collections refresh

pull/2/head
Ricki Hirner 9 years ago
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;

@ -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";
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;
}
DISPLAY_NAME = "displayName",
DESCRIPTION = "description",
COLOR = "color",
SUPPORTS_VEVENT = "supportsVEVENT",
SUPPORTS_VTODO = "supportsVTODO";
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;
public static class ServiceInfo {
long id;
boolean refreshing;
Long calDavService;
boolean calDavRefreshing;
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.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)
insertService(db, accountName, Services.SERVICE_CALDAV, config.calDAV);
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…
Cancel
Save