Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog for nextcloud api

## Version 14.3.0 (unreleased)
- Add calendar (CalDAV) and contacts (CardDAV) support (issue #59):
- Calendars: list calendars, read all entries of a calendar, query the events
overlapping a time range, read a single entry, create/replace and delete
entries, and create and delete calendars
- The time-range query optionally lets the server expand recurring events
into one occurrence per repetition (`getCalendarEntriesInRange(..., true)`)
instead of returning the stored event with its recurrence rule
- Address books: list address books, read all contacts, read a single
contact, create/replace and delete contacts, and create and delete address
books
- Entries are exchanged as raw iCalendar/vCard documents, so the library
gains no iCalendar or vCard dependency and callers stay free to parse them
with the library of their choice (e.g. ical4j or ez-vcard)
- Updates accept the etag of the entry they are based on, so a concurrent
change is reported instead of silently overwritten
- DAV paths use the internal user id rather than the login name, so they are
also correct when the two differ (external user backends)
- `NextcloudApiException` gained a `(String message, Throwable cause)`
constructor so failures can be reported with both context and cause

## Version 14.2.1
- 2026-08-11
- Updated dependencies:
Expand Down
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Java api library to access nextcloud features from java applications
- Management of groups
- Folder management (Without access control)
- List shares and create new file shares (No way to delete/update shares atm.)
- Calendars (CalDAV) and contacts (CardDAV), see below
- Tested against nextCloud 31.0.0 server version, but should also work with older nextCloud and ownCloud systems

## Usage
Expand All @@ -23,13 +24,77 @@ Java api library to access nextcloud features from java applications
</dependency>
```

### Trying a pre-release snapshot

Unreleased work is published as a `-SNAPSHOT` to the Central Portal snapshot
repository so it can be tried before a release. Snapshots are overwritten by
later builds and Sonatype removes them after about 90 days, so don't depend on
one from a production build.

```xml
<repositories>
<repository>
<id>central-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>

<dependency>
<groupId>org.aarboard.nextcloud</groupId>
<artifactId>nextcloud-api</artifactId>
<version>14.3.0-SNAPSHOT</version>
</dependency>
```

- The 14.x versions require Java 11+,as the jakarta.xml binding requires Java 11+
- The 13.x versions are now using the jakarta.xml binding stuff, to prevent problems with Java 11+
No API changes have been made in v13, but at some places the XML stuff is exposed
Which made it necessary to bump the major version number
- Create a NextcloudConnector instance and provide your server settings and authentification
- Now you can use the methods exposed to access your nextcloud instance

## Calendars and contacts

Calendar entries and contacts are exchanged as raw iCalendar/vCard documents.
The library does not parse them, so it needs no iCalendar or vCard dependency
and you stay free to use the parser of your choice (for example
[ical4j](https://github.com/ical4j/ical4j) or
[ez-vcard](https://github.com/mangstadt/ez-vcard)).

```java
try (NextcloudConnector nc = new NextcloudConnector("cloud.example.org", true, 443, "user", "password")) {
for (Calendar calendar : nc.listCalendars()) {
System.out.println(calendar.getName() + " -> " + calendar.getDisplayName());
}

// All entries of a calendar, or only the events in a time range
List<CalendarEntry> all = nc.getCalendarEntries("personal");
List<CalendarEntry> thisWeek = nc.getCalendarEntriesInRange("personal",
Instant.now(), Instant.now().plus(7, ChronoUnit.DAYS));

String ics = thisWeek.get(0).getData(); // the iCalendar document

// Pass true to have the server expand recurring events into one VEVENT per
// occurrence in the range, instead of one event carrying its RRULE. The
// expanded result is a computed view of that range, so don't write it back.
List<CalendarEntry> occurrences = nc.getCalendarEntriesInRange("personal",
Instant.now(), Instant.now().plus(7, ChronoUnit.DAYS), true);

// Store an entry, and update it only while it still carries this etag
String etag = nc.putCalendarEntry("personal", "my-event.ics", ics);
nc.putCalendarEntry("personal", "my-event.ics", changedIcs, etag);

nc.deleteCalendarEntry("personal", "my-event.ics");
}
```

Contacts work the same way via `listAddressBooks()`, `getContacts(book)`,
`getContact(book, name)`, `putContact(...)` and `deleteContact(...)`.
Calendars and address books can also be created and deleted with
`createCalendar(name, displayName, colour)` / `deleteCalendar(name)` and
`createAddressBook(name, displayName, description)` / `deleteAddressBook(name)`.

## When you wish to contribute to the project
[Infos for contributors](./README.developers.md)

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.aarboard.nextcloud</groupId>
<artifactId>nextcloud-api</artifactId>
<version>14.2.2-SNAPSHOT</version>
<version>14.3.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<!-- compile time dependencies -->
Expand Down
236 changes: 236 additions & 0 deletions src/main/java/org/aarboard/nextcloud/api/NextcloudConnector.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
import org.aarboard.nextcloud.api.filesharing.RemoteShare;
import org.aarboard.nextcloud.api.filesharing.SingleShareXMLAnswer;
import org.aarboard.nextcloud.api.groupfolders.GroupFolderInfo;
import org.aarboard.nextcloud.api.calendar.Calendar;
import org.aarboard.nextcloud.api.calendar.CalendarEntry;
import org.aarboard.nextcloud.api.calendar.Calendars;
import org.aarboard.nextcloud.api.contacts.AddressBook;
import org.aarboard.nextcloud.api.contacts.AddressBooks;
import org.aarboard.nextcloud.api.contacts.Contact;
import org.aarboard.nextcloud.api.groupfolders.GroupFolders;
import org.aarboard.nextcloud.api.provisioning.*;
import org.aarboard.nextcloud.api.systemtags.SystemTags;
Expand Down Expand Up @@ -66,6 +72,8 @@ public class NextcloudConnector implements AutoCloseable {
private final Files fl;
private final GroupFolders gf;
private final SystemTags st;
private final Calendars cal;
private final AddressBooks ab;

/**
*
Expand Down Expand Up @@ -130,6 +138,8 @@ public NextcloudConnector(String originalServiceUrl, AuthenticationConfig authen
fl = new Files(this.serverConfig);
gf = new GroupFolders(this.serverConfig);
st = new SystemTags(this.serverConfig);
cal = new Calendars(this.serverConfig);
ab = new AddressBooks(this.serverConfig);
OPEN_INSTANCES.incrementAndGet();

} catch (MalformedURLException e) {
Expand All @@ -154,6 +164,8 @@ public NextcloudConnector(String serverName, boolean useHTTPS, int port,
fl = new Files(this.serverConfig);
gf = new GroupFolders(this.serverConfig);
st = new SystemTags(this.serverConfig);
cal = new Calendars(this.serverConfig);
ab = new AddressBooks(this.serverConfig);
OPEN_INSTANCES.incrementAndGet();
}

Expand Down Expand Up @@ -280,6 +292,230 @@ public boolean declinePendingRemoteShare(int remoteShareId) {
return fc.declinePendingRemoteShare(remoteShareId);
}

/**
* Lists the calendars of the authenticated user.
*
* @return the user's calendars
* @since 14.3
*/
public java.util.List<Calendar> listCalendars() {
return cal.listCalendars();
}

/**
* Fetches every entry of a calendar as a raw iCalendar document.
*
* @param calendarName name of the calendar
* @return all entries of the calendar
* @since 14.3
*/
public java.util.List<CalendarEntry> getCalendarEntries(String calendarName) {
return cal.getCalendarEntries(calendarName);
}

/**
* Fetches the events of a calendar overlapping a time range. Recurring
* events are returned once, as stored, carrying their recurrence rule.
*
* @param calendarName name of the calendar
* @param from start of the range, inclusive
* @param to end of the range, exclusive
* @return the matching entries
* @since 14.3
*/
public java.util.List<CalendarEntry> getCalendarEntriesInRange(String calendarName,
java.time.Instant from, java.time.Instant to) {
return cal.getCalendarEntriesInRange(calendarName, from, to);
}

/**
* Fetches the events of a calendar overlapping a time range, optionally
* having the server expand recurring events into one occurrence each.
* <p>
* An expanded result is a computed view of that range, not the stored
* resource, so it must not be written back.
*
* @param calendarName name of the calendar
* @param from start of the range, inclusive
* @param to end of the range, exclusive
* @param expandRecurrences whether recurring events should be expanded
* @return the matching entries
* @since 14.3
*/
public java.util.List<CalendarEntry> getCalendarEntriesInRange(String calendarName,
java.time.Instant from, java.time.Instant to, boolean expandRecurrences) {
return cal.getCalendarEntriesInRange(calendarName, from, to, expandRecurrences);
}

/**
* Fetches a single calendar entry.
*
* @param calendarName name of the calendar
* @param entryName name of the entry, or its href
* @return the entry, or {@code null} if it does not exist
* @since 14.3
*/
public CalendarEntry getCalendarEntry(String calendarName, String entryName) {
return cal.getCalendarEntry(calendarName, entryName);
}

/**
* Creates a calendar entry, or replaces it if one of that name exists.
*
* @param calendarName name of the calendar
* @param entryName name of the entry, conventionally the event UID
* followed by {@code .ics}
* @param iCalendar the iCalendar document to store
* @return the etag of the stored entry
* @since 14.3
*/
public String putCalendarEntry(String calendarName, String entryName, String iCalendar) {
return cal.putCalendarEntry(calendarName, entryName, iCalendar);
}

/**
* Replaces a calendar entry only while it still carries the given etag.
*
* @param calendarName name of the calendar
* @param entryName name of the entry
* @param iCalendar the iCalendar document to store
* @param etag the etag the stored entry must still have
* @return the etag of the stored entry
* @since 14.3
*/
public String putCalendarEntry(String calendarName, String entryName, String iCalendar, String etag) {
return cal.putCalendarEntry(calendarName, entryName, iCalendar, etag);
}

/**
* Deletes a calendar entry.
*
* @param calendarName name of the calendar
* @param entryName name of the entry, or its href
* @since 14.3
*/
public void deleteCalendarEntry(String calendarName, String entryName) {
cal.deleteCalendarEntry(calendarName, entryName);
}

/**
* Creates a calendar.
*
* @param calendarName name of the calendar as it appears in the URL
* @param displayName name shown in the UI, may be {@code null}
* @param color HTML colour code, e.g. {@code #FF0000}, may be
* {@code null}
* @since 14.3
*/
public void createCalendar(String calendarName, String displayName, String color) {
cal.createCalendar(calendarName, displayName, color);
}

/**
* Deletes a calendar and all of its entries.
*
* @param calendarName name of the calendar
* @since 14.3
*/
public void deleteCalendar(String calendarName) {
cal.deleteCalendar(calendarName);
}

/**
* Lists the address books of the authenticated user.
*
* @return the user's address books
* @since 14.3
*/
public java.util.List<AddressBook> listAddressBooks() {
return ab.listAddressBooks();
}

/**
* Fetches every contact of an address book as a raw vCard document.
*
* @param addressBookName name of the address book
* @return all contacts of the address book
* @since 14.3
*/
public java.util.List<Contact> getContacts(String addressBookName) {
return ab.getContacts(addressBookName);
}

/**
* Fetches a single contact.
*
* @param addressBookName name of the address book
* @param contactName name of the contact resource, or its href
* @return the contact, or {@code null} if it does not exist
* @since 14.3
*/
public Contact getContact(String addressBookName, String contactName) {
return ab.getContact(addressBookName, contactName);
}

/**
* Creates a contact, or replaces it if one of that name exists.
*
* @param addressBookName name of the address book
* @param contactName name of the contact resource, conventionally the
* vCard UID followed by {@code .vcf}
* @param vCard the vCard document to store
* @return the etag of the stored contact
* @since 14.3
*/
public String putContact(String addressBookName, String contactName, String vCard) {
return ab.putContact(addressBookName, contactName, vCard);
}

/**
* Replaces a contact only while it still carries the given etag.
*
* @param addressBookName name of the address book
* @param contactName name of the contact resource
* @param vCard the vCard document to store
* @param etag the etag the stored contact must still have
* @return the etag of the stored contact
* @since 14.3
*/
public String putContact(String addressBookName, String contactName, String vCard, String etag) {
return ab.putContact(addressBookName, contactName, vCard, etag);
}

/**
* Deletes a contact.
*
* @param addressBookName name of the address book
* @param contactName name of the contact resource, or its href
* @since 14.3
*/
public void deleteContact(String addressBookName, String contactName) {
ab.deleteContact(addressBookName, contactName);
}

/**
* Creates an address book.
*
* @param addressBookName name of the address book as it appears in the URL
* @param displayName name shown in the UI, may be {@code null}
* @param description description of the address book, may be
* {@code null}
* @since 14.3
*/
public void createAddressBook(String addressBookName, String displayName, String description) {
ab.createAddressBook(addressBookName, displayName, description);
}

/**
* Deletes an address book and all of its contacts.
*
* @param addressBookName name of the address book
* @since 14.3
*/
public void deleteAddressBook(String addressBookName) {
ab.deleteAddressBook(addressBookName);
}

/**
* @return all system tags on the server
*/
Expand Down
Loading
Loading