mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
contact: use last_seen column
It was there since the C core, labeled with "/* last_seen is for future use */" but never actually used. The comment was lost during the translation from C to Rust.
This commit is contained in:
@@ -4382,6 +4382,16 @@ uint32_t dc_contact_get_color (const dc_contact_t* contact);
|
|||||||
*/
|
*/
|
||||||
char* dc_contact_get_status (const dc_contact_t* contact);
|
char* dc_contact_get_status (const dc_contact_t* contact);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the contact's last seen timestamp.
|
||||||
|
*
|
||||||
|
* @memberof dc_contact_t
|
||||||
|
* @param contact The contact object.
|
||||||
|
* @return Last seen timestamp.
|
||||||
|
* 0 on error or if the contact was never seen.
|
||||||
|
*/
|
||||||
|
int64_t dc_contact_get_last_seen (const dc_contact_t* contact);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a contact is blocked.
|
* Check if a contact is blocked.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3544,6 +3544,16 @@ pub unsafe extern "C" fn dc_contact_get_status(contact: *mut dc_contact_t) -> *m
|
|||||||
ffi_contact.contact.get_status().strdup()
|
ffi_contact.contact.get_status().strdup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_contact_get_last_seen(contact: *mut dc_contact_t) -> i64 {
|
||||||
|
if contact.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_contact_get_last_seen()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let ffi_contact = &*contact;
|
||||||
|
ffi_contact.contact.last_seen()
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_contact_is_blocked(contact: *mut dc_contact_t) -> libc::c_int {
|
pub unsafe extern "C" fn dc_contact_is_blocked(contact: *mut dc_contact_t) -> libc::c_int {
|
||||||
if contact.is_null() {
|
if contact.is_null() {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
""" Contact object. """
|
""" Contact object. """
|
||||||
|
|
||||||
from . import props
|
from datetime import date, datetime, timezone
|
||||||
from .cutil import from_dc_charpointer, from_optional_dc_charpointer
|
|
||||||
from .capi import lib, ffi
|
|
||||||
from .chat import Chat
|
|
||||||
from . import const
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from . import const, props
|
||||||
|
from .capi import ffi, lib
|
||||||
|
from .chat import Chat
|
||||||
|
from .cutil import from_dc_charpointer, from_optional_dc_charpointer
|
||||||
|
|
||||||
|
|
||||||
class Contact(object):
|
class Contact(object):
|
||||||
""" Delta-Chat Contact.
|
""" Delta-Chat Contact.
|
||||||
@@ -48,6 +49,13 @@ class Contact(object):
|
|||||||
# deprecated alias
|
# deprecated alias
|
||||||
display_name = name
|
display_name = name
|
||||||
|
|
||||||
|
@props.with_doc
|
||||||
|
def last_seen(self) -> date:
|
||||||
|
"""Last seen timestamp."""
|
||||||
|
return datetime.fromtimestamp(
|
||||||
|
lib.dc_contact_get_last_seen(self._dc_contact), timezone.utc
|
||||||
|
)
|
||||||
|
|
||||||
def is_blocked(self):
|
def is_blocked(self):
|
||||||
""" Return True if the contact is blocked. """
|
""" Return True if the contact is blocked. """
|
||||||
return lib.dc_contact_is_blocked(self._dc_contact)
|
return lib.dc_contact_is_blocked(self._dc_contact)
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ pub struct Contact {
|
|||||||
/// Blocked state. Use dc_contact_is_blocked to access this field.
|
/// Blocked state. Use dc_contact_is_blocked to access this field.
|
||||||
pub blocked: bool,
|
pub blocked: bool,
|
||||||
|
|
||||||
|
/// Time when the contact was seen last time, Unix time in seconds.
|
||||||
|
last_seen: i64,
|
||||||
|
|
||||||
/// The origin/source of the contact.
|
/// The origin/source of the contact.
|
||||||
pub origin: Origin,
|
pub origin: Origin,
|
||||||
|
|
||||||
@@ -184,7 +187,8 @@ impl Contact {
|
|||||||
let mut contact = context
|
let mut contact = context
|
||||||
.sql
|
.sql
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param, c.status
|
"SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
|
||||||
|
c.authname, c.param, c.status
|
||||||
FROM contacts c
|
FROM contacts c
|
||||||
WHERE c.id=?;",
|
WHERE c.id=?;",
|
||||||
paramsv![contact_id as i32],
|
paramsv![contact_id as i32],
|
||||||
@@ -193,15 +197,17 @@ impl Contact {
|
|||||||
let addr: String = row.get(1)?;
|
let addr: String = row.get(1)?;
|
||||||
let origin: Origin = row.get(2)?;
|
let origin: Origin = row.get(2)?;
|
||||||
let blocked: Option<bool> = row.get(3)?;
|
let blocked: Option<bool> = row.get(3)?;
|
||||||
let authname: String = row.get(4)?;
|
let last_seen: i64 = row.get(4)?;
|
||||||
let param: String = row.get(5)?;
|
let authname: String = row.get(5)?;
|
||||||
let status: Option<String> = row.get(6)?;
|
let param: String = row.get(6)?;
|
||||||
|
let status: Option<String> = row.get(7)?;
|
||||||
let contact = Self {
|
let contact = Self {
|
||||||
id: contact_id,
|
id: contact_id,
|
||||||
name,
|
name,
|
||||||
authname,
|
authname,
|
||||||
addr,
|
addr,
|
||||||
blocked: blocked.unwrap_or_default(),
|
blocked: blocked.unwrap_or_default(),
|
||||||
|
last_seen,
|
||||||
origin,
|
origin,
|
||||||
param: param.parse().unwrap_or_default(),
|
param: param.parse().unwrap_or_default(),
|
||||||
status: status.unwrap_or_default(),
|
status: status.unwrap_or_default(),
|
||||||
@@ -233,6 +239,11 @@ impl Contact {
|
|||||||
self.blocked
|
self.blocked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns last seen timestamp.
|
||||||
|
pub fn last_seen(&self) -> i64 {
|
||||||
|
self.last_seen
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if a contact is blocked.
|
/// Check if a contact is blocked.
|
||||||
pub async fn is_blocked_load(context: &Context, id: u32) -> Result<bool> {
|
pub async fn is_blocked_load(context: &Context, id: u32) -> Result<bool> {
|
||||||
let blocked = Self::load_from_db(context, id).await?.blocked;
|
let blocked = Self::load_from_db(context, id).await?.blocked;
|
||||||
@@ -1288,6 +1299,27 @@ pub(crate) async fn set_status(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates last seen timestamp of the contact if it is earlier than the given `timestamp`.
|
||||||
|
pub(crate) async fn update_last_seen(
|
||||||
|
context: &Context,
|
||||||
|
contact_id: u32,
|
||||||
|
timestamp: i64,
|
||||||
|
) -> Result<()> {
|
||||||
|
ensure!(
|
||||||
|
contact_id > DC_CONTACT_ID_LAST_SPECIAL,
|
||||||
|
"Can not update special contact last seen timestamp"
|
||||||
|
);
|
||||||
|
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
"UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
|
||||||
|
paramsv![timestamp, contact_id],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Normalize a name.
|
/// Normalize a name.
|
||||||
///
|
///
|
||||||
/// - Remove quotes (come from some bad MUA implementations)
|
/// - Remove quotes (come from some bad MUA implementations)
|
||||||
@@ -1374,6 +1406,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::chat::send_text_msg;
|
use crate::chat::send_text_msg;
|
||||||
|
use crate::dc_receive_imf::dc_receive_imf;
|
||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
use crate::test_utils::{self, TestContext};
|
use crate::test_utils::{self, TestContext};
|
||||||
|
|
||||||
@@ -2031,4 +2064,34 @@ CCCB 5AA9 F6E1 141C 9431
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_last_seen() -> Result<()> {
|
||||||
|
let alice = TestContext::new_alice().await;
|
||||||
|
|
||||||
|
let (contact_id, _) =
|
||||||
|
Contact::add_or_lookup(&alice, "Bob", "bob@example.net", Origin::ManuallyCreated)
|
||||||
|
.await?;
|
||||||
|
let contact = Contact::load_from_db(&alice, contact_id).await?;
|
||||||
|
assert_eq!(contact.last_seen(), 0);
|
||||||
|
|
||||||
|
let mime = br#"Subject: Hello
|
||||||
|
Message-ID: message@example.net
|
||||||
|
To: Alice <alice@example.com>
|
||||||
|
From: Bob <bob@example.net>
|
||||||
|
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||||
|
Chat-Version: 1.0
|
||||||
|
Date: Sun, 22 Mar 2020 22:37:55 +0000
|
||||||
|
|
||||||
|
Hi."#;
|
||||||
|
dc_receive_imf(&alice, mime, "Inbox", 1, false).await?;
|
||||||
|
let msg = alice.get_last_msg().await;
|
||||||
|
|
||||||
|
let timestamp = msg.get_timestamp();
|
||||||
|
assert!(timestamp > 0);
|
||||||
|
let contact = Contact::load_from_db(&alice, contact_id).await?;
|
||||||
|
assert_eq!(contact.last_seen(), timestamp);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,6 +216,10 @@ pub(crate) async fn dc_receive_imf_inner(
|
|||||||
.await
|
.await
|
||||||
.map_err(|err| err.context("add_parts error"))?;
|
.map_err(|err| err.context("add_parts error"))?;
|
||||||
|
|
||||||
|
if from_id > DC_CONTACT_ID_LAST_SPECIAL {
|
||||||
|
contact::update_last_seen(context, from_id, sent_timestamp).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Update gossiped timestamp for the chat if someone else or our other device sent
|
// Update gossiped timestamp for the chat if someone else or our other device sent
|
||||||
// Autocrypt-Gossip for all recipients in the chat to avoid sending Autocrypt-Gossip ourselves
|
// Autocrypt-Gossip for all recipients in the chat to avoid sending Autocrypt-Gossip ourselves
|
||||||
// and waste traffic.
|
// and waste traffic.
|
||||||
|
|||||||
Reference in New Issue
Block a user