mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 21:42:34 +00:00
frontend: Add basic information about collections
This commit is contained in:
@@ -202,14 +202,16 @@ ul.collection-list {
|
|||||||
background: color-mix(in srgb, var(--background-color), var(--dilute-color) 5%);
|
background: color-mix(in srgb, var(--background-color), var(--dilute-color) 5%);
|
||||||
display: grid;
|
display: grid;
|
||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
|
height: fit-content;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
". . color-chip"
|
". . color-chip"
|
||||||
"title comps color-chip"
|
"title comps color-chip"
|
||||||
"description description color-chip"
|
"description description color-chip"
|
||||||
"subscription-url subscription-url color-chip"
|
"subscription-url subscription-url color-chip"
|
||||||
|
"metadata metadata color-chip"
|
||||||
"actions actions color-chip"
|
"actions actions color-chip"
|
||||||
". . color-chip";
|
". . color-chip";
|
||||||
grid-template-rows: 12px auto auto auto auto 12px;
|
grid-template-rows: 12px auto auto auto auto auto 12px;
|
||||||
grid-template-columns: min-content auto 80px;
|
grid-template-columns: min-content auto 80px;
|
||||||
row-gap: 4px;
|
row-gap: 4px;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
@@ -251,6 +253,11 @@ ul.collection-list {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metadata {
|
||||||
|
grid-area: metadata;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.subscription-url {
|
.subscription-url {
|
||||||
grid-area: subscription-url;
|
grid-area: subscription-url;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<h2>{{user.id }}'s Addressbooks</h2>
|
<h2>{{user.id }}'s Addressbooks</h2>
|
||||||
<ul class="collection-list">
|
<ul class="collection-list">
|
||||||
{% for addressbook in addressbooks %}
|
{% for (meta, addressbook) in addressbooks %}
|
||||||
<li class="collection-list-item">
|
<li class="collection-list-item">
|
||||||
<a href="/frontend/user/{{ addressbook.principal }}/addressbook/{{ addressbook.id}}">
|
<a href="/frontend/user/{{ addressbook.principal }}/addressbook/{{ addressbook.id}}">
|
||||||
<span class="title">
|
<span class="title">
|
||||||
@@ -18,6 +18,9 @@
|
|||||||
<delete-button trash
|
<delete-button trash
|
||||||
href="/carddav/principal/{{ addressbook.principal }}/{{ addressbook.id }}"></delete-button>
|
href="/carddav/principal/{{ addressbook.principal }}/{{ addressbook.id }}"></delete-button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="metadata">
|
||||||
|
{{ meta.len }} objects, {{ meta.deleted_len }} deleted objects
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -27,7 +30,7 @@
|
|||||||
{%if !deleted_addressbooks.is_empty() %}
|
{%if !deleted_addressbooks.is_empty() %}
|
||||||
<h3>Deleted Addressbooks</h3>
|
<h3>Deleted Addressbooks</h3>
|
||||||
<ul class="collection-list">
|
<ul class="collection-list">
|
||||||
{% for addressbook in deleted_addressbooks %}
|
{% for (meta, addressbook) in deleted_addressbooks %}
|
||||||
<li class="collection-list-item">
|
<li class="collection-list-item">
|
||||||
<a href="/frontend/user/{{ addressbook.principal }}/addressbook/{{ addressbook.id}}">
|
<a href="/frontend/user/{{ addressbook.principal }}/addressbook/{{ addressbook.id}}">
|
||||||
<span class="title">
|
<span class="title">
|
||||||
@@ -44,6 +47,9 @@
|
|||||||
</form>
|
</form>
|
||||||
<delete-button href="/carddav/principal/{{ addressbook.principal }}/{{ addressbook.id }}"></delete-button>
|
<delete-button href="/carddav/principal/{{ addressbook.principal }}/{{ addressbook.id }}"></delete-button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="metadata">
|
||||||
|
{{ meta.len }} objects, {{ meta.deleted_len }} deleted objects
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<h2>{{ user.id }}'s Calendars</h2>
|
<h2>{{ user.id }}'s Calendars</h2>
|
||||||
<ul class="collection-list">
|
<ul class="collection-list">
|
||||||
{% for calendar in calendars %}
|
{% for (meta, calendar) in calendars %}
|
||||||
{% let color = calendar.color.to_owned().unwrap_or("transparent".to_owned()) %}
|
{% let color = calendar.color.to_owned().unwrap_or("transparent".to_owned()) %}
|
||||||
<li class="collection-list-item" style="--color: {{ color }}">
|
<li class="collection-list-item" style="--color: {{ color }}">
|
||||||
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id }}">
|
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id }}">
|
||||||
@@ -27,6 +27,9 @@
|
|||||||
<delete-button trash href="/caldav/principal/{{ calendar.principal }}/{{ calendar.id }}"></delete-button>
|
<delete-button trash href="/caldav/principal/{{ calendar.principal }}/{{ calendar.id }}"></delete-button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="metadata">
|
||||||
|
{{ meta.len }} objects, {{ meta.deleted_len }} deleted objects
|
||||||
|
</div>
|
||||||
<div class="color-chip"></div>
|
<div class="color-chip"></div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -37,7 +40,7 @@
|
|||||||
{%if !deleted_calendars.is_empty() %}
|
{%if !deleted_calendars.is_empty() %}
|
||||||
<h3>Deleted Calendars</h3>
|
<h3>Deleted Calendars</h3>
|
||||||
<ul class="collection-list">
|
<ul class="collection-list">
|
||||||
{% for calendar in deleted_calendars %}
|
{% for (meta, calendar) in deleted_calendars %}
|
||||||
{% let color = calendar.color.to_owned().unwrap_or("transparent".to_owned()) %}
|
{% let color = calendar.color.to_owned().unwrap_or("transparent".to_owned()) %}
|
||||||
<li class="collection-list-item" style="--color: {{ color }}">
|
<li class="collection-list-item" style="--color: {{ color }}">
|
||||||
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id}}">
|
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id}}">
|
||||||
@@ -60,6 +63,9 @@
|
|||||||
</form>
|
</form>
|
||||||
<delete-button href="/caldav/principal/{{ calendar.principal }}/{{ calendar.id }}"></delete-button>
|
<delete-button href="/caldav/principal/{{ calendar.principal }}/{{ calendar.id }}"></delete-button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="metadata">
|
||||||
|
{{ meta.len }} objects, {{ meta.deleted_len }} deleted objects
|
||||||
|
</div>
|
||||||
<div class="color-chip"></div>
|
<div class="color-chip"></div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use askama::Template;
|
|||||||
use askama_web::WebTemplate;
|
use askama_web::WebTemplate;
|
||||||
use axum::{Extension, extract::Path, response::IntoResponse};
|
use axum::{Extension, extract::Path, response::IntoResponse};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use rustical_store::{Addressbook, AddressbookStore, auth::Principal};
|
use rustical_store::{Addressbook, AddressbookStore, CollectionMetadata, auth::Principal};
|
||||||
|
|
||||||
use crate::pages::user::{Section, UserPage};
|
use crate::pages::user::{Section, UserPage};
|
||||||
|
|
||||||
@@ -18,8 +18,8 @@ impl Section for AddressbooksSection {
|
|||||||
#[template(path = "components/sections/addressbooks_section.html")]
|
#[template(path = "components/sections/addressbooks_section.html")]
|
||||||
pub struct AddressbooksSection {
|
pub struct AddressbooksSection {
|
||||||
pub user: Principal,
|
pub user: Principal,
|
||||||
pub addressbooks: Vec<Addressbook>,
|
pub addressbooks: Vec<(CollectionMetadata, Addressbook)>,
|
||||||
pub deleted_addressbooks: Vec<Addressbook>,
|
pub deleted_addressbooks: Vec<(CollectionMetadata, Addressbook)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn route_addressbooks<AS: AddressbookStore>(
|
pub async fn route_addressbooks<AS: AddressbookStore>(
|
||||||
@@ -41,11 +41,33 @@ pub async fn route_addressbooks<AS: AddressbookStore>(
|
|||||||
deleted_addressbooks.extend(addr_store.get_deleted_addressbooks(group).await.unwrap());
|
deleted_addressbooks.extend(addr_store.get_deleted_addressbooks(group).await.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut addressbook_infos = vec![];
|
||||||
|
for addressbook in addressbooks {
|
||||||
|
addressbook_infos.push((
|
||||||
|
addr_store
|
||||||
|
.addressbook_metadata(&addressbook.principal, &addressbook.id)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
addressbook,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut deleted_addressbook_infos = vec![];
|
||||||
|
for addressbook in deleted_addressbooks {
|
||||||
|
deleted_addressbook_infos.push((
|
||||||
|
addr_store
|
||||||
|
.addressbook_metadata(&addressbook.principal, &addressbook.id)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
addressbook,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
UserPage {
|
UserPage {
|
||||||
section: AddressbooksSection {
|
section: AddressbooksSection {
|
||||||
user: user.clone(),
|
user: user.clone(),
|
||||||
addressbooks,
|
addressbooks: addressbook_infos,
|
||||||
deleted_addressbooks,
|
deleted_addressbooks: deleted_addressbook_infos,
|
||||||
},
|
},
|
||||||
user,
|
user,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use askama::Template;
|
|||||||
use askama_web::WebTemplate;
|
use askama_web::WebTemplate;
|
||||||
use axum::{Extension, extract::Path, response::IntoResponse};
|
use axum::{Extension, extract::Path, response::IntoResponse};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use rustical_store::{Calendar, CalendarStore, auth::Principal};
|
use rustical_store::{Calendar, CalendarStore, CollectionMetadata, auth::Principal};
|
||||||
|
|
||||||
impl Section for CalendarsSection {
|
impl Section for CalendarsSection {
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
@@ -17,8 +17,8 @@ impl Section for CalendarsSection {
|
|||||||
#[template(path = "components/sections/calendars_section.html")]
|
#[template(path = "components/sections/calendars_section.html")]
|
||||||
pub struct CalendarsSection {
|
pub struct CalendarsSection {
|
||||||
pub user: Principal,
|
pub user: Principal,
|
||||||
pub calendars: Vec<Calendar>,
|
pub calendars: Vec<(CollectionMetadata, Calendar)>,
|
||||||
pub deleted_calendars: Vec<Calendar>,
|
pub deleted_calendars: Vec<(CollectionMetadata, Calendar)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn route_calendars<CS: CalendarStore>(
|
pub async fn route_calendars<CS: CalendarStore>(
|
||||||
@@ -35,16 +35,38 @@ pub async fn route_calendars<CS: CalendarStore>(
|
|||||||
calendars.extend(cal_store.get_calendars(group).await.unwrap());
|
calendars.extend(cal_store.get_calendars(group).await.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut calendar_infos = vec![];
|
||||||
|
for calendar in calendars {
|
||||||
|
calendar_infos.push((
|
||||||
|
cal_store
|
||||||
|
.calendar_metadata(&calendar.principal, &calendar.id)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
calendar,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let mut deleted_calendars = vec![];
|
let mut deleted_calendars = vec![];
|
||||||
for group in user.memberships() {
|
for group in user.memberships() {
|
||||||
deleted_calendars.extend(cal_store.get_deleted_calendars(group).await.unwrap());
|
deleted_calendars.extend(cal_store.get_deleted_calendars(group).await.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut deleted_calendar_infos = vec![];
|
||||||
|
for calendar in deleted_calendars {
|
||||||
|
deleted_calendar_infos.push((
|
||||||
|
cal_store
|
||||||
|
.calendar_metadata(&calendar.principal, &calendar.id)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
calendar,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
UserPage {
|
UserPage {
|
||||||
section: CalendarsSection {
|
section: CalendarsSection {
|
||||||
user: user.clone(),
|
user: user.clone(),
|
||||||
calendars,
|
calendars: calendar_infos,
|
||||||
deleted_calendars,
|
deleted_calendars: deleted_calendar_infos,
|
||||||
},
|
},
|
||||||
user,
|
user,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Error, addressbook::Addressbook};
|
use crate::{CollectionMetadata, Error, addressbook::Addressbook};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use rustical_ical::AddressObject;
|
use rustical_ical::AddressObject;
|
||||||
|
|
||||||
@@ -35,6 +35,12 @@ pub trait AddressbookStore: Send + Sync + 'static {
|
|||||||
synctoken: i64,
|
synctoken: i64,
|
||||||
) -> Result<(Vec<AddressObject>, Vec<String>, i64), Error>;
|
) -> Result<(Vec<AddressObject>, Vec<String>, i64), Error>;
|
||||||
|
|
||||||
|
async fn addressbook_metadata(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
addressbook_id: &str,
|
||||||
|
) -> Result<CollectionMetadata, Error>;
|
||||||
|
|
||||||
async fn get_objects(
|
async fn get_objects(
|
||||||
&self,
|
&self,
|
||||||
principal: &str,
|
principal: &str,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Calendar, error::Error};
|
use crate::{Calendar, CollectionMetadata, error::Error};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use rustical_ical::CalendarObject;
|
use rustical_ical::CalendarObject;
|
||||||
@@ -53,6 +53,12 @@ pub trait CalendarStore: Send + Sync + 'static {
|
|||||||
self.get_objects(principal, cal_id).await
|
self.get_objects(principal, cal_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn calendar_metadata(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
) -> Result<CollectionMetadata, Error>;
|
||||||
|
|
||||||
async fn get_objects(
|
async fn get_objects(
|
||||||
&self,
|
&self,
|
||||||
principal: &str,
|
principal: &str,
|
||||||
|
|||||||
@@ -135,6 +135,20 @@ impl<CS: CalendarStore, BS: CalendarStore> CalendarStore for CombinedCalendarSto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn calendar_metadata(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
) -> Result<crate::CollectionMetadata, Error> {
|
||||||
|
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store
|
||||||
|
.calendar_metadata(principal, cal_id)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
self.cal_store.calendar_metadata(principal, cal_id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn get_objects(
|
async fn get_objects(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ fn birthday_calendar(addressbook: Addressbook) -> Calendar {
|
|||||||
id: format!("{}{}", BIRTHDAYS_PREFIX, addressbook.id),
|
id: format!("{}{}", BIRTHDAYS_PREFIX, addressbook.id),
|
||||||
displayname: addressbook
|
displayname: addressbook
|
||||||
.displayname
|
.displayname
|
||||||
.map(|name| format!("{} birthdays", name)),
|
.map(|name| format!("{name} birthdays")),
|
||||||
order: 0,
|
order: 0,
|
||||||
description: None,
|
description: None,
|
||||||
color: None,
|
color: None,
|
||||||
@@ -104,6 +104,17 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
|||||||
Ok((objects, deleted_objects, new_synctoken))
|
Ok((objects, deleted_objects, new_synctoken))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn calendar_metadata(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
) -> Result<crate::CollectionMetadata, Error> {
|
||||||
|
let cal_id = cal_id
|
||||||
|
.strip_prefix(BIRTHDAYS_PREFIX)
|
||||||
|
.ok_or(Error::NotFound)?;
|
||||||
|
self.0.addressbook_metadata(principal, cal_id).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_objects(
|
async fn get_objects(
|
||||||
&self,
|
&self,
|
||||||
principal: &str,
|
principal: &str,
|
||||||
|
|||||||
@@ -37,3 +37,11 @@ pub struct CollectionOperation {
|
|||||||
pub topic: String,
|
pub topic: String,
|
||||||
pub data: CollectionOperationInfo,
|
pub data: CollectionOperationInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct CollectionMetadata {
|
||||||
|
pub len: usize,
|
||||||
|
pub deleted_len: usize,
|
||||||
|
pub size: u64,
|
||||||
|
pub deleted_size: u64,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const SYNC_NAMESPACE: &str = "github.com/lennart-k/rustical/ns/";
|
const SYNC_NAMESPACE: &str = "github.com/lennart-k/rustical/ns/";
|
||||||
|
|
||||||
pub fn format_synctoken(synctoken: i64) -> String {
|
pub fn format_synctoken(synctoken: i64) -> String {
|
||||||
format!("{}{}", SYNC_NAMESPACE, synctoken)
|
format!("{SYNC_NAMESPACE}{synctoken}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_synctoken(synctoken: &str) -> Option<i64> {
|
pub fn parse_synctoken(synctoken: &str) -> Option<i64> {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use async_trait::async_trait;
|
|||||||
use derive_more::derive::Constructor;
|
use derive_more::derive::Constructor;
|
||||||
use rustical_ical::AddressObject;
|
use rustical_ical::AddressObject;
|
||||||
use rustical_store::{
|
use rustical_store::{
|
||||||
Addressbook, AddressbookStore, CollectionOperation, CollectionOperationInfo, Error,
|
Addressbook, AddressbookStore, CollectionMetadata, CollectionOperation,
|
||||||
synctoken::format_synctoken,
|
CollectionOperationInfo, Error, synctoken::format_synctoken,
|
||||||
};
|
};
|
||||||
use sqlx::{Acquire, Executor, Sqlite, SqlitePool, Transaction};
|
use sqlx::{Acquire, Executor, Sqlite, SqlitePool, Transaction};
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
@@ -223,6 +223,28 @@ impl SqliteAddressbookStore {
|
|||||||
Ok((objects, deleted_objects, new_synctoken))
|
Ok((objects, deleted_objects, new_synctoken))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn _list_objects<'e, E: Executor<'e, Database = Sqlite>>(
|
||||||
|
executor: E,
|
||||||
|
principal: &str,
|
||||||
|
addressbook_id: &str,
|
||||||
|
) -> Result<Vec<(u64, bool)>, rustical_store::Error> {
|
||||||
|
struct ObjectEntry {
|
||||||
|
length: u64,
|
||||||
|
deleted: bool,
|
||||||
|
}
|
||||||
|
Ok(sqlx::query_as!(
|
||||||
|
ObjectEntry,
|
||||||
|
"SELECT length(vcf) AS 'length!: u64', deleted_at AS 'deleted!: bool' FROM addressobjects WHERE principal = ? AND addressbook_id = ?",
|
||||||
|
principal,
|
||||||
|
addressbook_id
|
||||||
|
)
|
||||||
|
.fetch_all(executor)
|
||||||
|
.await.map_err(crate::Error::from)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| (row.length, row.deleted))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
async fn _get_objects<'e, E: Executor<'e, Database = Sqlite>>(
|
async fn _get_objects<'e, E: Executor<'e, Database = Sqlite>>(
|
||||||
executor: E,
|
executor: E,
|
||||||
principal: &str,
|
principal: &str,
|
||||||
@@ -442,6 +464,29 @@ impl AddressbookStore for SqliteAddressbookStore {
|
|||||||
Self::_sync_changes(&self.db, principal, addressbook_id, synctoken).await
|
Self::_sync_changes(&self.db, principal, addressbook_id, synctoken).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
async fn addressbook_metadata(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
addressbook_id: &str,
|
||||||
|
) -> Result<CollectionMetadata, rustical_store::Error> {
|
||||||
|
let mut sizes = vec![];
|
||||||
|
let mut deleted_sizes = vec![];
|
||||||
|
for (size, deleted) in Self::_list_objects(&self.db, principal, addressbook_id).await? {
|
||||||
|
if deleted {
|
||||||
|
deleted_sizes.push(size)
|
||||||
|
} else {
|
||||||
|
sizes.push(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(CollectionMetadata {
|
||||||
|
len: sizes.len(),
|
||||||
|
deleted_len: deleted_sizes.len(),
|
||||||
|
size: sizes.iter().sum(),
|
||||||
|
deleted_size: deleted_sizes.iter().sum(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
async fn get_objects(
|
async fn get_objects(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use derive_more::derive::Constructor;
|
|||||||
use rustical_ical::{CalDateTime, CalendarObject, CalendarObjectType};
|
use rustical_ical::{CalDateTime, CalendarObject, CalendarObjectType};
|
||||||
use rustical_store::calendar_store::CalendarQuery;
|
use rustical_store::calendar_store::CalendarQuery;
|
||||||
use rustical_store::synctoken::format_synctoken;
|
use rustical_store::synctoken::format_synctoken;
|
||||||
use rustical_store::{Calendar, CalendarStore, Error};
|
use rustical_store::{Calendar, CalendarStore, CollectionMetadata, Error};
|
||||||
use rustical_store::{CollectionOperation, CollectionOperationInfo};
|
use rustical_store::{CollectionOperation, CollectionOperationInfo};
|
||||||
use sqlx::types::chrono::NaiveDateTime;
|
use sqlx::types::chrono::NaiveDateTime;
|
||||||
use sqlx::{Acquire, Executor, Sqlite, SqlitePool, Transaction};
|
use sqlx::{Acquire, Executor, Sqlite, SqlitePool, Transaction};
|
||||||
@@ -242,6 +242,28 @@ impl SqliteCalendarStore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn _list_objects<'e, E: Executor<'e, Database = Sqlite>>(
|
||||||
|
executor: E,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
) -> Result<Vec<(u64, bool)>, rustical_store::Error> {
|
||||||
|
struct ObjectEntry {
|
||||||
|
length: u64,
|
||||||
|
deleted: bool,
|
||||||
|
}
|
||||||
|
Ok(sqlx::query_as!(
|
||||||
|
ObjectEntry,
|
||||||
|
"SELECT length(ics) AS 'length!: u64', deleted_at AS 'deleted!: bool' FROM calendarobjects WHERE principal = ? AND cal_id = ?",
|
||||||
|
principal,
|
||||||
|
cal_id
|
||||||
|
)
|
||||||
|
.fetch_all(executor)
|
||||||
|
.await.map_err(crate::Error::from)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| (row.length, row.deleted))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
async fn _get_objects<'e, E: Executor<'e, Database = Sqlite>>(
|
async fn _get_objects<'e, E: Executor<'e, Database = Sqlite>>(
|
||||||
executor: E,
|
executor: E,
|
||||||
principal: &str,
|
principal: &str,
|
||||||
@@ -552,6 +574,28 @@ impl CalendarStore for SqliteCalendarStore {
|
|||||||
Self::_calendar_query(&self.db, principal, cal_id, query).await
|
Self::_calendar_query(&self.db, principal, cal_id, query).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn calendar_metadata(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
) -> Result<CollectionMetadata, Error> {
|
||||||
|
let mut sizes = vec![];
|
||||||
|
let mut deleted_sizes = vec![];
|
||||||
|
for (size, deleted) in Self::_list_objects(&self.db, principal, cal_id).await? {
|
||||||
|
if deleted {
|
||||||
|
deleted_sizes.push(size)
|
||||||
|
} else {
|
||||||
|
sizes.push(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(CollectionMetadata {
|
||||||
|
len: sizes.len(),
|
||||||
|
deleted_len: deleted_sizes.len(),
|
||||||
|
size: sizes.iter().sum(),
|
||||||
|
deleted_size: deleted_sizes.iter().sum(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
async fn get_objects(
|
async fn get_objects(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
Reference in New Issue
Block a user