Using Collections
This section assumes you have already read the overview and understand what collections are and what they are for.
Creating collections
When creating collections, you need to pass the collection type as the first parameter, followed by the metadata and the content.
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
const etebase = await Etebase.Account.login("username", "password");
const collectionManager = etebase.getCollectionManager();
// Create, encrypt and upload a new collection
const collection = await collectionManager.create("cyberdyne.calendar",
{
name: "Holidays",
description: "My holiday calendar",
color: "#23aabbff",
},
"" // Empty content
);
await collectionManager.upload(collection);
from etebase import Client, Account
client = Client("client-name")
etebase = Account.login(client, "username", "password")
col_mgr = etebase.get_collection_manager()
# Create, encrypt and upload a new collection
collection = col_mgr.create("cyberdyne.calendar",
{
"name": "Holidays",
"description": "My holiday calendar",
"color": "#23aabbff",
},
b"" # Empty content
)
col_mgr.upload(collection)
import com.etebase.client.*;
Client client = Client.create(httpClient, null);
Account etebase = Account.login(client, "username", "password");
CollectionManager colMgr = etebase.getCollectionManager();
// Create, encrypt and upload a new collection
ItemMetadata collectionMetadata = new ItemMetadata()
collectionMetadata.setName("Holidays");
collectionMetadata.setDescription("My holiday calendar");
collectionMetadata.setColor("#23aabbff");
Collection collection = colMgr.create("cyberdyne.calendar", collectionMetadata, "");
colMgr.upload(collection);
import com.etebase.client.*
val client = Client.create(httpClient, null)
val etebase = Account.login(client, "username", "password")
val colMgr = etebase.collectionManager
// Create, encrypt and upload a new collection
val collectionMetadata = ItemMetadata()
collectionMetadata.name = "Holidays"
collectionMetadata.description = "My holiday calendar"
collectionMetadata.color = "#23aabbff"
val collection = colMgr.create("cyberdyne.calendar", collectionMetadata, "")
colMgr.upload(collection)
const char *server_url = etebase_get_default_server_url();
EtebaseClient *client = etebase_client_new("client-name", server_url);
EtebaseAccount *etebase = etebase_account_login(client, "username", "password");
// Create, encrypt and upload a new collection
EtebaseCollectionManager *col_mgr = etebase_account_get_collection_manager(etebase);
EtebaseItemMetadata *col_meta = etebase_collection_metadata_new();
etebase_collection_metadata_set_name(col_meta, "Holidays");
etebase_collection_metadata_set_description(col_meta, "My holiday calendar");
etebase_collection_metadata_set_color(col_meta, "#23aabbff");
EtebaseCollection *col = etebase_collection_manager_create("cyberdyne.calendar", col_mgr, col_meta, "", 0);
etebase_collection_metadata_destroy(col_meta);
etebase_collection_manager_upload(col_mgr, col, NULL);
// Cleanup
etebase_collection_destroy(col);
etebase_collection_manager_destroy(col_mgr);
use etebase::{Account, Client, ItemMetadata};
let client = Client::new(..., server_url)?;
let etebase = Account::login(client, "username", "password")?;
let collection_manager = etebase.collection_manager()?;
// Create, encrypt and upload a new collection
let collection = collection_manager.create(
"cyberdyne.calendar",
ItemMetadata::new()
.set_name(Some("Holidays"))
.set_description(Some("My holiday calendar"))
.set_color(Some("#23aabbff")),
&[], // Empty content
)?;
collection_manager.upload(&collection, None)?;
Fetching collections
Fetching the list of collections is easy, and is the first step in any Etebase application, because collections contain the data.
Simple fetch
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
const etebase = await Etebase.Account.login("username", "password");
const collectionManager = etebase.getCollectionManager();
const collections = await collectionManager.list("cyberdyne.calendar");
/*
Collections:
{
data: Etebase.Collection[], // Returned array of collections
stoken: string, // The sync token for this fetch
... // More fields we'll cover later
}
*/
etebase = Account.login(...)
col_mgr = etebase.get_collection_manager()
collections = col_mgr.list("cyberdyne.calendar")
# {
# data: iter(etebase.Collection), # Returned iterator of collections
# stoken: string, # The sync token for this fetch
# ... # More fields we'll cover later
# }
import com.etebase.client.*;
Account etebase = Account.login(...);
CollectionManager colMgr = etebase.getCollectionManager();
CollectionListResponse collections = colMgr.list("cyberdyne.calendar");
/*
Collections:
{
data: Collection[], // Returned array of collections
stoken: string, // The sync token for this fetch
... // More fields we'll cover later
}
*/
import com.etebase.client.*
val etebase = Account.login(...)
val colMgr = etebase.collectionManager
val collections = colMgr.list("cyberdyne.calendar")
/*
Collections:
{
data: Collection[], // Returned array of collections
stoken: string, // The sync token for this fetch
... // More fields we'll cover later
}
*/
EtebaseAccount *etebase = etebase_account_login(...);
EtebaseCollectionManager *col_mgr = etebase_account_get_collection_manager(etebase);
EtebaseCollectionListResponse *col_list = etebase_collection_manager_list(col_mgr, "cyberdyne.calendar", NULL);
/*
Collections:
{
etebase_collection_list_response_get_data(): EtebaseCollection[] // Returned array of collections
etebase_collection_list_response_get_stoken(): const char * // The sync token for this fetch
... // More fields we'll cover later
}
*/
// Cleanup
etebase_collection_list_response_destroy(col_list);
etebase_collection_manager_destroy(col_mgr);
use etebase::{Account};
let etebase = Account::login(...)?;
let collection_manager = etebase.collection_manager()?;
let collections = collection_manager.list("cyberdyne.calendar", None)?;
/*
collections: CollectionListResponse<Collection> {
data: Vec<Collection>, // Returned array of collections
stoken: Option<String>, // The sync token for this fetch
... // More fields we'll cover later
}
*/
The number of returned collections is limited by default, and you can control this limit by passing a different limit parameter as we'll see in the next example:
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
const collections = await collectionManager.list("cyberdyne.calendar", { limit: 50 });
from etebase import FetchOptions
collections = col_mgr.list("cyberdyne.calendar", FetchOptions().limit(50))
import com.etebase.client.*;
CollectionListResponse collections = colMgr.list("cyberdyne.calendar", new FetchOptions().limit(50));
import com.etebase.client.*
val collections = colMgr.list("cyberdyne.calendar", FetchOptions().limit(50))
EtebaseFetchOptions *fetch_options = etebase_fetch_options_new();
etebase_fetch_options_set_limit(fetch_options, 50);
EtebaseCollectionListResponse *col_list = etebase_collection_manager_list(col_mgr, "cyberdyne.calendar", fetch_options);
// Cleanup
etebase_fetch_options_destroy(fetch_options);
etebase_collection_list_response_destroy(col_list);
let collections =
collection_manager.list("cyberdyne.calendar", Some(&FetchOptions::new().limit(50)))?;
You can also fetch multiple collection types at the same time:
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
const collections = await collectionManager.list(["cyberdyne.calendar", "cyberdyne.tasks"]);
collections = col_mgr.list(["cyberdyne.calendar", "cyberdyne.tasks"])
CollectionListResponse collections = colMgr.list(new String[] {"cyberdyne.calendar", "cyberdyne.tasks"});
val collections = colMgr.list(arrayOf("cyberdyne.calendar", "cyberdyne.tasks"))
const char *col_types[] = {"cyberdyne.calendar", "cyberdyne.tasks"};
EtebaseCollectionListResponse *col_list = etebase_collection_manager_list_multi(
col_mgr, col_types, ETEBASE_UTILS_C_ARRAY_LEN(col_types), NULL);
// Cleanup
etebase_collection_list_response_destroy(col_list);
let collections = collection_manager.list_multi(
vec!["cyberdyne.calendar", "cyberdyne.tasks"].into_iter(),
None,
)?;
Only fetch recent changes
We can use the stoken
we have gotten in previous fetches to only return changed collections. A collection is considered changed if either it or any of its items have changed.
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
const stoken = "..."; // An stoken we got previously e.g. collections.stoken
const collections = await collectionManager.list("cyberdyne.calendar", { stoken });
stoken = "..." # An stoken we got previously e.g. collections.stoken
col_mgr = etebase.get_collection_manager()
collections = col_mgr.list("cyberdyne.calendar", FetchOptions().stoken(stoken))
String stoken = "..."; // An stoken we got previously e.g. collections.stoken
CollectionManager colMgr = etebase.getCollectionManager();
CollectionListResponse collections = colMgr.list("cyberdyne.calendar", new FetchOptions().stoken(stoken));
val stoken = "..." // An stoken we got previously e.g. collections.stoken
val colMgr = etebase.collectionManager
val collections = colMgr.list("cyberdyne.calendar", FetchOptions().stoken(stoken))
const char *stoken = "..."; // An stoken we got previously e.g. collections.stoken
EtebaseCollectionManager *col_mgr = etebase_account_get_collection_manager(etebase);
EtebaseFetchOptions *fetch_options = etebase_fetch_options_new();
etebase_fetch_options_set_stoken(fetch_options, stoken);
EtebaseCollectionListResponse *col_list = etebase_collection_manager_list(col_mgr, "cyberdyne.calendar", fetch_options);
// Cleanup
etebase_fetch_options_destroy(fetch_options);
etebase_collection_list_response_destroy(col_list);
etebase_collection_manager_destroy(col_mgr);
let stoken = "..." // An stoken we got previously e.g. collections.stoken()
let collection_manager = etebase.collection_manager()?;
let collections = collection_manager.list(
"cyberdyne.calendar",
Some(&FetchOptions::new().stoken(Some(stoken)))
)?;
Fetch in chunks
We can use a combination of limit and stoken to fetch the changes in chunks rather than all at once. This is more resistant to spotty internet connections, and means we can show data to users faster.
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
let stoken = null;
while (true) {
const collections = await collectionManager.list("cyberdyne.calendar", { stoken, limit: 30 });
stoken = collections.stoken;
processNewCollections(collections.data);
if (collections.done) {
break;
}
}
stoken = None
done = False
while not done:
collections = col_mgr.list("cyberdyne.calendar", FetchOptions().stoken(stoken).limit(30))
stoken = collections.stoken
done = collections.done
process_new_collections(collections.data)
String stoken = null;
boolean done = false;
while (!done) {
CollectionListResponse collections = colMgr.list("cyberdyne.calendar", new FetchOptions().stoken(stoken).limit(30));
stoken = collections.getStoken();
done = collections.isDone();
process_new_collections(collections.getData());
}
var stoken: String? = null
var done = false
while (!done) {
val collections = colMgr.list("cyberdyne.calendar", FetchOptions().stoken(stoken).limit(30))
stoken = collections.stoken
done = collections.isDone
process_new_collections(collections.data)
}
char *stoken = NULL;
bool done = 0;
while (!done) {
EtebaseFetchOptions *fetch_options = etebase_fetch_options_new();
etebase_fetch_options_set_stoken(fetch_options, stoken);
etebase_fetch_options_set_limit(fetch_options, 30);
EtebaseCollectionListResponse *col_list = etebase_collection_manager_list("cyberdyne.calendar", col_mgr, fetch_options);
if (stoken) {
free(stoken);
}
stoken = strdup(etebase_collection_list_response_get_stoken(col_list));
done = etebase_collection_list_response_is_done(col_list);
uintptr_t data_len = etebase_collection_list_response_get_data_length(col_list);
const EtebaseCollection *data[data_len];
fail_if(etebase_collection_list_response_get_data(col_list, data));
process_new_collections(data, data_len);
etebase_fetch_options_destroy(fetch_options);
etebase_collection_list_response_destroy(col_list);
}
if (stoken) {
free(stoken);
}
let mut stoken: Option<&str> = None;
let mut collections: CollectionListResponse<Collection>;
loop {
collections = collection_manager.list(
"cyberdyne.calendar",
Some(&FetchOptions::new().stoken(stoken).limit(30))
)?;
stoken = collections.stoken();
process_new_collections(collections.data());
if collections.done() {
break
}
}
Fetch by uid
Sometimes we don't care about getting the whole list of collections, and we are just interested in fetching one collection based on its uid
. We can do it like this:
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
const collection = await collectionManager.fetch(collectionUid);
// Can optionally pass stoken to only return the collection if changed:
const collection = await collectionManager.fetch(collectionUid, { stoken });
collection = col_mgr.fetch(collection_uid)
# Can optionally pass stoken to only return the collection if changed:
collection = col_mgr.fetch(collection_uid, FetchOptions().stoken(stoken))
Collection collection = colMgr.fetch(collectionUid);
// Can optionally pass stoken to only return the collection if changed:
Collection collection = colMgr.fetch(collectionUid, new FetchOptions().stoken(stoken));
val collection = colMgr.fetch(collectionUid);
// Can optionally pass stoken to only return the collection if changed:
val collection = colMgr.fetch(collectionUid, FetchOptions().stoken(stoken))
EtebaseCollection *col = etebase_collection_manager_fetch(col_mgr, col_uid, NULL);
// Can optionally pass stoken to only return the collection if changed:
EtebaseFetchOptions *fetch_options = etebase_fetch_options_new();
etebase_fetch_options_set_stoken(fetch_options, stoken);
EtebaseCollection *col = etebase_collection_manager_fetch(col_mgr, col_uid, fetch_options);
etebase_fetch_options_destroy(fetch_options);
let collection = collection_manager.fetch(collection_uid, None)?;
// Can optionally pass stoken to only return the collection if changed:
let collection = collection_manager.fetch(collection_uid, Some(&FetchOptions::new().stoken(stoken)))?;
Removed memberships
When a collection is deleted it will be marked as deleted and signed so clients can verify it was really deleted by the owner rather than forged by the server.
However, when a user loses access to a collection it's not marked as deleted. Instead, its uid
is returned in a special list. Here is how it looks:
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
const stoken = "..."; // An stoken we got previously
const collections = await collectionManager.list("cyberdyne.calendar", { stoken });
// collections.removedMemberships is either undefined or a list of removed uids.
stoken = "..." # An stoken we got previously e.g. collections.stoken
collections = col_mgr.list("cyberdyne.calendar", FetchOptions().stoken(stoken))
# collections.removed_memberships is an iterator of removed uids
String stoken = "..."; // An stoken we got previously e.g. collections.stoken
CollectionListResponse collections = colMgr.list("cyberdyne.calendar", new FetchOptions().stoken(stoken));
// collections.removedMemberships is either empty or a list of removed uids.
val stoken = "..." // An stoken we got previously e.g. collections.stoken
val collections = colMgr.list("cyberdyne.calendar", FetchOptions().stoken(stoken))
// collections.removedMemberships is either empty or a list of removed uids.
const char *stoken = "..."; // An stoken we got previously e.g. collections.stoken
EtebaseFetchOptions *fetch_options = etebase_fetch_options_new();
etebase_fetch_options_set_stoken(fetch_options, stoken);
EtebaseCollectionListResponse *col_list = etebase_collection_manager_list(col_mgr, "cyberdyne.calendar", fetch_options);
etebase_fetch_options_destroy(fetch_options);
uintptr_t removed_len = etebase_collection_list_response_get_removed_memberships_length(col_list);
const EtebaseRemovedCollection *removed[removed_len];
fail_if(etebase_collection_list_response_get_removed_memberships(col_list, removed));
// removed is either empty or a list of removed collections
let stoken = "..."; // An stoken we got previously e.g. collections.stoken()
let collections = collection_manager.list("cyberdyne.calendar", Some(&FetchOptions::new().stoken(Some(stoken))))?;
// collections.removed_memberships() is either None or a list of removed uids.
Note: because membership removals are not signed, it's good practice to show an indication to the user before removing the local copy.
Modifying and deleting collections
Modifying collections is easy, it's just a matter of changing them and uploading them.
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
const meta = collection.getMeta();
collection.setMeta({ ...meta, name: "New name" });
await collectionManager.upload(collection);
meta = collection.meta
meta["name"] = "New name"
collection.meta = meta
col_mgr.upload(collection)
ItemMetadata collectionMeta = collection.getMeta();
collectionMeta.setName("Name");
collection.setMeta(collectionMeta);
colMgr.upload(collection);
val collectionMeta = collection.meta
collectionMeta.name = "Name"
collection.meta = collectionMeta
colMgr.upload(collection)
EtebaseItemMetadata *col_meta = etebase_collection_get_meta(col);
etebase_collection_metadata_set_name(col_meta, "Name");
etebase_collection_set_meta(col, col_meta);
etebase_collection_metadata_destroy(col_meta);
etebase_collection_manager_upload(col_mgr, col, NULL);
let mut collection = collection_manager.fetch("collection_uid", None)?;
let mut collection_meta = collection.meta()?;
collection_meta.set_name("name");
collection.set_meta(&collection_meta)?;
collection_manager.upload(&collection, None)?;
Deleting is even easier:
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
collection.delete();
await collectionManager.upload(collection);
collection.delete()
col_mgr.upload(collection)
collection.delete();
colMgr.upload(collection);
collection.delete()
colMgr.upload(collection)
etebase_collection_delete(col);
etebase_collection_manager_upload(col_mgr, col, NULL);
collection.delete()?;
collection_manager.upload(&collection, None)?;
Advanced uploads and transactions
In the examples above we always uploaded the collections in a way that overwrote whatever is on the server, regardless if it has changed since we last fetched it, or not. While this is fine in many cases, in some cases you want to prevent that in order to ensure the consistency of data.
Transactions
The easiest way to ensure consistency is just to use transactions. Transactions make sure that what we think is the most recent version, really is, and will fail otherwise.
For example:
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
// -> On device A:
const collection = await collectionManager.fetch(collectionUid);
// -> On device B:
const collection = await collectionManager.fetch(collectionUid);
const meta = collection.getMeta();
collection.setMeta({ ...meta, name: "New name" });
await collectionManager.upload(collection);
// -> On device A (using the previously saved collection)
const meta = collection.getMeta();
collection.setMeta({ ...meta, name: "Another name" });
// Will fail
await collectionManager.transaction(collection);
// Will succeed
await collectionManager.upload(collection);
# -> On device A:
collection = col_mgr.fetch(collection_uid)
# -> On device B:
collection = col_mgr.fetch(collection_uid)
meta = collection.meta
meta["name"] = "New name"
collection.meta = meta
col_mgr.upload(collection)
# -> On device A (using the previously saved collection)
meta = collection.meta
meta["name"] = "Another name"
collection.meta = meta
# Will fail
col_mgr.transaction(collection)
# Will succeed
col_mgr.upload(collection)
// -> On device A:
Collection collection = colMgr.fetch(collectionUid);
// -> On device B:
Collection collection = colMgr.fetch(collectionUid);
ItemMetadata meta = collection.getMeta();
meta.setName("New name");
collection.setMeta(meta);
colMgr.upload(collection);
// -> On device A (using the previously saved collection)
ItemMetadata meta = collection.getMeta();
meta.setName("Another name");
collection.setMeta(meta);
// Will fail
colMgr.transaction(collection);
// Will succeed
colMgr.upload(collection);
// -> On device A:
val collection = colMgr.fetch(collectionUid)
// -> On device B:
val collection = colMgr.fetch(collectionUid)
val meta = collection.getMeta()
meta.name = "New name"
collection.meta = meta
colMgr.upload(collection)
// -> On device A (using the previously saved collection)
val meta = collection.getMeta()
meta.name = "Another name"
collection.meta = meta
// Will fail
colMgr.transaction(collection)
// Will succeed
colMgr.upload(collection)
// -> On device A:
EtebaseCollection *col = etebase_collection_manager_fetch(col_mgr, col_uid, NULL);
// -> On device B:
EtebaseCollection *col = etebase_collection_manager_fetch(col_mgr, col_uid, NULL);
EtebaseItemMetadata *col_meta = etebase_collection_get_meta(col);
etebase_collection_metadata_set_name(col_meta, "New name");
etebase_collection_set_meta(col, col_meta);
etebase_collection_metadata_destroy(col_meta);
etebase_collection_manager_upload(col_mgr, col, NULL);
etebase_collection_destroy(col);
// -> On device A (using the previously saved collection)
EtebaseItemMetadata *col_meta = etebase_collection_get_meta(col);
etebase_collection_metadata_set_name(col_meta, "Another name");
etebase_collection_set_meta(col, col_meta);
etebase_collection_metadata_destroy(col_meta);
// Will fail
etebase_collection_manager_transaction(col_mgr, col, NULL);
// Will succeed
etebase_collection_manager_upload(col_mgr, col, NULL);
etebase_collection_destroy(col);
// -> On device A:
let mut collection = collection_manager.fetch(collection_uid, None)?;
// -> On device B:
let mut collection = collection_manager.fetch(collection_uid, None)?;
let mut meta = collection.meta()?;
meta.set_name("New name");
collection.set_meta(&meta)?;
collection_manager.upload(&collection, None)?;
// -> On device A (using the previously saved collection)
let mut meta = collection.meta()?;
meta.set_name("Another name");
collection.set_meta(&meta)?;
// Will fail
collection_manager.transaction(&collection, None)?;
// Will succeed
collection_manager.upload(&collection, None)?;
Using stoken
Transactions will only fail if the collection itself has changed, but will not fail if one of its items has changed. In some cases we want to have collection-wide consistency and want to make sure nothing has changed.
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
// -> On device A:
const collection = await collectionManager.fetch(collectionUid);
const stoken = collection.stoken;
// -> On device B:
const collection = await collectionManager.fetch(collectionUid);
const itemManager = collectionManager.getItemManager(collection);
// Add a new item to tho collection (redacted for simplicity)
await itemManager.batch(...);
// -> On device A (using the previously saved collection)
const stoken = collection.stoken;
const meta = collection.getMeta();
collection.setMeta({ ...meta, name: "Another name" });
// Will both fail
await collectionManager.transaction(collection, { stoken });
await collectionManager.upload(collection, { stoken });
// Will both succeed
await collectionManager.transaction(collection);
await collectionManager.upload(collection);
# -> On device A:
collection = col_mgr.fetch(collection_uid)
stoken = collection.stoken
# -> On device B:
collection = col_mgr.fetch(collection_uid)
item_mgr = col_mgr.get_item_manager(collection)
# Add a new item to tho collection (redacted for simplicity)
item_mgr.batch(...)
# -> On device A (using the previously saved collection)
meta = collection.meta
meta["name"] = "Another name"
collection.meta = meta
# Will both fail
col_mgr.transaction(collection, FetchOptions().stoken(stoken))
col_mgr.upload(collection, FetchOptions().stoken(stoken))
# Will both succeed
col_mgr.transaction(collection)
col_mgr.upload(collection)
// -> On device A:
Collection collection = colMgr.fetch(collectionUid);
String stoken = collection.stoken
// -> On device B:
Collection collection = colMgr.fetch(collectionUid);
ItemManager itemMgr = colMgr.getItemManager(collection);
// Add a new item to tho collection (redacted for simplicity)
itemMgr.batch(...)
// -> On device A (using the previously saved collection)
ItemMetadata meta = collection.getMeta();
meta.setName("Another name");
collection.setMeta(meta);
// Will both fail
colMgr.transaction(collection, new FetchOptions().stoken(stoken));
colMgr.upload(collection, new FetchOptions().stoken(stoken));
// Will both succeed
colMgr.transaction(collection);
colMgr.upload(collection);
// -> On device A:
val collection = colMgr.fetch(collectionUid)
val stoken = collection.stoken
// -> On device B:
val collection = colMgr.fetch(collectionUid)
val itemMgr = colMgr.getItemManager(collection)
// Add a new item to tho collection (redacted for simplicity)
itemMgr.batch(...)
// -> On device A (using the previously saved collection)
val meta = collection.getMeta()
meta.name = "Another name"
collection.meta = meta
// Will both fail
colMgr.transaction(collection, FetchOptions().stoken(stoken))
colMgr.upload(collection, FetchOptions().stoken(stoken))
// Will both succeed
colMgr.transaction(collection)
colMgr.upload(collection)
// -> On device A:
EtebaseCollection *col = etebase_collection_manager_fetch(col_mgr, col_uid, NULL);
const char *stoken = etebase_collection_get_stoken(col);
{
// -> On device B:
EtebaseCollection *col = etebase_collection_manager_fetch(col_mgr, col_uid, NULL);
EtebaseItemManager *item_mgr = etebase_collection_manager_get_item_manager(col_mgr, col);
etebase_item_manager_batch(...);
etebase_item_manager_destroy(item_mgr);
etebase_collection_destroy(col);
}
// -> On device A (using the previously saved collection)
EtebaseItemMetadata *col_meta = etebase_collection_get_meta(col);
etebase_collection_metadata_set_name(col_meta, "Another name");
etebase_collection_set_meta(col, col_meta);
etebase_collection_metadata_destroy(col_meta);
EtebaseFetchOptions *fetch_options = etebase_fetch_options_new();
etebase_fetch_options_set_stoken(fetch_options, stoken);
// Will both fail
etebase_collection_manager_transaction(col_mgr, col, fetch_options);
etebase_collection_manager_upload(col_mgr, col, fetch_options);
etebase_fetch_options_destroy(fetch_options);
// Will both succeed
etebase_collection_manager_transaction(col_mgr, col, NULL);
etebase_collection_manager_upload(col_mgr, col, NULL);
etebase_collection_destroy(col);
// -> On device A:
let mut collection = collection_manager.fetch(collection_uid, None)?;
let stoken = collection.stoken();
// -> On device B:
let mut collection = collection_manager.fetch(collection_uid, None)?;
let item_manager = collection_manager.item_manager(&collection)?;
// Add a new item to tho collection (redacted for simplicity)
item_manager.batch(..., None);
// -> On device A (using the previously saved collection)
let mut meta = collection.meta()?;
meta.set_name("Another name");
collection.set_meta(&meta)?;
// Will both fail
collection_manager.transaction(&collection, Some(&FetchOptions::new().stoken(stoken)))?;
collection_manager.upload(&collection, Some(&FetchOptions::new().stoken(stoken)))?;
// Will both succeed
collection_manager.transaction(&collection, None)?;
collection_manager.upload(&collection, None)?;
Binary content
In the examples above content was always a string. However, content is actually a binary blob of data, not a string. Using it as a string is just a convenience.
Here is how you can control the formatting of the data:
- JavaScript
- Python
- Java
- Kotlin
- C/C++
- Rust
// default, returns a Promise<Uint8Array>
await collection.getContent();
// tries to convert the binary data to a string and returns that
await collection.getContent(Etebase.OutputFormat.String);
// Sets the content to a binary blob
await collection.setContent(Uint8Array.from([72, 101, 108, 108, 111]));
// Sets the content to a string
await collection.setContent("Hello");
# The Python API returns `bytes` by default
collection.content
# Try decoding the binary data to a UTF-8 string
collection.content.decode()
# Sets the content to some byte array
collection.content = b"Some bytes"
# Set the content to a string
collection.content = "Some bytes".encode()
// The Java API returns `byte[]` by default
collection.getContent();
// Try decoding the binary data to a UTF-8 string and return that
collection.getContentString();
// Sets the content to a binary blob
collection.setContent("Bla".getBytes("UTF-8"));
// Sets the content to a string
collection.setContent("Hello");
// The Java API returns `byte[]` by default
collection.content
// Try decoding the binary data to a UTF-8 string and return that
collection.contentString
// Sets the content to a binary blob
collection.content = "Bla".toByteArray()
// Sets the content to a string
collection.content = "Hello"
// The C API always works on binary data,
// This means content is *not* NULL terminated!
// It's generally fastest to just try to get the content into a buffer
// if we think it's likely to be under a certain size.
char tmp[500];
intptr_t len = etebase_collection_get_content(col, tmp, sizeof(tmp));
if (len < 0) {
// Error
} else if (len > sizeof(tmp)) {
// This means we should allocate a buffer of size len to fetch into
char *tmp2 = malloc(len);
etebase_collection_get_content(col, tmp2, len);
// Do something with tmp2
free(tmp2);
}
// As said above, content is not null terminated.
// To get the content as a null terminated string:
char tmp[500];
intptr_t len = etebase_collection_get_content(col, tmp, sizeof(tmp));
if (len < 0) {
// Error
} else {
tmp[len] = 0;
}
// The rust API returns `Vec<u8>` by default
collection.content()?;
// Try decoding the binary data to a UTF-8 string and return that
String::from_utf8(collection.content()?);
// Sets the content to a binary blob
collection.set_content(&vec![0, 1, 2])?;
// Sets the content to a string
collection.set_content("Hello".as_bytes())?;