fix all the warnings!!!! (0 clippy and rustc warnings now)
too many changes to list, codebase significantly better than it was a few weeks ago though Signed-off-by: strawberry <june@girlboss.ceo>
This commit is contained in:
parent
20b686cac5
commit
87d6a17f0a
27 changed files with 162 additions and 139 deletions
|
@ -50,8 +50,10 @@ pub async fn create_content_route(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let content_uri = mxc.into();
|
||||||
|
|
||||||
Ok(create_content::v3::Response {
|
Ok(create_content::v3::Response {
|
||||||
content_uri: mxc.try_into()?,
|
content_uri,
|
||||||
blurhash: None,
|
blurhash: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -722,7 +722,8 @@ async fn join_room_by_id_helper(
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Running send_join auth check");
|
info!("Running send_join auth check");
|
||||||
if !state_res::event_auth::auth_check(
|
|
||||||
|
let auth_check = state_res::event_auth::auth_check(
|
||||||
&state_res::RoomVersion::new(&room_version_id).expect("room version is supported"),
|
&state_res::RoomVersion::new(&room_version_id).expect("room version is supported"),
|
||||||
&parsed_join_pdu,
|
&parsed_join_pdu,
|
||||||
None::<PduEvent>, // TODO: third party invite
|
None::<PduEvent>, // TODO: third party invite
|
||||||
|
@ -745,7 +746,9 @@ async fn join_room_by_id_helper(
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
warn!("Auth check failed: {e}");
|
warn!("Auth check failed: {e}");
|
||||||
Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed")
|
Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed")
|
||||||
})? {
|
})?;
|
||||||
|
|
||||||
|
if !auth_check {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::InvalidParam,
|
ErrorKind::InvalidParam,
|
||||||
"Auth check failed",
|
"Auth check failed",
|
||||||
|
|
|
@ -572,7 +572,7 @@ pub async fn get_server_version_route(
|
||||||
|
|
||||||
Ok(get_server_version::v1::Response {
|
Ok(get_server_version::v1::Response {
|
||||||
server: Some(get_server_version::v1::Server {
|
server: Some(get_server_version::v1::Server {
|
||||||
name: Some("cowonduit".to_owned()),
|
name: Some(env!("CARGO_CRATE_NAME").to_owned()),
|
||||||
version: Some(env!("CARGO_PKG_VERSION").to_owned()),
|
version: Some(env!("CARGO_PKG_VERSION").to_owned()),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
|
@ -122,6 +122,7 @@ pub struct TlsConfig {
|
||||||
const DEPRECATED_KEYS: &[&str] = &["cache_capacity"];
|
const DEPRECATED_KEYS: &[&str] = &["cache_capacity"];
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
/// Iterates over all the keys in the config file and warns if there is a deprecated key specified
|
||||||
pub fn warn_deprecated(&self) {
|
pub fn warn_deprecated(&self) {
|
||||||
let mut was_deprecated = false;
|
let mut was_deprecated = false;
|
||||||
for key in self
|
for key in self
|
||||||
|
@ -139,16 +140,17 @@ impl Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks the presence of the `address` and `unix_socket_path` keys in the raw_config, exiting the process if both keys were detected.
|
/// Checks the presence of the `address` and `unix_socket_path` keys in the raw_config, exiting the process if both keys were detected.
|
||||||
pub fn error_dual_listening(&self, raw_config: Figment) -> Result<(), ()> {
|
pub fn is_dual_listening(&self, raw_config: Figment) -> bool {
|
||||||
let check_address = raw_config.find_value("address");
|
let check_address = raw_config.find_value("address");
|
||||||
let check_unix_socket = raw_config.find_value("unix_socket_path");
|
let check_unix_socket = raw_config.find_value("unix_socket_path");
|
||||||
|
|
||||||
|
// are the check_address and check_unix_socket keys both Ok (specified) at the same time?
|
||||||
if check_address.is_ok() && check_unix_socket.is_ok() {
|
if check_address.is_ok() && check_unix_socket.is_ok() {
|
||||||
error!("TOML keys \"address\" and \"unix_socket_path\" were both defined. Please specify only one option.");
|
error!("TOML keys \"address\" and \"unix_socket_path\" were both defined. Please specify only one option.");
|
||||||
return Err(());
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,11 @@ use std::{
|
||||||
};
|
};
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
||||||
|
type Watcher = RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(super) struct Watchers {
|
pub(super) struct Watchers {
|
||||||
watchers: RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>,
|
watchers: Watcher,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Watchers {
|
impl Watchers {
|
||||||
|
|
|
@ -6,6 +6,7 @@ use ruma::{
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
RoomId, UserId,
|
RoomId, UserId,
|
||||||
};
|
};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
||||||
|
|
||||||
|
@ -123,13 +124,15 @@ impl service::account_data::Data for KeyValueDatabase {
|
||||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||||
.map(|(k, v)| {
|
.map(|(k, v)| {
|
||||||
Ok::<_, Error>((
|
Ok::<_, Error>((
|
||||||
RoomAccountDataEventType::try_from(
|
RoomAccountDataEventType::from(
|
||||||
utils::string_from_bytes(k.rsplit(|&b| b == 0xff).next().ok_or_else(
|
utils::string_from_bytes(k.rsplit(|&b| b == 0xff).next().ok_or_else(
|
||||||
|| Error::bad_database("RoomUserData ID in db is invalid."),
|
|| Error::bad_database("RoomUserData ID in db is invalid."),
|
||||||
)?)
|
)?)
|
||||||
.map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?,
|
.map_err(|e| {
|
||||||
)
|
warn!("RoomUserData ID in database is invalid: {}", e);
|
||||||
.map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?,
|
Error::bad_database("RoomUserData ID in db is invalid.")
|
||||||
|
})?,
|
||||||
|
),
|
||||||
serde_json::from_slice::<Raw<AnyEphemeralRoomEvent>>(&v).map_err(|_| {
|
serde_json::from_slice::<Raw<AnyEphemeralRoomEvent>>(&v).map_err(|_| {
|
||||||
Error::bad_database("Database contains invalid account data.")
|
Error::bad_database("Database contains invalid account data.")
|
||||||
})?,
|
})?,
|
||||||
|
|
|
@ -4,8 +4,11 @@ use ruma::{EventId, RoomId, UserId};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::KeyValueDatabase,
|
database::KeyValueDatabase,
|
||||||
service::{self, rooms::timeline::PduCount},
|
service::{
|
||||||
services, utils, Error, PduEvent, Result,
|
self,
|
||||||
|
rooms::timeline::{data::PduData, PduCount},
|
||||||
|
},
|
||||||
|
services, utils, Error, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl service::rooms::pdu_metadata::Data for KeyValueDatabase {
|
impl service::rooms::pdu_metadata::Data for KeyValueDatabase {
|
||||||
|
@ -22,7 +25,7 @@ impl service::rooms::pdu_metadata::Data for KeyValueDatabase {
|
||||||
shortroomid: u64,
|
shortroomid: u64,
|
||||||
target: u64,
|
target: u64,
|
||||||
until: PduCount,
|
until: PduCount,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
) -> PduData<'a> {
|
||||||
let prefix = target.to_be_bytes().to_vec();
|
let prefix = target.to_be_bytes().to_vec();
|
||||||
let mut current = prefix.clone();
|
let mut current = prefix.clone();
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ use ruma::RoomId;
|
||||||
|
|
||||||
use crate::{database::KeyValueDatabase, service, services, utils, Result};
|
use crate::{database::KeyValueDatabase, service, services, utils, Result};
|
||||||
|
|
||||||
|
type SearchPdusResult<'a> = Result<Option<(Box<dyn Iterator<Item = Vec<u8>> + 'a>, Vec<String>)>>;
|
||||||
|
|
||||||
impl service::rooms::search::Data for KeyValueDatabase {
|
impl service::rooms::search::Data for KeyValueDatabase {
|
||||||
fn index_pdu<'a>(&self, shortroomid: u64, pdu_id: &[u8], message_body: &str) -> Result<()> {
|
fn index_pdu<'a>(&self, shortroomid: u64, pdu_id: &[u8], message_body: &str) -> Result<()> {
|
||||||
let mut batch = message_body
|
let mut batch = message_body
|
||||||
|
@ -20,11 +22,7 @@ impl service::rooms::search::Data for KeyValueDatabase {
|
||||||
self.tokenids.insert_batch(&mut batch)
|
self.tokenids.insert_batch(&mut batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_pdus<'a>(
|
fn search_pdus<'a>(&'a self, room_id: &RoomId, search_string: &str) -> SearchPdusResult<'a> {
|
||||||
&'a self,
|
|
||||||
room_id: &RoomId,
|
|
||||||
search_string: &str,
|
|
||||||
) -> Result<Option<(Box<dyn Iterator<Item = Vec<u8>> + 'a>, Vec<String>)>> {
|
|
||||||
let prefix = services()
|
let prefix = services()
|
||||||
.rooms
|
.rooms
|
||||||
.short
|
.short
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ruma::{events::StateEventType, EventId, RoomId};
|
use ruma::{events::StateEventType, EventId, RoomId};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
||||||
|
|
||||||
|
@ -157,10 +158,10 @@ impl service::rooms::short::Data for KeyValueDatabase {
|
||||||
.ok_or_else(|| Error::bad_database("Invalid statekey in shortstatekey_statekey."))?;
|
.ok_or_else(|| Error::bad_database("Invalid statekey in shortstatekey_statekey."))?;
|
||||||
|
|
||||||
let event_type =
|
let event_type =
|
||||||
StateEventType::try_from(utils::string_from_bytes(eventtype_bytes).map_err(|_| {
|
StateEventType::from(utils::string_from_bytes(eventtype_bytes).map_err(|e| {
|
||||||
Error::bad_database("Event type in shortstatekey_statekey is invalid unicode.")
|
warn!("Event type in shortstatekey_statekey is invalid: {}", e);
|
||||||
})?)
|
Error::bad_database("Event type in shortstatekey_statekey is invalid.")
|
||||||
.map_err(|_| Error::bad_database("Event type in shortstatekey_statekey is invalid."))?;
|
})?);
|
||||||
|
|
||||||
let state_key = utils::string_from_bytes(statekey_bytes).map_err(|_| {
|
let state_key = utils::string_from_bytes(statekey_bytes).map_err(|_| {
|
||||||
Error::bad_database("Statekey in shortstatekey_statekey is invalid unicode.")
|
Error::bad_database("Statekey in shortstatekey_statekey is invalid unicode.")
|
||||||
|
|
|
@ -9,6 +9,12 @@ use ruma::{
|
||||||
|
|
||||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
||||||
|
|
||||||
|
type StrippedStateEventIter<'a> =
|
||||||
|
Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>;
|
||||||
|
|
||||||
|
type AnySyncStateEventIter<'a> =
|
||||||
|
Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnySyncStateEvent>>)>> + 'a>;
|
||||||
|
|
||||||
impl service::rooms::state_cache::Data for KeyValueDatabase {
|
impl service::rooms::state_cache::Data for KeyValueDatabase {
|
||||||
fn mark_as_once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<()> {
|
fn mark_as_once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<()> {
|
||||||
let mut userroom_id = user_id.as_bytes().to_vec();
|
let mut userroom_id = user_id.as_bytes().to_vec();
|
||||||
|
@ -472,10 +478,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
||||||
|
|
||||||
/// Returns an iterator over all rooms a user was invited to.
|
/// Returns an iterator over all rooms a user was invited to.
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
fn rooms_invited<'a>(
|
fn rooms_invited<'a>(&'a self, user_id: &UserId) -> StrippedStateEventIter<'a> {
|
||||||
&'a self,
|
|
||||||
user_id: &UserId,
|
|
||||||
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a> {
|
|
||||||
let mut prefix = user_id.as_bytes().to_vec();
|
let mut prefix = user_id.as_bytes().to_vec();
|
||||||
prefix.push(0xff);
|
prefix.push(0xff);
|
||||||
|
|
||||||
|
@ -550,10 +553,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
||||||
|
|
||||||
/// Returns an iterator over all rooms a user left.
|
/// Returns an iterator over all rooms a user left.
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
fn rooms_left<'a>(
|
fn rooms_left<'a>(&'a self, user_id: &UserId) -> AnySyncStateEventIter<'a> {
|
||||||
&'a self,
|
|
||||||
user_id: &UserId,
|
|
||||||
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnySyncStateEvent>>)>> + 'a> {
|
|
||||||
let mut prefix = user_id.as_bytes().to_vec();
|
let mut prefix = user_id.as_bytes().to_vec();
|
||||||
prefix.push(0xff);
|
prefix.push(0xff);
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, R
|
||||||
|
|
||||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, PduEvent, Result};
|
use crate::{database::KeyValueDatabase, service, services, utils, Error, PduEvent, Result};
|
||||||
|
|
||||||
|
type PduEventIterResult<'a> = Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>>;
|
||||||
|
|
||||||
impl service::rooms::threads::Data for KeyValueDatabase {
|
impl service::rooms::threads::Data for KeyValueDatabase {
|
||||||
fn threads_until<'a>(
|
fn threads_until<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
@ -11,7 +13,7 @@ impl service::rooms::threads::Data for KeyValueDatabase {
|
||||||
room_id: &'a RoomId,
|
room_id: &'a RoomId,
|
||||||
until: u64,
|
until: u64,
|
||||||
_include: &'a IncludeThreads,
|
_include: &'a IncludeThreads,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>> {
|
) -> PduEventIterResult<'a> {
|
||||||
let prefix = services()
|
let prefix = services()
|
||||||
.rooms
|
.rooms
|
||||||
.short
|
.short
|
||||||
|
|
|
@ -5,7 +5,11 @@ use ruma::{
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, PduEvent, Result};
|
use crate::{
|
||||||
|
database::KeyValueDatabase,
|
||||||
|
service::{self, rooms::timeline::data::PduData},
|
||||||
|
services, utils, Error, PduEvent, Result,
|
||||||
|
};
|
||||||
|
|
||||||
use service::rooms::timeline::PduCount;
|
use service::rooms::timeline::PduCount;
|
||||||
|
|
||||||
|
@ -228,7 +232,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
until: PduCount,
|
until: PduCount,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
) -> PduData<'a> {
|
||||||
let (prefix, current) = count_to_id(room_id, until, 1, true)?;
|
let (prefix, current) = count_to_id(room_id, until, 1, true)?;
|
||||||
|
|
||||||
let user_id = user_id.to_owned();
|
let user_id = user_id.to_owned();
|
||||||
|
@ -250,12 +254,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pdus_after<'a>(
|
fn pdus_after<'a>(&'a self, user_id: &UserId, room_id: &RoomId, from: PduCount) -> PduData<'a> {
|
||||||
&'a self,
|
|
||||||
user_id: &UserId,
|
|
||||||
room_id: &RoomId,
|
|
||||||
from: PduCount,
|
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
|
||||||
let (prefix, current) = count_to_id(room_id, from, 1, false)?;
|
let (prefix, current) = count_to_id(room_id, from, 1, false)?;
|
||||||
|
|
||||||
let user_id = user_id.to_owned();
|
let user_id = user_id.to_owned();
|
||||||
|
|
|
@ -146,10 +146,12 @@ impl service::users::Data for KeyValueDatabase {
|
||||||
self.userid_avatarurl
|
self.userid_avatarurl
|
||||||
.get(user_id.as_bytes())?
|
.get(user_id.as_bytes())?
|
||||||
.map(|bytes| {
|
.map(|bytes| {
|
||||||
let s = utils::string_from_bytes(&bytes)
|
let s_bytes = utils::string_from_bytes(&bytes).map_err(|e| {
|
||||||
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))?;
|
warn!("Avatar URL in db is invalid: {}", e);
|
||||||
s.try_into()
|
Error::bad_database("Avatar URL in db is invalid.")
|
||||||
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))
|
})?;
|
||||||
|
let mxc_uri: OwnedMxcUri = s_bytes.into();
|
||||||
|
Ok(mxc_uri)
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ async fn main() {
|
||||||
maximize_fd_limit().expect("should be able to increase the soft limit to the hard limit");
|
maximize_fd_limit().expect("should be able to increase the soft limit to the hard limit");
|
||||||
|
|
||||||
config.warn_deprecated();
|
config.warn_deprecated();
|
||||||
if config.error_dual_listening(raw_config).is_err() {
|
if config.is_dual_listening(raw_config) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -542,7 +542,7 @@ async fn initial_sync(_uri: Uri) -> impl IntoResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn it_works() -> &'static str {
|
async fn it_works() -> &'static str {
|
||||||
"hewwo from cowonduit woof!"
|
"hewwo from conduwuit woof!"
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -109,13 +109,15 @@ impl Default for RotationHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DnsOverrides = Box<dyn Fn(&str) -> Option<SocketAddr> + Send + Sync>;
|
||||||
|
|
||||||
pub struct Resolver {
|
pub struct Resolver {
|
||||||
inner: GaiResolver,
|
inner: GaiResolver,
|
||||||
overrides: Box<dyn Fn(&str) -> Option<SocketAddr> + Send + Sync>,
|
overrides: DnsOverrides,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolver {
|
impl Resolver {
|
||||||
pub fn new(overrides: Box<dyn Fn(&str) -> Option<SocketAddr> + Send + Sync>) -> Resolver {
|
pub fn new(overrides: DnsOverrides) -> Resolver {
|
||||||
Resolver {
|
Resolver {
|
||||||
inner: GaiResolver::new(),
|
inner: GaiResolver::new(),
|
||||||
overrides,
|
overrides,
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use ruma::{events::receipt::ReceiptEvent, serde::Raw, OwnedUserId, RoomId, UserId};
|
use ruma::{
|
||||||
|
events::{receipt::ReceiptEvent, AnySyncEphemeralRoomEvent},
|
||||||
|
serde::Raw,
|
||||||
|
OwnedUserId, RoomId, UserId,
|
||||||
|
};
|
||||||
|
|
||||||
|
type AnySyncEphemeralRoomEventIter<'a> =
|
||||||
|
Box<dyn Iterator<Item = Result<(OwnedUserId, u64, Raw<AnySyncEphemeralRoomEvent>)>> + 'a>;
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
/// Replaces the previous read receipt.
|
/// Replaces the previous read receipt.
|
||||||
|
@ -11,19 +18,8 @@ pub trait Data: Send + Sync {
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
/// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`.
|
/// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`.
|
||||||
fn readreceipts_since<'a>(
|
fn readreceipts_since(&self, room_id: &RoomId, since: u64)
|
||||||
&'a self,
|
-> AnySyncEphemeralRoomEventIter<'_>;
|
||||||
room_id: &RoomId,
|
|
||||||
since: u64,
|
|
||||||
) -> Box<
|
|
||||||
dyn Iterator<
|
|
||||||
Item = Result<(
|
|
||||||
OwnedUserId,
|
|
||||||
u64,
|
|
||||||
Raw<ruma::events::AnySyncEphemeralRoomEvent>,
|
|
||||||
)>,
|
|
||||||
> + 'a,
|
|
||||||
>;
|
|
||||||
|
|
||||||
/// Sets a private read marker at `count`.
|
/// Sets a private read marker at `count`.
|
||||||
fn private_read_set(&self, room_id: &RoomId, user_id: &UserId, count: u64) -> Result<()>;
|
fn private_read_set(&self, room_id: &RoomId, user_id: &UserId, count: u64) -> Result<()>;
|
||||||
|
|
|
@ -40,6 +40,11 @@ use crate::{service::*, services, Error, PduEvent, Result};
|
||||||
|
|
||||||
use super::state_compressor::CompressedStateEvent;
|
use super::state_compressor::CompressedStateEvent;
|
||||||
|
|
||||||
|
type AsyncRecursiveCanonicalJsonVec<'a> =
|
||||||
|
AsyncRecursiveType<'a, Vec<(Arc<PduEvent>, Option<BTreeMap<String, CanonicalJsonValue>>)>>;
|
||||||
|
type AsyncRecursiveCanonicalJsonResult<'a> =
|
||||||
|
AsyncRecursiveType<'a, Result<(Arc<PduEvent>, BTreeMap<String, CanonicalJsonValue>)>>;
|
||||||
|
|
||||||
pub struct Service;
|
pub struct Service;
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
|
@ -287,7 +292,7 @@ impl Service {
|
||||||
mut value: BTreeMap<String, CanonicalJsonValue>,
|
mut value: BTreeMap<String, CanonicalJsonValue>,
|
||||||
auth_events_known: bool,
|
auth_events_known: bool,
|
||||||
pub_key_map: &'a RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
|
pub_key_map: &'a RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
|
||||||
) -> AsyncRecursiveType<'a, Result<(Arc<PduEvent>, BTreeMap<String, CanonicalJsonValue>)>> {
|
) -> AsyncRecursiveCanonicalJsonResult<'a> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
// 1. Remove unsigned field
|
// 1. Remove unsigned field
|
||||||
value.remove("unsigned");
|
value.remove("unsigned");
|
||||||
|
@ -1022,8 +1027,7 @@ impl Service {
|
||||||
room_id: &'a RoomId,
|
room_id: &'a RoomId,
|
||||||
room_version_id: &'a RoomVersionId,
|
room_version_id: &'a RoomVersionId,
|
||||||
pub_key_map: &'a RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
|
pub_key_map: &'a RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
|
||||||
) -> AsyncRecursiveType<'a, Vec<(Arc<PduEvent>, Option<BTreeMap<String, CanonicalJsonValue>>)>>
|
) -> AsyncRecursiveCanonicalJsonVec<'a> {
|
||||||
{
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let back_off = |id| match services()
|
let back_off = |id| match services()
|
||||||
.globals
|
.globals
|
||||||
|
|
|
@ -11,11 +11,13 @@ use crate::Result;
|
||||||
|
|
||||||
use super::timeline::PduCount;
|
use super::timeline::PduCount;
|
||||||
|
|
||||||
|
type LazyLoadWaitingMutex =
|
||||||
|
Mutex<HashMap<(OwnedUserId, OwnedDeviceId, OwnedRoomId, PduCount), HashSet<OwnedUserId>>>;
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
pub db: &'static dyn Data,
|
pub db: &'static dyn Data,
|
||||||
|
|
||||||
pub lazy_load_waiting:
|
pub lazy_load_waiting: LazyLoadWaitingMutex,
|
||||||
Mutex<HashMap<(OwnedUserId, OwnedDeviceId, OwnedRoomId, PduCount), HashSet<OwnedUserId>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{service::rooms::timeline::PduCount, PduEvent, Result};
|
use crate::{
|
||||||
|
service::rooms::timeline::{data::PduData, PduCount},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
use ruma::{EventId, RoomId, UserId};
|
use ruma::{EventId, RoomId, UserId};
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
|
@ -11,7 +14,7 @@ pub trait Data: Send + Sync {
|
||||||
room_id: u64,
|
room_id: u64,
|
||||||
target: u64,
|
target: u64,
|
||||||
until: PduCount,
|
until: PduCount,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>>;
|
) -> PduData<'a>;
|
||||||
fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()>;
|
fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()>;
|
||||||
fn is_event_referenced(&self, room_id: &RoomId, event_id: &EventId) -> Result<bool>;
|
fn is_event_referenced(&self, room_id: &RoomId, event_id: &EventId) -> Result<bool>;
|
||||||
fn mark_event_soft_failed(&self, event_id: &EventId) -> Result<()>;
|
fn mark_event_soft_failed(&self, event_id: &EventId) -> Result<()>;
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use ruma::RoomId;
|
use ruma::RoomId;
|
||||||
|
|
||||||
|
type SearchPdusResult<'a> = Result<Option<(Box<dyn Iterator<Item = Vec<u8>> + 'a>, Vec<String>)>>;
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
fn index_pdu(&self, shortroomid: u64, pdu_id: &[u8], message_body: &str) -> Result<()>;
|
fn index_pdu(&self, shortroomid: u64, pdu_id: &[u8], message_body: &str) -> Result<()>;
|
||||||
|
|
||||||
fn search_pdus<'a>(
|
fn search_pdus<'a>(&'a self, room_id: &RoomId, search_string: &str) -> SearchPdusResult<'a>;
|
||||||
&'a self,
|
|
||||||
room_id: &RoomId,
|
|
||||||
search_string: &str,
|
|
||||||
) -> Result<Option<(Box<dyn Iterator<Item = Vec<u8>> + 'a>, Vec<String>)>>;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,12 @@ use ruma::{
|
||||||
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StrippedStateEventIter<'a> =
|
||||||
|
Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>;
|
||||||
|
|
||||||
|
type AnySyncStateEventIter<'a> =
|
||||||
|
Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnySyncStateEvent>>)>> + 'a>;
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
fn mark_as_once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<()>;
|
fn mark_as_once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<()>;
|
||||||
fn mark_as_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<()>;
|
fn mark_as_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<()>;
|
||||||
|
@ -78,10 +84,7 @@ pub trait Data: Send + Sync {
|
||||||
) -> Box<dyn Iterator<Item = Result<OwnedRoomId>> + 'a>;
|
) -> Box<dyn Iterator<Item = Result<OwnedRoomId>> + 'a>;
|
||||||
|
|
||||||
/// Returns an iterator over all rooms a user was invited to.
|
/// Returns an iterator over all rooms a user was invited to.
|
||||||
fn rooms_invited<'a>(
|
fn rooms_invited<'a>(&'a self, user_id: &UserId) -> StrippedStateEventIter<'a>;
|
||||||
&'a self,
|
|
||||||
user_id: &UserId,
|
|
||||||
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>;
|
|
||||||
|
|
||||||
fn invite_state(
|
fn invite_state(
|
||||||
&self,
|
&self,
|
||||||
|
@ -96,10 +99,7 @@ pub trait Data: Send + Sync {
|
||||||
) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>>;
|
) -> Result<Option<Vec<Raw<AnyStrippedStateEvent>>>>;
|
||||||
|
|
||||||
/// Returns an iterator over all rooms a user left.
|
/// Returns an iterator over all rooms a user left.
|
||||||
fn rooms_left<'a>(
|
fn rooms_left<'a>(&'a self, user_id: &UserId) -> AnySyncStateEventIter<'a>;
|
||||||
&'a self,
|
|
||||||
user_id: &UserId,
|
|
||||||
) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnySyncStateEvent>>)>> + 'a>;
|
|
||||||
|
|
||||||
fn once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool>;
|
fn once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool>;
|
||||||
|
|
||||||
|
|
|
@ -13,20 +13,44 @@ use crate::{services, utils, Result};
|
||||||
|
|
||||||
use self::data::StateDiff;
|
use self::data::StateDiff;
|
||||||
|
|
||||||
|
type StateInfoLruCache = Mutex<
|
||||||
|
LruCache<
|
||||||
|
u64,
|
||||||
|
Vec<(
|
||||||
|
u64, // sstatehash
|
||||||
|
Arc<HashSet<CompressedStateEvent>>, // full state
|
||||||
|
Arc<HashSet<CompressedStateEvent>>, // added
|
||||||
|
Arc<HashSet<CompressedStateEvent>>, // removed
|
||||||
|
)>,
|
||||||
|
>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
type ShortStateInfoResult = Result<
|
||||||
|
Vec<(
|
||||||
|
u64, // sstatehash
|
||||||
|
Arc<HashSet<CompressedStateEvent>>, // full state
|
||||||
|
Arc<HashSet<CompressedStateEvent>>, // added
|
||||||
|
Arc<HashSet<CompressedStateEvent>>, // removed
|
||||||
|
)>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
type ParentStatesVec = Vec<(
|
||||||
|
u64, // sstatehash
|
||||||
|
Arc<HashSet<CompressedStateEvent>>, // full state
|
||||||
|
Arc<HashSet<CompressedStateEvent>>, // added
|
||||||
|
Arc<HashSet<CompressedStateEvent>>, // removed
|
||||||
|
)>;
|
||||||
|
|
||||||
|
type HashSetCompressStateEvent = Result<(
|
||||||
|
u64,
|
||||||
|
Arc<HashSet<CompressedStateEvent>>,
|
||||||
|
Arc<HashSet<CompressedStateEvent>>,
|
||||||
|
)>;
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
pub db: &'static dyn Data,
|
pub db: &'static dyn Data,
|
||||||
|
|
||||||
pub stateinfo_cache: Mutex<
|
pub stateinfo_cache: StateInfoLruCache,
|
||||||
LruCache<
|
|
||||||
u64,
|
|
||||||
Vec<(
|
|
||||||
u64, // sstatehash
|
|
||||||
Arc<HashSet<CompressedStateEvent>>, // full state
|
|
||||||
Arc<HashSet<CompressedStateEvent>>, // added
|
|
||||||
Arc<HashSet<CompressedStateEvent>>, // removed
|
|
||||||
)>,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type CompressedStateEvent = [u8; 2 * size_of::<u64>()];
|
pub type CompressedStateEvent = [u8; 2 * size_of::<u64>()];
|
||||||
|
@ -34,17 +58,7 @@ pub type CompressedStateEvent = [u8; 2 * size_of::<u64>()];
|
||||||
impl Service {
|
impl Service {
|
||||||
/// Returns a stack with info on shortstatehash, full state, added diff and removed diff for the selected shortstatehash and each parent layer.
|
/// Returns a stack with info on shortstatehash, full state, added diff and removed diff for the selected shortstatehash and each parent layer.
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn load_shortstatehash_info(
|
pub fn load_shortstatehash_info(&self, shortstatehash: u64) -> ShortStateInfoResult {
|
||||||
&self,
|
|
||||||
shortstatehash: u64,
|
|
||||||
) -> Result<
|
|
||||||
Vec<(
|
|
||||||
u64, // sstatehash
|
|
||||||
Arc<HashSet<CompressedStateEvent>>, // full state
|
|
||||||
Arc<HashSet<CompressedStateEvent>>, // added
|
|
||||||
Arc<HashSet<CompressedStateEvent>>, // removed
|
|
||||||
)>,
|
|
||||||
> {
|
|
||||||
if let Some(r) = self
|
if let Some(r) = self
|
||||||
.stateinfo_cache
|
.stateinfo_cache
|
||||||
.lock()
|
.lock()
|
||||||
|
@ -144,12 +158,7 @@ impl Service {
|
||||||
statediffnew: Arc<HashSet<CompressedStateEvent>>,
|
statediffnew: Arc<HashSet<CompressedStateEvent>>,
|
||||||
statediffremoved: Arc<HashSet<CompressedStateEvent>>,
|
statediffremoved: Arc<HashSet<CompressedStateEvent>>,
|
||||||
diff_to_sibling: usize,
|
diff_to_sibling: usize,
|
||||||
mut parent_states: Vec<(
|
mut parent_states: ParentStatesVec,
|
||||||
u64, // sstatehash
|
|
||||||
Arc<HashSet<CompressedStateEvent>>, // full state
|
|
||||||
Arc<HashSet<CompressedStateEvent>>, // added
|
|
||||||
Arc<HashSet<CompressedStateEvent>>, // removed
|
|
||||||
)>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let diffsum = statediffnew.len() + statediffremoved.len();
|
let diffsum = statediffnew.len() + statediffremoved.len();
|
||||||
|
|
||||||
|
@ -257,11 +266,7 @@ impl Service {
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
new_state_ids_compressed: Arc<HashSet<CompressedStateEvent>>,
|
new_state_ids_compressed: Arc<HashSet<CompressedStateEvent>>,
|
||||||
) -> Result<(
|
) -> HashSetCompressStateEvent {
|
||||||
u64,
|
|
||||||
Arc<HashSet<CompressedStateEvent>>,
|
|
||||||
Arc<HashSet<CompressedStateEvent>>,
|
|
||||||
)> {
|
|
||||||
let previous_shortstatehash = services().rooms.state.get_room_shortstatehash(room_id)?;
|
let previous_shortstatehash = services().rooms.state.get_room_shortstatehash(room_id)?;
|
||||||
|
|
||||||
let state_hash = utils::calculate_hash(
|
let state_hash = utils::calculate_hash(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::{PduEvent, Result};
|
use crate::{PduEvent, Result};
|
||||||
use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId};
|
use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId};
|
||||||
|
|
||||||
|
type PduEventIterResult<'a> = Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>>;
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
fn threads_until<'a>(
|
fn threads_until<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
@ -8,7 +10,7 @@ pub trait Data: Send + Sync {
|
||||||
room_id: &'a RoomId,
|
room_id: &'a RoomId,
|
||||||
until: u64,
|
until: u64,
|
||||||
include: &'a IncludeThreads,
|
include: &'a IncludeThreads,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>>;
|
) -> PduEventIterResult<'a>;
|
||||||
|
|
||||||
fn update_participants(&self, root_id: &[u8], participants: &[OwnedUserId]) -> Result<()>;
|
fn update_participants(&self, root_id: &[u8], participants: &[OwnedUserId]) -> Result<()>;
|
||||||
fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>>;
|
fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>>;
|
||||||
|
|
|
@ -6,6 +6,8 @@ use crate::{PduEvent, Result};
|
||||||
|
|
||||||
use super::PduCount;
|
use super::PduCount;
|
||||||
|
|
||||||
|
pub type PduData<'a> = Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>>;
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
fn last_timeline_count(&self, sender_user: &UserId, room_id: &RoomId) -> Result<PduCount>;
|
fn last_timeline_count(&self, sender_user: &UserId, room_id: &RoomId) -> Result<PduCount>;
|
||||||
|
|
||||||
|
@ -66,21 +68,12 @@ pub trait Data: Send + Sync {
|
||||||
|
|
||||||
/// Returns an iterator over all events and their tokens in a room that happened before the
|
/// Returns an iterator over all events and their tokens in a room that happened before the
|
||||||
/// event with id `until` in reverse-chronological order.
|
/// event with id `until` in reverse-chronological order.
|
||||||
fn pdus_until<'a>(
|
fn pdus_until<'a>(&'a self, user_id: &UserId, room_id: &RoomId, until: PduCount)
|
||||||
&'a self,
|
-> PduData<'a>;
|
||||||
user_id: &UserId,
|
|
||||||
room_id: &RoomId,
|
|
||||||
until: PduCount,
|
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>>;
|
|
||||||
|
|
||||||
/// Returns an iterator over all events in a room that happened after the event with id `from`
|
/// Returns an iterator over all events in a room that happened after the event with id `from`
|
||||||
/// in chronological order.
|
/// in chronological order.
|
||||||
fn pdus_after<'a>(
|
fn pdus_after<'a>(&'a self, user_id: &UserId, room_id: &RoomId, from: PduCount) -> PduData<'a>;
|
||||||
&'a self,
|
|
||||||
user_id: &UserId,
|
|
||||||
room_id: &RoomId,
|
|
||||||
from: PduCount,
|
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>>;
|
|
||||||
|
|
||||||
fn increment_notification_counts(
|
fn increment_notification_counts(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mod data;
|
pub(crate) mod data;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
|
|
|
@ -4,14 +4,13 @@ use crate::Result;
|
||||||
|
|
||||||
use super::{OutgoingKind, SendingEventType};
|
use super::{OutgoingKind, SendingEventType};
|
||||||
|
|
||||||
|
type OutgoingSendingIter<'a> =
|
||||||
|
Box<dyn Iterator<Item = Result<(Vec<u8>, OutgoingKind, SendingEventType)>> + 'a>;
|
||||||
|
type SendingEventTypeIter<'a> = Box<dyn Iterator<Item = Result<(Vec<u8>, SendingEventType)>> + 'a>;
|
||||||
|
|
||||||
pub trait Data: Send + Sync {
|
pub trait Data: Send + Sync {
|
||||||
fn active_requests<'a>(
|
fn active_requests(&self) -> OutgoingSendingIter<'_>;
|
||||||
&'a self,
|
fn active_requests_for(&self, outgoing_kind: &OutgoingKind) -> SendingEventTypeIter<'_>;
|
||||||
) -> Box<dyn Iterator<Item = Result<(Vec<u8>, OutgoingKind, SendingEventType)>> + 'a>;
|
|
||||||
fn active_requests_for<'a>(
|
|
||||||
&'a self,
|
|
||||||
outgoing_kind: &OutgoingKind,
|
|
||||||
) -> Box<dyn Iterator<Item = Result<(Vec<u8>, SendingEventType)>> + 'a>;
|
|
||||||
fn delete_active_request(&self, key: Vec<u8>) -> Result<()>;
|
fn delete_active_request(&self, key: Vec<u8>) -> Result<()>;
|
||||||
fn delete_all_active_requests_for(&self, outgoing_kind: &OutgoingKind) -> Result<()>;
|
fn delete_all_active_requests_for(&self, outgoing_kind: &OutgoingKind) -> Result<()>;
|
||||||
fn delete_all_requests_for(&self, outgoing_kind: &OutgoingKind) -> Result<()>;
|
fn delete_all_requests_for(&self, outgoing_kind: &OutgoingKind) -> Result<()>;
|
||||||
|
|
|
@ -32,10 +32,12 @@ pub struct SlidingSyncCache {
|
||||||
extensions: ExtensionsConfig,
|
extensions: ExtensionsConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DbConnections =
|
||||||
|
Mutex<BTreeMap<(OwnedUserId, OwnedDeviceId, String), Arc<Mutex<SlidingSyncCache>>>>;
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
pub db: &'static dyn Data,
|
pub db: &'static dyn Data,
|
||||||
pub connections:
|
pub connections: DbConnections,
|
||||||
Mutex<BTreeMap<(OwnedUserId, OwnedDeviceId, String), Arc<Mutex<SlidingSyncCache>>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
|
|
Loading…
Add table
Reference in a new issue