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:
link2xt
2021-11-21 12:14:11 +00:00
parent ddefd2cf09
commit 5c571520a0
5 changed files with 104 additions and 9 deletions

View File

@@ -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.
* *

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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(())
}
} }

View File

@@ -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.