mirror of
https://github.com/chatmail/core.git
synced 2026-05-16 21:36:30 +03:00
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -463,6 +463,7 @@ version = "1.0.0-alpha.4"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
"backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ itertools = "0.8.0"
|
|||||||
image-meta = "0.1.0"
|
image-meta = "0.1.0"
|
||||||
quick-xml = "0.15.0"
|
quick-xml = "0.15.0"
|
||||||
escaper = "0.1.0"
|
escaper = "0.1.0"
|
||||||
|
bitflags = "1.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.0"
|
tempfile = "3.0"
|
||||||
|
|||||||
@@ -1043,7 +1043,7 @@ pub unsafe extern "C" fn dc_send_locations_to_chat(
|
|||||||
assert!(!context.is_null());
|
assert!(!context.is_null());
|
||||||
let context = &*context;
|
let context = &*context;
|
||||||
|
|
||||||
dc_location::dc_send_locations_to_chat(context, chat_id, seconds as i64)
|
location::send_locations_to_chat(context, chat_id, seconds as i64)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -1054,7 +1054,7 @@ pub unsafe extern "C" fn dc_is_sending_locations_to_chat(
|
|||||||
assert!(!context.is_null());
|
assert!(!context.is_null());
|
||||||
let context = &*context;
|
let context = &*context;
|
||||||
|
|
||||||
dc_location::dc_is_sending_locations_to_chat(context, chat_id) as libc::c_int
|
location::is_sending_locations_to_chat(context, chat_id) as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -1067,7 +1067,7 @@ pub unsafe extern "C" fn dc_set_location(
|
|||||||
assert!(!context.is_null());
|
assert!(!context.is_null());
|
||||||
let context = &*context;
|
let context = &*context;
|
||||||
|
|
||||||
dc_location::dc_set_location(context, latitude, longitude, accuracy)
|
location::set(context, latitude, longitude, accuracy)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -1081,7 +1081,7 @@ pub unsafe extern "C" fn dc_get_locations(
|
|||||||
assert!(!context.is_null());
|
assert!(!context.is_null());
|
||||||
let context = &*context;
|
let context = &*context;
|
||||||
|
|
||||||
let res = dc_location::dc_get_locations(
|
let res = location::get_range(
|
||||||
context,
|
context,
|
||||||
chat_id,
|
chat_id,
|
||||||
contact_id,
|
contact_id,
|
||||||
@@ -1096,7 +1096,7 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) {
|
|||||||
assert!(!context.is_null());
|
assert!(!context.is_null());
|
||||||
let context = &*context;
|
let context = &*context;
|
||||||
|
|
||||||
dc_location::dc_delete_all_locations(context);
|
location::delete_all(context).log_err(context, "Failed to delete locations");
|
||||||
}
|
}
|
||||||
|
|
||||||
// dc_array_t
|
// dc_array_t
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ use deltachat::constants::*;
|
|||||||
use deltachat::contact::*;
|
use deltachat::contact::*;
|
||||||
use deltachat::context::*;
|
use deltachat::context::*;
|
||||||
use deltachat::dc_imex::*;
|
use deltachat::dc_imex::*;
|
||||||
use deltachat::dc_location::*;
|
|
||||||
use deltachat::dc_receive_imf::*;
|
use deltachat::dc_receive_imf::*;
|
||||||
use deltachat::dc_tools::*;
|
use deltachat::dc_tools::*;
|
||||||
use deltachat::error::Error;
|
use deltachat::error::Error;
|
||||||
use deltachat::job::*;
|
use deltachat::job::*;
|
||||||
|
use deltachat::location;
|
||||||
use deltachat::lot::LotState;
|
use deltachat::lot::LotState;
|
||||||
use deltachat::message::*;
|
use deltachat::message::*;
|
||||||
use deltachat::peerstate::*;
|
use deltachat::peerstate::*;
|
||||||
@@ -652,7 +652,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dc_is_sending_locations_to_chat(context, 0 as uint32_t) {
|
if location::is_sending_locations_to_chat(context, 0 as uint32_t) {
|
||||||
info!(context, 0, "Location streaming enabled.");
|
info!(context, 0, "Location streaming enabled.");
|
||||||
}
|
}
|
||||||
println!("{} chats", cnt);
|
println!("{} chats", cnt);
|
||||||
@@ -778,14 +778,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
println!(
|
println!(
|
||||||
"{} contacts\nLocation streaming: {}",
|
"{} contacts\nLocation streaming: {}",
|
||||||
contacts.len(),
|
contacts.len(),
|
||||||
dc_is_sending_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id()),
|
location::is_sending_locations_to_chat(
|
||||||
|
context,
|
||||||
|
sel_chat.as_ref().unwrap().get_id()
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"getlocations" => {
|
"getlocations" => {
|
||||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||||
|
|
||||||
let contact_id = arg1.parse().unwrap_or_default();
|
let contact_id = arg1.parse().unwrap_or_default();
|
||||||
let locations = dc_get_locations(
|
let locations = location::get_range(
|
||||||
context,
|
context,
|
||||||
sel_chat.as_ref().unwrap().get_id(),
|
sel_chat.as_ref().unwrap().get_id(),
|
||||||
contact_id,
|
contact_id,
|
||||||
@@ -819,7 +822,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
ensure!(!arg1.is_empty(), "No timeout given.");
|
ensure!(!arg1.is_empty(), "No timeout given.");
|
||||||
|
|
||||||
let seconds = arg1.parse()?;
|
let seconds = arg1.parse()?;
|
||||||
dc_send_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id(), seconds);
|
location::send_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id(), seconds);
|
||||||
println!(
|
println!(
|
||||||
"Locations will be sent to Chat#{} for {} seconds. Use 'setlocation <lat> <lng>' to play around.",
|
"Locations will be sent to Chat#{} for {} seconds. Use 'setlocation <lat> <lng>' to play around.",
|
||||||
sel_chat.as_ref().unwrap().get_id(),
|
sel_chat.as_ref().unwrap().get_id(),
|
||||||
@@ -834,7 +837,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
let latitude = arg1.parse()?;
|
let latitude = arg1.parse()?;
|
||||||
let longitude = arg2.parse()?;
|
let longitude = arg2.parse()?;
|
||||||
|
|
||||||
let continue_streaming = dc_set_location(context, latitude, longitude, 0.);
|
let continue_streaming = location::set(context, latitude, longitude, 0.);
|
||||||
if 0 != continue_streaming {
|
if 0 != continue_streaming {
|
||||||
println!("Success, streaming should be continued.");
|
println!("Success, streaming should be continued.");
|
||||||
} else {
|
} else {
|
||||||
@@ -842,7 +845,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"dellocations" => {
|
"dellocations" => {
|
||||||
dc_delete_all_locations(context);
|
location::delete_all(context)?;
|
||||||
}
|
}
|
||||||
"send" => {
|
"send" => {
|
||||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use crate::dc_location::dc_location;
|
use crate::location::Location;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
/* * the structure behind dc_array_t */
|
/* * the structure behind dc_array_t */
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum dc_array_t {
|
pub enum dc_array_t {
|
||||||
Locations(Vec<dc_location>),
|
Locations(Vec<Location>),
|
||||||
Uint(Vec<u32>),
|
Uint(Vec<u32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ impl dc_array_t {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_location(&mut self, location: dc_location) {
|
pub fn add_location(&mut self, location: Location) {
|
||||||
if let Self::Locations(array) = self {
|
if let Self::Locations(array) = self {
|
||||||
array.push(location)
|
array.push(location)
|
||||||
} else {
|
} else {
|
||||||
@@ -42,7 +42,7 @@ impl dc_array_t {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_location(&self, index: usize) -> &dc_location {
|
pub fn get_location(&self, index: usize) -> &Location {
|
||||||
if let Self::Locations(array) = self {
|
if let Self::Locations(array) = self {
|
||||||
&array[index]
|
&array[index]
|
||||||
} else {
|
} else {
|
||||||
@@ -108,8 +108,8 @@ impl From<Vec<u32>> for dc_array_t {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<dc_location>> for dc_array_t {
|
impl From<Vec<Location>> for dc_array_t {
|
||||||
fn from(array: Vec<dc_location>) -> Self {
|
fn from(array: Vec<Location>) -> Self {
|
||||||
dc_array_t::Locations(array)
|
dc_array_t::Locations(array)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,776 +0,0 @@
|
|||||||
use quick_xml;
|
|
||||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
|
||||||
|
|
||||||
use crate::chat;
|
|
||||||
use crate::constants::Event;
|
|
||||||
use crate::constants::*;
|
|
||||||
use crate::context::*;
|
|
||||||
use crate::dc_tools::*;
|
|
||||||
use crate::job::*;
|
|
||||||
use crate::message::*;
|
|
||||||
use crate::param::*;
|
|
||||||
use crate::sql;
|
|
||||||
use crate::stock::StockMessage;
|
|
||||||
use crate::types::*;
|
|
||||||
use crate::x::*;
|
|
||||||
|
|
||||||
// location handling
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub struct dc_location {
|
|
||||||
pub location_id: uint32_t,
|
|
||||||
pub latitude: libc::c_double,
|
|
||||||
pub longitude: libc::c_double,
|
|
||||||
pub accuracy: libc::c_double,
|
|
||||||
pub timestamp: i64,
|
|
||||||
pub contact_id: uint32_t,
|
|
||||||
pub msg_id: uint32_t,
|
|
||||||
pub chat_id: uint32_t,
|
|
||||||
pub marker: Option<String>,
|
|
||||||
pub independent: uint32_t,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl dc_location {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
dc_location {
|
|
||||||
location_id: 0,
|
|
||||||
latitude: 0.0,
|
|
||||||
longitude: 0.0,
|
|
||||||
accuracy: 0.0,
|
|
||||||
timestamp: 0,
|
|
||||||
contact_id: 0,
|
|
||||||
msg_id: 0,
|
|
||||||
chat_id: 0,
|
|
||||||
marker: None,
|
|
||||||
independent: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub struct dc_kml_t {
|
|
||||||
pub addr: *mut libc::c_char,
|
|
||||||
pub locations: Option<Vec<dc_location>>,
|
|
||||||
pub tag: libc::c_int,
|
|
||||||
pub curr: dc_location,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl dc_kml_t {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
dc_kml_t {
|
|
||||||
addr: std::ptr::null_mut(),
|
|
||||||
locations: None,
|
|
||||||
tag: 0,
|
|
||||||
curr: dc_location::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// location streaming
|
|
||||||
pub unsafe fn dc_send_locations_to_chat(context: &Context, chat_id: uint32_t, seconds: i64) {
|
|
||||||
let now = time();
|
|
||||||
let mut msg: Message;
|
|
||||||
let is_sending_locations_before: bool;
|
|
||||||
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
|
|
||||||
is_sending_locations_before = dc_is_sending_locations_to_chat(context, chat_id);
|
|
||||||
if sql::execute(
|
|
||||||
context,
|
|
||||||
&context.sql,
|
|
||||||
"UPDATE chats \
|
|
||||||
SET locations_send_begin=?, \
|
|
||||||
locations_send_until=? \
|
|
||||||
WHERE id=?",
|
|
||||||
params![
|
|
||||||
if 0 != seconds { now } else { 0 },
|
|
||||||
if 0 != seconds { now + seconds } else { 0 },
|
|
||||||
chat_id as i32,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
if 0 != seconds && !is_sending_locations_before {
|
|
||||||
msg = dc_msg_new(context, Viewtype::Text);
|
|
||||||
msg.text =
|
|
||||||
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
|
||||||
msg.param.set_int(Param::Cmd, 8);
|
|
||||||
chat::send_msg(context, chat_id, &mut msg).unwrap();
|
|
||||||
} else if 0 == seconds && is_sending_locations_before {
|
|
||||||
let stock_str =
|
|
||||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
|
||||||
chat::add_device_msg(context, chat_id, stock_str);
|
|
||||||
}
|
|
||||||
context.call_cb(
|
|
||||||
Event::CHAT_MODIFIED,
|
|
||||||
chat_id as uintptr_t,
|
|
||||||
0i32 as uintptr_t,
|
|
||||||
);
|
|
||||||
if 0 != seconds {
|
|
||||||
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
|
||||||
job_add(
|
|
||||||
context,
|
|
||||||
Action::MaybeSendLocationsEnded,
|
|
||||||
chat_id as libc::c_int,
|
|
||||||
Params::new(),
|
|
||||||
seconds + 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* job to send locations out to all chats that want them
|
|
||||||
******************************************************************************/
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
unsafe fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: libc::c_int) {
|
|
||||||
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
|
|
||||||
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.exists(
|
|
||||||
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
|
|
||||||
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
|
|
||||||
)
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_set_location(
|
|
||||||
context: &Context,
|
|
||||||
latitude: libc::c_double,
|
|
||||||
longitude: libc::c_double,
|
|
||||||
accuracy: libc::c_double,
|
|
||||||
) -> libc::c_int {
|
|
||||||
if latitude == 0.0 && longitude == 0.0 {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.sql.query_map(
|
|
||||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
|
||||||
params![time()], |row| row.get::<_, i32>(0),
|
|
||||||
|chats| {
|
|
||||||
let mut continue_streaming = false;
|
|
||||||
|
|
||||||
for chat in chats {
|
|
||||||
let chat_id = chat?;
|
|
||||||
context.sql.execute(
|
|
||||||
"INSERT INTO locations \
|
|
||||||
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
|
|
||||||
params![
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
accuracy,
|
|
||||||
time(),
|
|
||||||
chat_id,
|
|
||||||
1,
|
|
||||||
]
|
|
||||||
)?;
|
|
||||||
continue_streaming = true;
|
|
||||||
}
|
|
||||||
if continue_streaming {
|
|
||||||
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
|
|
||||||
};
|
|
||||||
unsafe { schedule_MAYBE_SEND_LOCATIONS(context, 0) };
|
|
||||||
Ok(continue_streaming as libc::c_int)
|
|
||||||
}
|
|
||||||
).unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_get_locations(
|
|
||||||
context: &Context,
|
|
||||||
chat_id: uint32_t,
|
|
||||||
contact_id: uint32_t,
|
|
||||||
timestamp_from: i64,
|
|
||||||
mut timestamp_to: i64,
|
|
||||||
) -> Vec<dc_location> {
|
|
||||||
if timestamp_to == 0 {
|
|
||||||
timestamp_to = time() + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.query_map(
|
|
||||||
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
|
|
||||||
m.id, l.from_id, l.chat_id, m.txt \
|
|
||||||
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
|
|
||||||
AND (? OR l.from_id=?) \
|
|
||||||
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
|
||||||
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
|
|
||||||
params![
|
|
||||||
if chat_id == 0 { 1 } else { 0 },
|
|
||||||
chat_id as i32,
|
|
||||||
if contact_id == 0 { 1 } else { 0 },
|
|
||||||
contact_id as i32,
|
|
||||||
timestamp_from,
|
|
||||||
timestamp_to,
|
|
||||||
],
|
|
||||||
|row| {
|
|
||||||
let msg_id = row.get(6)?;
|
|
||||||
let txt: String = row.get(9)?;
|
|
||||||
let marker = if msg_id != 0 && is_marker(&txt) {
|
|
||||||
Some(txt)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let loc = dc_location {
|
|
||||||
location_id: row.get(0)?,
|
|
||||||
latitude: row.get(1)?,
|
|
||||||
longitude: row.get(2)?,
|
|
||||||
accuracy: row.get(3)?,
|
|
||||||
timestamp: row.get(4)?,
|
|
||||||
independent: row.get(5)?,
|
|
||||||
msg_id,
|
|
||||||
contact_id: row.get(7)?,
|
|
||||||
chat_id: row.get(8)?,
|
|
||||||
marker,
|
|
||||||
};
|
|
||||||
Ok(loc)
|
|
||||||
},
|
|
||||||
|locations| {
|
|
||||||
let mut ret = Vec::new();
|
|
||||||
|
|
||||||
for location in locations {
|
|
||||||
ret.push(location?);
|
|
||||||
}
|
|
||||||
Ok(ret)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_marker(txt: &str) -> bool {
|
|
||||||
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_delete_all_locations(context: &Context) -> bool {
|
|
||||||
if sql::execute(context, &context.sql, "DELETE FROM locations;", params![]).is_err() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_get_location_kml(
|
|
||||||
context: &Context,
|
|
||||||
chat_id: uint32_t,
|
|
||||||
last_added_location_id: *mut uint32_t,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
let mut success: libc::c_int = 0;
|
|
||||||
let now = time();
|
|
||||||
let mut location_count: libc::c_int = 0;
|
|
||||||
let mut ret = String::new();
|
|
||||||
|
|
||||||
let self_addr = context
|
|
||||||
.sql
|
|
||||||
.get_config(context, "configured_addr")
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if let Ok((locations_send_begin, locations_send_until, locations_last_sent)) = context.sql.query_row(
|
|
||||||
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
|
|
||||||
params![chat_id as i32], |row| {
|
|
||||||
let send_begin: i64 = row.get(0)?;
|
|
||||||
let send_until: i64 = row.get(1)?;
|
|
||||||
let last_sent: i64 = row.get(2)?;
|
|
||||||
|
|
||||||
Ok((send_begin, send_until, last_sent))
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if !(locations_send_begin == 0 || now > locations_send_until) {
|
|
||||||
ret += &format!(
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
|
|
||||||
self_addr,
|
|
||||||
);
|
|
||||||
|
|
||||||
context.sql.query_map(
|
|
||||||
"SELECT id, latitude, longitude, accuracy, timestamp\
|
|
||||||
FROM locations WHERE from_id=? \
|
|
||||||
AND timestamp>=? \
|
|
||||||
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
|
|
||||||
AND independent=0 \
|
|
||||||
GROUP BY timestamp \
|
|
||||||
ORDER BY timestamp;",
|
|
||||||
params![1, locations_send_begin, locations_last_sent, 1],
|
|
||||||
|row| {
|
|
||||||
let location_id: i32 = row.get(0)?;
|
|
||||||
let latitude: f64 = row.get(1)?;
|
|
||||||
let longitude: f64 = row.get(2)?;
|
|
||||||
let accuracy: f64 = row.get(3)?;
|
|
||||||
let timestamp = unsafe { get_kml_timestamp(row.get(4)?) };
|
|
||||||
|
|
||||||
Ok((location_id, latitude, longitude, accuracy, timestamp))
|
|
||||||
},
|
|
||||||
|rows| {
|
|
||||||
for row in rows {
|
|
||||||
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
|
|
||||||
ret += &format!(
|
|
||||||
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
|
|
||||||
as_str(timestamp),
|
|
||||||
accuracy,
|
|
||||||
longitude,
|
|
||||||
latitude
|
|
||||||
);
|
|
||||||
location_count += 1;
|
|
||||||
if !last_added_location_id.is_null() {
|
|
||||||
unsafe { *last_added_location_id = location_id as u32 };
|
|
||||||
}
|
|
||||||
unsafe { free(timestamp as *mut libc::c_void) };
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
).unwrap(); // TODO: better error handling
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if location_count > 0 {
|
|
||||||
ret += "</Document>\n</kml>";
|
|
||||||
success = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if 0 != success {
|
|
||||||
unsafe { ret.strdup() }
|
|
||||||
} else {
|
|
||||||
std::ptr::null_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* create kml-files
|
|
||||||
******************************************************************************/
|
|
||||||
unsafe fn get_kml_timestamp(utc: i64) -> *mut libc::c_char {
|
|
||||||
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
|
|
||||||
let res = chrono::NaiveDateTime::from_timestamp(utc, 0)
|
|
||||||
.format("%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
.to_string();
|
|
||||||
res.strdup()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn dc_get_message_kml(
|
|
||||||
timestamp: i64,
|
|
||||||
latitude: libc::c_double,
|
|
||||||
longitude: libc::c_double,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
let timestamp_str = get_kml_timestamp(timestamp);
|
|
||||||
let latitude_str = dc_ftoa(latitude);
|
|
||||||
let longitude_str = dc_ftoa(longitude);
|
|
||||||
|
|
||||||
let ret = dc_mprintf(
|
|
||||||
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
|
||||||
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
|
|
||||||
<Document>\n\
|
|
||||||
<Placemark>\
|
|
||||||
<Timestamp><when>%s</when></Timestamp>\
|
|
||||||
<Point><coordinates>%s,%s</coordinates></Point>\
|
|
||||||
</Placemark>\n\
|
|
||||||
</Document>\n\
|
|
||||||
</kml>\x00" as *const u8 as *const libc::c_char,
|
|
||||||
timestamp_str,
|
|
||||||
longitude_str, // reverse order!
|
|
||||||
latitude_str,
|
|
||||||
);
|
|
||||||
|
|
||||||
free(latitude_str as *mut libc::c_void);
|
|
||||||
free(longitude_str as *mut libc::c_void);
|
|
||||||
free(timestamp_str as *mut libc::c_void);
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_set_kml_sent_timestamp(context: &Context, chat_id: u32, timestamp: i64) -> bool {
|
|
||||||
sql::execute(
|
|
||||||
context,
|
|
||||||
&context.sql,
|
|
||||||
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
|
||||||
params![timestamp, chat_id as i32],
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> bool {
|
|
||||||
sql::execute(
|
|
||||||
context,
|
|
||||||
&context.sql,
|
|
||||||
"UPDATE msgs SET location_id=? WHERE id=?;",
|
|
||||||
params![location_id, msg_id as i32],
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn dc_save_locations(
|
|
||||||
context: &Context,
|
|
||||||
chat_id: u32,
|
|
||||||
contact_id: u32,
|
|
||||||
locations_opt: &Option<Vec<dc_location>>,
|
|
||||||
independent: libc::c_int,
|
|
||||||
) -> u32 {
|
|
||||||
if chat_id <= 9 || locations_opt.is_none() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let locations = locations_opt.as_ref().unwrap();
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.prepare2(
|
|
||||||
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
|
|
||||||
"INSERT INTO locations\
|
|
||||||
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
|
||||||
VALUES (?,?,?,?,?,?,?);",
|
|
||||||
|mut stmt_test, mut stmt_insert, conn| {
|
|
||||||
let mut newest_timestamp = 0;
|
|
||||||
let mut newest_location_id = 0;
|
|
||||||
|
|
||||||
for location in locations {
|
|
||||||
let exists =
|
|
||||||
stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
|
||||||
|
|
||||||
if 0 != independent || !exists {
|
|
||||||
stmt_insert.execute(params![
|
|
||||||
location.timestamp,
|
|
||||||
contact_id as i32,
|
|
||||||
chat_id as i32,
|
|
||||||
location.latitude,
|
|
||||||
location.longitude,
|
|
||||||
location.accuracy,
|
|
||||||
independent,
|
|
||||||
])?;
|
|
||||||
|
|
||||||
if location.timestamp > newest_timestamp {
|
|
||||||
newest_timestamp = location.timestamp;
|
|
||||||
newest_location_id = sql::get_rowid2_with_conn(
|
|
||||||
context,
|
|
||||||
conn,
|
|
||||||
"locations",
|
|
||||||
"timestamp",
|
|
||||||
location.timestamp,
|
|
||||||
"from_id",
|
|
||||||
contact_id as i32,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(newest_location_id)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn dc_kml_parse(
|
|
||||||
context: &Context,
|
|
||||||
content: *const libc::c_char,
|
|
||||||
content_bytes: size_t,
|
|
||||||
) -> dc_kml_t {
|
|
||||||
let mut kml = dc_kml_t::new();
|
|
||||||
|
|
||||||
if content_bytes > (1 * 1024 * 1024) {
|
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
0, "A kml-files with {} bytes is larger than reasonably expected.", content_bytes,
|
|
||||||
);
|
|
||||||
return kml;
|
|
||||||
}
|
|
||||||
|
|
||||||
let content_null = dc_null_terminate(content, content_bytes as libc::c_int);
|
|
||||||
if !content_null.is_null() {
|
|
||||||
let mut reader = quick_xml::Reader::from_str(as_str(content_null));
|
|
||||||
reader.trim_text(true);
|
|
||||||
|
|
||||||
kml.locations = Some(Vec::with_capacity(100));
|
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match reader.read_event(&mut buf) {
|
|
||||||
Ok(quick_xml::events::Event::Start(ref e)) => kml_starttag_cb(e, &mut kml, &reader),
|
|
||||||
Ok(quick_xml::events::Event::End(ref e)) => kml_endtag_cb(e, &mut kml),
|
|
||||||
Ok(quick_xml::events::Event::Text(ref e)) => kml_text_cb(e, &mut kml, &reader),
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
context,
|
|
||||||
0,
|
|
||||||
"Location parsing: Error at position {}: {:?}",
|
|
||||||
reader.buffer_position(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::Eof) => break,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
buf.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(content_null.cast());
|
|
||||||
|
|
||||||
kml
|
|
||||||
}
|
|
||||||
|
|
||||||
fn kml_text_cb<B: std::io::BufRead>(
|
|
||||||
event: &BytesText,
|
|
||||||
kml: &mut dc_kml_t,
|
|
||||||
reader: &quick_xml::Reader<B>,
|
|
||||||
) {
|
|
||||||
if 0 != kml.tag & (0x4 | 0x10) {
|
|
||||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
|
||||||
|
|
||||||
let val = val
|
|
||||||
.replace("\n", "")
|
|
||||||
.replace("\r", "")
|
|
||||||
.replace("\t", "")
|
|
||||||
.replace(" ", "");
|
|
||||||
|
|
||||||
if 0 != kml.tag & 0x4 && val.len() >= 19 {
|
|
||||||
// YYYY-MM-DDTHH:MM:SSZ
|
|
||||||
// 0 4 7 10 13 16 19
|
|
||||||
match chrono::NaiveDateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%SZ") {
|
|
||||||
Ok(res) => {
|
|
||||||
kml.curr.timestamp = res.timestamp();
|
|
||||||
if kml.curr.timestamp > time() {
|
|
||||||
kml.curr.timestamp = time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_err) => {
|
|
||||||
kml.curr.timestamp = time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if 0 != kml.tag & 0x10 {
|
|
||||||
let parts = val.splitn(2, ',').collect::<Vec<_>>();
|
|
||||||
if parts.len() == 2 {
|
|
||||||
kml.curr.longitude = parts[0].parse().unwrap_or_default();
|
|
||||||
kml.curr.latitude = parts[1].parse().unwrap_or_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn kml_endtag_cb(event: &BytesEnd, kml: &mut dc_kml_t) {
|
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
|
||||||
|
|
||||||
if tag == "placemark" {
|
|
||||||
if 0 != kml.tag & 0x1
|
|
||||||
&& 0 != kml.curr.timestamp
|
|
||||||
&& 0. != kml.curr.latitude
|
|
||||||
&& 0. != kml.curr.longitude
|
|
||||||
{
|
|
||||||
if let Some(ref mut locations) = kml.locations {
|
|
||||||
locations.push(std::mem::replace(&mut kml.curr, dc_location::new()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kml.tag = 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn kml_starttag_cb<B: std::io::BufRead>(
|
|
||||||
event: &BytesStart,
|
|
||||||
kml: &mut dc_kml_t,
|
|
||||||
reader: &quick_xml::Reader<B>,
|
|
||||||
) {
|
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
|
||||||
if tag == "document" {
|
|
||||||
if let Some(addr) = event.attributes().find(|attr| {
|
|
||||||
attr.as_ref()
|
|
||||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "addr")
|
|
||||||
.unwrap_or_default()
|
|
||||||
}) {
|
|
||||||
kml.addr = unsafe {
|
|
||||||
addr.unwrap()
|
|
||||||
.unescape_and_decode_value(reader)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.strdup()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if tag == "placemark" {
|
|
||||||
kml.tag = 0x1;
|
|
||||||
kml.curr.timestamp = 0;
|
|
||||||
kml.curr.latitude = 0 as libc::c_double;
|
|
||||||
kml.curr.longitude = 0.0f64;
|
|
||||||
kml.curr.accuracy = 0.0f64
|
|
||||||
} else if tag == "timestamp" && 0 != kml.tag & 0x1 {
|
|
||||||
kml.tag = 0x1 | 0x2
|
|
||||||
} else if tag == "when" && 0 != kml.tag & 0x2 {
|
|
||||||
kml.tag = 0x1 | 0x2 | 0x4
|
|
||||||
} else if tag == "point" && 0 != kml.tag & 0x1 {
|
|
||||||
kml.tag = 0x1 | 0x8
|
|
||||||
} else if tag == "coordinates" && 0 != kml.tag & 0x8 {
|
|
||||||
kml.tag = 0x1 | 0x8 | 0x10;
|
|
||||||
if let Some(acc) = event.attributes().find(|attr| {
|
|
||||||
attr.as_ref()
|
|
||||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "accuracy")
|
|
||||||
.unwrap_or_default()
|
|
||||||
}) {
|
|
||||||
let v = acc
|
|
||||||
.unwrap()
|
|
||||||
.unescape_and_decode_value(reader)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
kml.curr.accuracy = v.trim().parse().unwrap_or_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn dc_kml_unref(kml: &mut dc_kml_t) {
|
|
||||||
free(kml.addr as *mut libc::c_void);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
|
||||||
let now = time();
|
|
||||||
let mut continue_streaming: libc::c_int = 1;
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
|
|
||||||
);
|
|
||||||
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.query_map(
|
|
||||||
"SELECT id, locations_send_begin, locations_last_sent \
|
|
||||||
FROM chats \
|
|
||||||
WHERE locations_send_until>?;",
|
|
||||||
params![now],
|
|
||||||
|row| {
|
|
||||||
let chat_id: i32 = row.get(0)?;
|
|
||||||
let locations_send_begin: i64 = row.get(1)?;
|
|
||||||
let locations_last_sent: i64 = row.get(2)?;
|
|
||||||
continue_streaming = 1;
|
|
||||||
|
|
||||||
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
|
||||||
if now - locations_last_sent < (60 - 3) {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|rows| {
|
|
||||||
context.sql.prepare(
|
|
||||||
"SELECT id \
|
|
||||||
FROM locations \
|
|
||||||
WHERE from_id=? \
|
|
||||||
AND timestamp>=? \
|
|
||||||
AND timestamp>? \
|
|
||||||
AND independent=0 \
|
|
||||||
ORDER BY timestamp;",
|
|
||||||
|mut stmt_locations, _| {
|
|
||||||
for (chat_id, locations_send_begin, locations_last_sent) in
|
|
||||||
rows.filter_map(|r| match r {
|
|
||||||
Ok(Some(v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
{
|
|
||||||
// TODO: do I need to reset?
|
|
||||||
if !stmt_locations
|
|
||||||
.exists(params![1, locations_send_begin, locations_last_sent,])
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
// if there is no new location, there's nothing to send.
|
|
||||||
// however, maybe we want to bypass this test eg. 15 minutes
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// pending locations are attached automatically to every message,
|
|
||||||
// so also to this empty text message.
|
|
||||||
// DC_CMD_LOCATION is only needed to create a nicer subject.
|
|
||||||
//
|
|
||||||
// for optimisation and to avoid flooding the sending queue,
|
|
||||||
// we could sending these messages only if we're really online.
|
|
||||||
// the easiest way to determine this, is to check for an empty message queue.
|
|
||||||
// (might not be 100%, however, as positions are sent combined later
|
|
||||||
// and dc_set_location() is typically called periodically, this is ok)
|
|
||||||
let mut msg = dc_msg_new(context, Viewtype::Text);
|
|
||||||
msg.hidden = true;
|
|
||||||
msg.param.set_int(Param::Cmd, 9);
|
|
||||||
// TODO: handle cleanup on error
|
|
||||||
chat::send_msg(context, chat_id as u32, &mut msg).unwrap();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap(); // TODO: Better error handling
|
|
||||||
|
|
||||||
if 0 != continue_streaming {
|
|
||||||
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
|
|
||||||
// this function is called when location-streaming _might_ have ended for a chat.
|
|
||||||
// the function checks, if location-streaming is really ended;
|
|
||||||
// if so, a device-message is added if not yet done.
|
|
||||||
|
|
||||||
let chat_id = (*job).foreign_id;
|
|
||||||
|
|
||||||
if let Ok((send_begin, send_until)) = context.sql.query_row(
|
|
||||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
|
||||||
params![chat_id as i32],
|
|
||||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
|
||||||
) {
|
|
||||||
if !(send_begin != 0 && time() <= send_until) {
|
|
||||||
// still streaming -
|
|
||||||
// may happen as several calls to dc_send_locations_to_chat()
|
|
||||||
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
|
||||||
if !(send_begin == 0 && send_until == 0) {
|
|
||||||
// not streaming, device-message already sent
|
|
||||||
if context.sql.execute(
|
|
||||||
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
|
||||||
params![chat_id as i32],
|
|
||||||
).is_ok() {
|
|
||||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
|
||||||
chat::add_device_msg(context, chat_id, stock_str);
|
|
||||||
context.call_cb(
|
|
||||||
Event::CHAT_MODIFIED,
|
|
||||||
chat_id as usize,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::test_utils::dummy_context;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_dc_kml_parse() {
|
|
||||||
unsafe {
|
|
||||||
let context = dummy_context();
|
|
||||||
|
|
||||||
let xml =
|
|
||||||
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>\x00"
|
|
||||||
as *const u8 as *const libc::c_char;
|
|
||||||
|
|
||||||
let mut kml = dc_kml_parse(&context.ctx, xml, strlen(xml));
|
|
||||||
|
|
||||||
assert!(!kml.addr.is_null());
|
|
||||||
assert_eq!(as_str(kml.addr as *const libc::c_char), "user@example.org",);
|
|
||||||
|
|
||||||
let locations_ref = &kml.locations.as_ref().unwrap();
|
|
||||||
assert_eq!(locations_ref.len(), 2);
|
|
||||||
|
|
||||||
assert!(locations_ref[0].latitude > 53.6f64);
|
|
||||||
assert!(locations_ref[0].latitude < 53.8f64);
|
|
||||||
assert!(locations_ref[0].longitude > 9.3f64);
|
|
||||||
assert!(locations_ref[0].longitude < 9.5f64);
|
|
||||||
assert!(locations_ref[0].accuracy > 31.9f64);
|
|
||||||
assert!(locations_ref[0].accuracy < 32.1f64);
|
|
||||||
assert_eq!(locations_ref[0].timestamp, 1551906597);
|
|
||||||
|
|
||||||
assert!(locations_ref[1].latitude > 63.6f64);
|
|
||||||
assert!(locations_ref[1].latitude < 63.8f64);
|
|
||||||
assert!(locations_ref[1].longitude > 19.3f64);
|
|
||||||
assert!(locations_ref[1].longitude < 19.5f64);
|
|
||||||
assert!(locations_ref[1].accuracy > 2.4f64);
|
|
||||||
assert!(locations_ref[1].accuracy < 2.6f64);
|
|
||||||
assert_eq!(locations_ref[1].timestamp, 1544739072);
|
|
||||||
|
|
||||||
dc_kml_unref(&mut kml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,10 +16,10 @@ use crate::constants::*;
|
|||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::{dc_get_version_str, Context};
|
use crate::context::{dc_get_version_str, Context};
|
||||||
use crate::dc_e2ee::*;
|
use crate::dc_e2ee::*;
|
||||||
use crate::dc_location::*;
|
|
||||||
use crate::dc_strencode::*;
|
use crate::dc_strencode::*;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use crate::location;
|
||||||
use crate::message::*;
|
use crate::message::*;
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
use crate::stock::StockMessage;
|
use crate::stock::StockMessage;
|
||||||
@@ -883,45 +883,47 @@ pub unsafe fn dc_mimefactory_render(factory: &mut dc_mimefactory_t) -> libc::c_i
|
|||||||
.param
|
.param
|
||||||
.get_float(Param::SetLongitude)
|
.get_float(Param::SetLongitude)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let kml_file =
|
let kml_file = location::get_message_kml(
|
||||||
dc_get_message_kml(factory.msg.timestamp_sort, latitude, longitude);
|
factory.msg.timestamp_sort,
|
||||||
if !kml_file.is_null() {
|
latitude,
|
||||||
|
longitude,
|
||||||
|
);
|
||||||
|
let content_type = mailmime_content_new_with_str(
|
||||||
|
b"application/vnd.google-earth.kml+xml\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
let mime_fields = mailmime_fields_new_filename(
|
||||||
|
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
||||||
|
dc_strdup(b"message.kml\x00" as *const u8 as *const libc::c_char),
|
||||||
|
MAILMIME_MECHANISM_8BIT as libc::c_int,
|
||||||
|
);
|
||||||
|
let kml_mime_part = mailmime_new_empty(content_type, mime_fields);
|
||||||
|
mailmime_set_body_text(kml_mime_part, kml_file.strdup(), kml_file.len());
|
||||||
|
mailmime_smart_add_part(message, kml_mime_part);
|
||||||
|
}
|
||||||
|
|
||||||
|
if location::is_sending_locations_to_chat(
|
||||||
|
factory.msg.context,
|
||||||
|
factory.msg.chat_id,
|
||||||
|
) {
|
||||||
|
if let Ok((kml_file, last_added_location_id)) =
|
||||||
|
location::get_kml(factory.msg.context, factory.msg.chat_id)
|
||||||
|
{
|
||||||
let content_type = mailmime_content_new_with_str(
|
let content_type = mailmime_content_new_with_str(
|
||||||
b"application/vnd.google-earth.kml+xml\x00" as *const u8
|
b"application/vnd.google-earth.kml+xml\x00" as *const u8
|
||||||
as *const libc::c_char,
|
as *const libc::c_char,
|
||||||
);
|
);
|
||||||
let mime_fields = mailmime_fields_new_filename(
|
let mime_fields = mailmime_fields_new_filename(
|
||||||
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
|
||||||
dc_strdup(b"message.kml\x00" as *const u8 as *const libc::c_char),
|
|
||||||
MAILMIME_MECHANISM_8BIT as libc::c_int,
|
|
||||||
);
|
|
||||||
let kml_mime_part = mailmime_new_empty(content_type, mime_fields);
|
|
||||||
mailmime_set_body_text(kml_mime_part, kml_file, strlen(kml_file));
|
|
||||||
|
|
||||||
mailmime_smart_add_part(message, kml_mime_part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if dc_is_sending_locations_to_chat(factory.msg.context, factory.msg.chat_id) {
|
|
||||||
let mut last_added_location_id: uint32_t = 0 as uint32_t;
|
|
||||||
let kml_file: *mut libc::c_char = dc_get_location_kml(
|
|
||||||
factory.msg.context,
|
|
||||||
factory.msg.chat_id,
|
|
||||||
&mut last_added_location_id,
|
|
||||||
);
|
|
||||||
if !kml_file.is_null() {
|
|
||||||
let content_type: *mut mailmime_content = mailmime_content_new_with_str(
|
|
||||||
b"application/vnd.google-earth.kml+xml\x00" as *const u8
|
|
||||||
as *const libc::c_char,
|
|
||||||
);
|
|
||||||
let mime_fields: *mut mailmime_fields = mailmime_fields_new_filename(
|
|
||||||
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
|
||||||
dc_strdup(b"location.kml\x00" as *const u8 as *const libc::c_char),
|
dc_strdup(b"location.kml\x00" as *const u8 as *const libc::c_char),
|
||||||
MAILMIME_MECHANISM_8BIT as libc::c_int,
|
MAILMIME_MECHANISM_8BIT as libc::c_int,
|
||||||
);
|
);
|
||||||
let kml_mime_part: *mut mailmime =
|
let kml_mime_part = mailmime_new_empty(content_type, mime_fields);
|
||||||
mailmime_new_empty(content_type, mime_fields);
|
mailmime_set_body_text(
|
||||||
mailmime_set_body_text(kml_mime_part, kml_file, strlen(kml_file));
|
kml_mime_part,
|
||||||
|
kml_file.strdup(),
|
||||||
|
kml_file.len(),
|
||||||
|
);
|
||||||
mailmime_smart_add_part(message, kml_mime_part);
|
mailmime_smart_add_part(message, kml_mime_part);
|
||||||
if !factory.msg.param.exists(Param::SetLatitude) {
|
if !factory.msg.param.exists(Param::SetLatitude) {
|
||||||
// otherwise, the independent location is already filed
|
// otherwise, the independent location is already filed
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ use mmime::other::*;
|
|||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_e2ee::*;
|
use crate::dc_e2ee::*;
|
||||||
use crate::dc_location::*;
|
|
||||||
use crate::dc_simplify::*;
|
use crate::dc_simplify::*;
|
||||||
use crate::dc_strencode::*;
|
use crate::dc_strencode::*;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
|
use crate::location;
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
use crate::stock::StockMessage;
|
use crate::stock::StockMessage;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
@@ -57,8 +57,8 @@ pub struct dc_mimeparser_t<'a> {
|
|||||||
pub context: &'a Context,
|
pub context: &'a Context,
|
||||||
pub reports: Vec<*mut mailmime>,
|
pub reports: Vec<*mut mailmime>,
|
||||||
pub is_system_message: libc::c_int,
|
pub is_system_message: libc::c_int,
|
||||||
pub location_kml: Option<dc_kml_t>,
|
pub location_kml: Option<location::Kml>,
|
||||||
pub message_kml: Option<dc_kml_t>,
|
pub message_kml: Option<location::Kml>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecated: flag to switch generation of compound messages on and off.
|
// deprecated: flag to switch generation of compound messages on and off.
|
||||||
@@ -112,14 +112,7 @@ unsafe fn dc_mimeparser_empty(mimeparser: &mut dc_mimeparser_t) {
|
|||||||
mimeparser.decrypting_failed = 0i32;
|
mimeparser.decrypting_failed = 0i32;
|
||||||
dc_e2ee_thanks(&mut mimeparser.e2ee_helper);
|
dc_e2ee_thanks(&mut mimeparser.e2ee_helper);
|
||||||
|
|
||||||
if let Some(location_kml) = mimeparser.location_kml.as_mut() {
|
|
||||||
dc_kml_unref(location_kml);
|
|
||||||
}
|
|
||||||
mimeparser.location_kml = None;
|
mimeparser.location_kml = None;
|
||||||
|
|
||||||
if let Some(message_kml) = mimeparser.message_kml.as_mut() {
|
|
||||||
dc_kml_unref(message_kml);
|
|
||||||
}
|
|
||||||
mimeparser.message_kml = None;
|
mimeparser.message_kml = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1300,11 +1293,13 @@ unsafe fn dc_mimeparser_add_single_part_if_known(
|
|||||||
4,
|
4,
|
||||||
) == 0i32
|
) == 0i32
|
||||||
{
|
{
|
||||||
mimeparser.location_kml = Some(dc_kml_parse(
|
if !decoded_data.is_null() && decoded_data_bytes > 0 {
|
||||||
mimeparser.context,
|
let d =
|
||||||
decoded_data,
|
dc_null_terminate(decoded_data, decoded_data_bytes as i32);
|
||||||
decoded_data_bytes,
|
mimeparser.location_kml =
|
||||||
));
|
location::Kml::parse(mimeparser.context, as_str(d)).ok();
|
||||||
|
free(d.cast());
|
||||||
|
}
|
||||||
} else if strncmp(
|
} else if strncmp(
|
||||||
desired_filename,
|
desired_filename,
|
||||||
b"message\x00" as *const u8 as *const libc::c_char,
|
b"message\x00" as *const u8 as *const libc::c_char,
|
||||||
@@ -1318,11 +1313,13 @@ unsafe fn dc_mimeparser_add_single_part_if_known(
|
|||||||
4,
|
4,
|
||||||
) == 0i32
|
) == 0i32
|
||||||
{
|
{
|
||||||
mimeparser.message_kml = Some(dc_kml_parse(
|
if !decoded_data.is_null() && decoded_data_bytes > 0 {
|
||||||
mimeparser.context,
|
let d =
|
||||||
decoded_data,
|
dc_null_terminate(decoded_data, decoded_data_bytes as i32);
|
||||||
decoded_data_bytes,
|
mimeparser.message_kml =
|
||||||
));
|
location::Kml::parse(mimeparser.context, as_str(d)).ok();
|
||||||
|
free(d.cast());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dc_replace_bad_utf8_chars(desired_filename);
|
dc_replace_bad_utf8_chars(desired_filename);
|
||||||
do_add_single_file_part(
|
do_add_single_file_part(
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use crate::chat::{self, Chat};
|
|||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_location::*;
|
|
||||||
use crate::dc_mimeparser::*;
|
use crate::dc_mimeparser::*;
|
||||||
use crate::dc_move::*;
|
use crate::dc_move::*;
|
||||||
use crate::dc_securejoin::*;
|
use crate::dc_securejoin::*;
|
||||||
@@ -22,6 +21,7 @@ use crate::dc_strencode::*;
|
|||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::job::*;
|
use crate::job::*;
|
||||||
|
use crate::location;
|
||||||
use crate::message::*;
|
use crate::message::*;
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
use crate::peerstate::*;
|
use crate::peerstate::*;
|
||||||
@@ -938,36 +938,34 @@ unsafe fn save_locations(
|
|||||||
let mut send_event = false;
|
let mut send_event = false;
|
||||||
|
|
||||||
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
|
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
|
||||||
let newest_location_id: uint32_t = dc_save_locations(
|
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
|
||||||
context,
|
let newest_location_id =
|
||||||
chat_id,
|
location::save(context, chat_id, from_id, locations, 1).unwrap_or_default();
|
||||||
from_id,
|
|
||||||
&mime_parser.message_kml.as_ref().unwrap().locations,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
if 0 != newest_location_id && 0 == hidden {
|
if 0 != newest_location_id && 0 == hidden {
|
||||||
dc_set_msg_location_id(context, insert_msg_id, newest_location_id);
|
if location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() {
|
||||||
location_id_written = true;
|
location_id_written = true;
|
||||||
send_event = true;
|
send_event = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !mime_parser.location_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
|
if !mime_parser.location_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
|
||||||
if !mime_parser.location_kml.as_ref().unwrap().addr.is_null() {
|
if let Some(ref addr) = mime_parser.location_kml.as_ref().unwrap().addr {
|
||||||
if let Ok(contact) = Contact::get_by_id(context, from_id) {
|
if let Ok(contact) = Contact::get_by_id(context, from_id) {
|
||||||
if !contact.get_addr().is_empty()
|
if !contact.get_addr().is_empty()
|
||||||
&& contact.get_addr().to_lowercase()
|
&& contact.get_addr().to_lowercase() == addr.to_lowercase()
|
||||||
== as_str(mime_parser.location_kml.as_ref().unwrap().addr).to_lowercase()
|
|
||||||
{
|
{
|
||||||
let newest_location_id = dc_save_locations(
|
let locations = &mime_parser.location_kml.as_ref().unwrap().locations;
|
||||||
context,
|
let newest_location_id =
|
||||||
chat_id,
|
location::save(context, chat_id, from_id, locations, 0).unwrap_or_default();
|
||||||
from_id,
|
|
||||||
&mime_parser.location_kml.as_ref().unwrap().locations,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
if newest_location_id != 0 && hidden == 0 && !location_id_written {
|
if newest_location_id != 0 && hidden == 0 && !location_id_written {
|
||||||
dc_set_msg_location_id(context, insert_msg_id, newest_location_id);
|
if let Err(err) = location::set_msg_location_id(
|
||||||
|
context,
|
||||||
|
insert_msg_id,
|
||||||
|
newest_location_id,
|
||||||
|
) {
|
||||||
|
error!(context, 0, "Failed to set msg_location_id: {:?}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
send_event = true;
|
send_event = true;
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/job.rs
26
src/job.rs
@@ -10,11 +10,11 @@ use crate::configure::*;
|
|||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_imex::*;
|
use crate::dc_imex::*;
|
||||||
use crate::dc_location::*;
|
|
||||||
use crate::dc_loginparam::*;
|
use crate::dc_loginparam::*;
|
||||||
use crate::dc_mimefactory::*;
|
use crate::dc_mimefactory::*;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
use crate::imap::*;
|
use crate::imap::*;
|
||||||
|
use crate::location;
|
||||||
use crate::message::*;
|
use crate::message::*;
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
use crate::sql;
|
use crate::sql;
|
||||||
@@ -739,13 +739,19 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
|
|||||||
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
chat::set_gossiped_timestamp(context, mimefactory.msg.chat_id, time());
|
||||||
}
|
}
|
||||||
if 0 != mimefactory.out_last_added_location_id {
|
if 0 != mimefactory.out_last_added_location_id {
|
||||||
dc_set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time());
|
if let Err(err) =
|
||||||
|
location::set_kml_sent_timestamp(context, mimefactory.msg.chat_id, time())
|
||||||
|
{
|
||||||
|
error!(context, 0, "Failed to set kml sent_timestamp: {:?}", err);
|
||||||
|
}
|
||||||
if !mimefactory.msg.hidden {
|
if !mimefactory.msg.hidden {
|
||||||
dc_set_msg_location_id(
|
if let Err(err) = location::set_msg_location_id(
|
||||||
context,
|
context,
|
||||||
mimefactory.msg.id,
|
mimefactory.msg.id,
|
||||||
mimefactory.out_last_added_location_id,
|
mimefactory.out_last_added_location_id,
|
||||||
);
|
) {
|
||||||
|
error!(context, 0, "Failed to set msg_location_id: {:?}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if 0 != mimefactory.out_encrypted
|
if 0 != mimefactory.out_encrypted
|
||||||
@@ -875,12 +881,12 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
|||||||
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
Action::SendMdn => job.do_DC_JOB_SEND(context),
|
||||||
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context, &job) },
|
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context, &job) },
|
||||||
Action::ImexImap => unsafe { dc_job_do_DC_JOB_IMEX_IMAP(context, &job) },
|
Action::ImexImap => unsafe { dc_job_do_DC_JOB_IMEX_IMAP(context, &job) },
|
||||||
Action::MaybeSendLocations => unsafe {
|
Action::MaybeSendLocations => {
|
||||||
dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
|
location::job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context, &job)
|
||||||
},
|
}
|
||||||
Action::MaybeSendLocationsEnded => unsafe {
|
Action::MaybeSendLocationsEnded => {
|
||||||
dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
|
location::job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context, &mut job)
|
||||||
},
|
}
|
||||||
Action::Housekeeping => sql::housekeeping(context),
|
Action::Housekeeping => sql::housekeeping(context),
|
||||||
Action::SendMdnOld => {}
|
Action::SendMdnOld => {}
|
||||||
Action::SendMsgToSmtpOld => {}
|
Action::SendMsgToSmtpOld => {}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ pub mod job;
|
|||||||
mod job_thread;
|
mod job_thread;
|
||||||
pub mod key;
|
pub mod key;
|
||||||
pub mod keyring;
|
pub mod keyring;
|
||||||
|
pub mod location;
|
||||||
pub mod lot;
|
pub mod lot;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod oauth2;
|
pub mod oauth2;
|
||||||
@@ -52,7 +53,6 @@ pub mod dc_array;
|
|||||||
mod dc_dehtml;
|
mod dc_dehtml;
|
||||||
mod dc_e2ee;
|
mod dc_e2ee;
|
||||||
pub mod dc_imex;
|
pub mod dc_imex;
|
||||||
pub mod dc_location;
|
|
||||||
mod dc_loginparam;
|
mod dc_loginparam;
|
||||||
mod dc_mimefactory;
|
mod dc_mimefactory;
|
||||||
pub mod dc_mimeparser;
|
pub mod dc_mimeparser;
|
||||||
|
|||||||
696
src/location.rs
Normal file
696
src/location.rs
Normal file
@@ -0,0 +1,696 @@
|
|||||||
|
use bitflags::bitflags;
|
||||||
|
use quick_xml;
|
||||||
|
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||||
|
|
||||||
|
use crate::chat;
|
||||||
|
use crate::constants::Event;
|
||||||
|
use crate::constants::*;
|
||||||
|
use crate::context::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::job::*;
|
||||||
|
use crate::message::*;
|
||||||
|
use crate::param::*;
|
||||||
|
use crate::sql;
|
||||||
|
use crate::stock::StockMessage;
|
||||||
|
use crate::types::*;
|
||||||
|
|
||||||
|
// location handling
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Location {
|
||||||
|
pub location_id: u32,
|
||||||
|
pub latitude: f64,
|
||||||
|
pub longitude: f64,
|
||||||
|
pub accuracy: f64,
|
||||||
|
pub timestamp: i64,
|
||||||
|
pub contact_id: u32,
|
||||||
|
pub msg_id: u32,
|
||||||
|
pub chat_id: u32,
|
||||||
|
pub marker: Option<String>,
|
||||||
|
pub independent: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Kml {
|
||||||
|
pub addr: Option<String>,
|
||||||
|
pub locations: Vec<Location>,
|
||||||
|
tag: KmlTag,
|
||||||
|
pub curr: Location,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Default)]
|
||||||
|
struct KmlTag: i32 {
|
||||||
|
const UNDEFINED = 0x00;
|
||||||
|
const PLACEMARK = 0x01;
|
||||||
|
const TIMESTAMP = 0x02;
|
||||||
|
const WHEN = 0x04;
|
||||||
|
const POINT = 0x08;
|
||||||
|
const COORDINATES = 0x10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kml {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(context: &Context, content: impl AsRef<str>) -> Result<Self, Error> {
|
||||||
|
ensure!(
|
||||||
|
content.as_ref().len() <= (1 * 1024 * 1024),
|
||||||
|
"A kml-files with {} bytes is larger than reasonably expected.",
|
||||||
|
content.as_ref().len()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut reader = quick_xml::Reader::from_str(content.as_ref());
|
||||||
|
reader.trim_text(true);
|
||||||
|
|
||||||
|
let mut kml = Kml::new();
|
||||||
|
kml.locations = Vec::with_capacity(100);
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match reader.read_event(&mut buf) {
|
||||||
|
Ok(quick_xml::events::Event::Start(ref e)) => kml.starttag_cb(e, &reader),
|
||||||
|
Ok(quick_xml::events::Event::End(ref e)) => kml.endtag_cb(e),
|
||||||
|
Ok(quick_xml::events::Event::Text(ref e)) => kml.text_cb(e, &reader),
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
"Location parsing: Error at position {}: {:?}",
|
||||||
|
reader.buffer_position(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(quick_xml::events::Event::Eof) => break,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
buf.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(kml)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_cb<B: std::io::BufRead>(&mut self, event: &BytesText, reader: &quick_xml::Reader<B>) {
|
||||||
|
if self.tag.contains(KmlTag::WHEN) || self.tag.contains(KmlTag::COORDINATES) {
|
||||||
|
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||||
|
|
||||||
|
let val = val
|
||||||
|
.replace("\n", "")
|
||||||
|
.replace("\r", "")
|
||||||
|
.replace("\t", "")
|
||||||
|
.replace(" ", "");
|
||||||
|
|
||||||
|
if self.tag.contains(KmlTag::WHEN) && val.len() >= 19 {
|
||||||
|
// YYYY-MM-DDTHH:MM:SSZ
|
||||||
|
// 0 4 7 10 13 16 19
|
||||||
|
match chrono::NaiveDateTime::parse_from_str(&val, "%Y-%m-%dT%H:%M:%SZ") {
|
||||||
|
Ok(res) => {
|
||||||
|
self.curr.timestamp = res.timestamp();
|
||||||
|
if self.curr.timestamp > time() {
|
||||||
|
self.curr.timestamp = time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_err) => {
|
||||||
|
self.curr.timestamp = time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if self.tag.contains(KmlTag::COORDINATES) {
|
||||||
|
let parts = val.splitn(2, ',').collect::<Vec<_>>();
|
||||||
|
if parts.len() == 2 {
|
||||||
|
self.curr.longitude = parts[0].parse().unwrap_or_default();
|
||||||
|
self.curr.latitude = parts[1].parse().unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endtag_cb(&mut self, event: &BytesEnd) {
|
||||||
|
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||||
|
|
||||||
|
if tag == "placemark" {
|
||||||
|
if self.tag.contains(KmlTag::PLACEMARK)
|
||||||
|
&& 0 != self.curr.timestamp
|
||||||
|
&& 0. != self.curr.latitude
|
||||||
|
&& 0. != self.curr.longitude
|
||||||
|
{
|
||||||
|
self.locations
|
||||||
|
.push(std::mem::replace(&mut self.curr, Location::new()));
|
||||||
|
}
|
||||||
|
self.tag = KmlTag::UNDEFINED;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starttag_cb<B: std::io::BufRead>(
|
||||||
|
&mut self,
|
||||||
|
event: &BytesStart,
|
||||||
|
reader: &quick_xml::Reader<B>,
|
||||||
|
) {
|
||||||
|
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||||
|
if tag == "document" {
|
||||||
|
if let Some(addr) = event.attributes().find(|attr| {
|
||||||
|
attr.as_ref()
|
||||||
|
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "addr")
|
||||||
|
.unwrap_or_default()
|
||||||
|
}) {
|
||||||
|
self.addr = addr.unwrap().unescape_and_decode_value(reader).ok();
|
||||||
|
}
|
||||||
|
} else if tag == "placemark" {
|
||||||
|
self.tag = KmlTag::PLACEMARK;
|
||||||
|
self.curr.timestamp = 0;
|
||||||
|
self.curr.latitude = 0.0;
|
||||||
|
self.curr.longitude = 0.0;
|
||||||
|
self.curr.accuracy = 0.0
|
||||||
|
} else if tag == "timestamp" && self.tag.contains(KmlTag::PLACEMARK) {
|
||||||
|
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP
|
||||||
|
} else if tag == "when" && self.tag.contains(KmlTag::TIMESTAMP) {
|
||||||
|
self.tag = KmlTag::PLACEMARK | KmlTag::TIMESTAMP | KmlTag::WHEN
|
||||||
|
} else if tag == "point" && self.tag.contains(KmlTag::PLACEMARK) {
|
||||||
|
self.tag = KmlTag::PLACEMARK | KmlTag::POINT
|
||||||
|
} else if tag == "coordinates" && self.tag.contains(KmlTag::POINT) {
|
||||||
|
self.tag = KmlTag::PLACEMARK | KmlTag::POINT | KmlTag::COORDINATES;
|
||||||
|
if let Some(acc) = event.attributes().find(|attr| {
|
||||||
|
attr.as_ref()
|
||||||
|
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "accuracy")
|
||||||
|
.unwrap_or_default()
|
||||||
|
}) {
|
||||||
|
let v = acc
|
||||||
|
.unwrap()
|
||||||
|
.unescape_and_decode_value(reader)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
self.curr.accuracy = v.trim().parse().unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// location streaming
|
||||||
|
pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||||
|
let now = time();
|
||||||
|
let mut msg: Message;
|
||||||
|
let is_sending_locations_before: bool;
|
||||||
|
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
|
||||||
|
is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
|
||||||
|
if sql::execute(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
"UPDATE chats \
|
||||||
|
SET locations_send_begin=?, \
|
||||||
|
locations_send_until=? \
|
||||||
|
WHERE id=?",
|
||||||
|
params![
|
||||||
|
if 0 != seconds { now } else { 0 },
|
||||||
|
if 0 != seconds { now + seconds } else { 0 },
|
||||||
|
chat_id as i32,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
if 0 != seconds && !is_sending_locations_before {
|
||||||
|
msg = dc_msg_new(context, Viewtype::Text);
|
||||||
|
msg.text =
|
||||||
|
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
||||||
|
msg.param.set_int(Param::Cmd, 8);
|
||||||
|
unsafe { chat::send_msg(context, chat_id, &mut msg).unwrap() };
|
||||||
|
} else if 0 == seconds && is_sending_locations_before {
|
||||||
|
let stock_str =
|
||||||
|
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||||
|
chat::add_device_msg(context, chat_id, stock_str);
|
||||||
|
}
|
||||||
|
context.call_cb(
|
||||||
|
Event::CHAT_MODIFIED,
|
||||||
|
chat_id as uintptr_t,
|
||||||
|
0i32 as uintptr_t,
|
||||||
|
);
|
||||||
|
if 0 != seconds {
|
||||||
|
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
||||||
|
job_add(
|
||||||
|
context,
|
||||||
|
Action::MaybeSendLocationsEnded,
|
||||||
|
chat_id as libc::c_int,
|
||||||
|
Params::new(),
|
||||||
|
seconds + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: i32) {
|
||||||
|
if 0 != flags & 0x1 || !job_action_exists(context, Action::MaybeSendLocations) {
|
||||||
|
job_add(context, Action::MaybeSendLocations, 0, Params::new(), 60);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.exists(
|
||||||
|
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
|
||||||
|
params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()],
|
||||||
|
)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
|
||||||
|
if latitude == 0.0 && longitude == 0.0 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.sql.query_map(
|
||||||
|
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||||
|
params![time()], |row| row.get::<_, i32>(0),
|
||||||
|
|chats| {
|
||||||
|
let mut continue_streaming = false;
|
||||||
|
|
||||||
|
for chat in chats {
|
||||||
|
let chat_id = chat?;
|
||||||
|
context.sql.execute(
|
||||||
|
"INSERT INTO locations \
|
||||||
|
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
|
||||||
|
params![
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
accuracy,
|
||||||
|
time(),
|
||||||
|
chat_id,
|
||||||
|
1,
|
||||||
|
]
|
||||||
|
)?;
|
||||||
|
continue_streaming = true;
|
||||||
|
}
|
||||||
|
if continue_streaming {
|
||||||
|
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
|
||||||
|
};
|
||||||
|
schedule_MAYBE_SEND_LOCATIONS(context, 0);
|
||||||
|
Ok(continue_streaming as libc::c_int)
|
||||||
|
}
|
||||||
|
).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_range(
|
||||||
|
context: &Context,
|
||||||
|
chat_id: u32,
|
||||||
|
contact_id: u32,
|
||||||
|
timestamp_from: i64,
|
||||||
|
mut timestamp_to: i64,
|
||||||
|
) -> Vec<Location> {
|
||||||
|
if timestamp_to == 0 {
|
||||||
|
timestamp_to = time() + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.query_map(
|
||||||
|
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
|
||||||
|
m.id, l.from_id, l.chat_id, m.txt \
|
||||||
|
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
|
||||||
|
AND (? OR l.from_id=?) \
|
||||||
|
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
||||||
|
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;",
|
||||||
|
params![
|
||||||
|
if chat_id == 0 { 1 } else { 0 },
|
||||||
|
chat_id as i32,
|
||||||
|
if contact_id == 0 { 1 } else { 0 },
|
||||||
|
contact_id as i32,
|
||||||
|
timestamp_from,
|
||||||
|
timestamp_to,
|
||||||
|
],
|
||||||
|
|row| {
|
||||||
|
let msg_id = row.get(6)?;
|
||||||
|
let txt: String = row.get(9)?;
|
||||||
|
let marker = if msg_id != 0 && is_marker(&txt) {
|
||||||
|
Some(txt)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let loc = Location {
|
||||||
|
location_id: row.get(0)?,
|
||||||
|
latitude: row.get(1)?,
|
||||||
|
longitude: row.get(2)?,
|
||||||
|
accuracy: row.get(3)?,
|
||||||
|
timestamp: row.get(4)?,
|
||||||
|
independent: row.get(5)?,
|
||||||
|
msg_id,
|
||||||
|
contact_id: row.get(7)?,
|
||||||
|
chat_id: row.get(8)?,
|
||||||
|
marker,
|
||||||
|
};
|
||||||
|
Ok(loc)
|
||||||
|
},
|
||||||
|
|locations| {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
|
||||||
|
for location in locations {
|
||||||
|
ret.push(location?);
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_marker(txt: &str) -> bool {
|
||||||
|
txt.len() == 1 && txt.chars().next().unwrap() != ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
||||||
|
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?;
|
||||||
|
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> {
|
||||||
|
let now = time();
|
||||||
|
let mut location_count = 0;
|
||||||
|
let mut ret = String::new();
|
||||||
|
let mut last_added_location_id = 0;
|
||||||
|
|
||||||
|
let self_addr = context
|
||||||
|
.sql
|
||||||
|
.get_config(context, "configured_addr")
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||||
|
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
|
||||||
|
params![chat_id as i32], |row| {
|
||||||
|
let send_begin: i64 = row.get(0)?;
|
||||||
|
let send_until: i64 = row.get(1)?;
|
||||||
|
let last_sent: i64 = row.get(2)?;
|
||||||
|
|
||||||
|
Ok((send_begin, send_until, last_sent))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !(locations_send_begin == 0 || now > locations_send_until) {
|
||||||
|
ret += &format!(
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
|
||||||
|
self_addr,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.sql.query_map(
|
||||||
|
"SELECT id, latitude, longitude, accuracy, timestamp\
|
||||||
|
FROM locations WHERE from_id=? \
|
||||||
|
AND timestamp>=? \
|
||||||
|
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
|
||||||
|
AND independent=0 \
|
||||||
|
GROUP BY timestamp \
|
||||||
|
ORDER BY timestamp;",
|
||||||
|
params![1, locations_send_begin, locations_last_sent, 1],
|
||||||
|
|row| {
|
||||||
|
let location_id: i32 = row.get(0)?;
|
||||||
|
let latitude: f64 = row.get(1)?;
|
||||||
|
let longitude: f64 = row.get(2)?;
|
||||||
|
let accuracy: f64 = row.get(3)?;
|
||||||
|
let timestamp = get_kml_timestamp(row.get(4)?);
|
||||||
|
|
||||||
|
Ok((location_id, latitude, longitude, accuracy, timestamp))
|
||||||
|
},
|
||||||
|
|rows| {
|
||||||
|
for row in rows {
|
||||||
|
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
|
||||||
|
ret += &format!(
|
||||||
|
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
|
||||||
|
timestamp,
|
||||||
|
accuracy,
|
||||||
|
longitude,
|
||||||
|
latitude
|
||||||
|
);
|
||||||
|
location_count += 1;
|
||||||
|
last_added_location_id = location_id as u32;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure!(location_count > 0, "No locations processed");
|
||||||
|
ret += "</Document>\n</kml>";
|
||||||
|
|
||||||
|
Ok((ret, last_added_location_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_kml_timestamp(utc: i64) -> String {
|
||||||
|
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
|
||||||
|
chrono::NaiveDateTime::from_timestamp(utc, 0)
|
||||||
|
.format("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String {
|
||||||
|
format!(
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||||
|
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
|
||||||
|
<Document>\n\
|
||||||
|
<Placemark>\
|
||||||
|
<Timestamp><when>{}</when></Timestamp>\
|
||||||
|
<Point><coordinates>{:.2},{:.2}</coordinates></Point>\
|
||||||
|
</Placemark>\n\
|
||||||
|
</Document>\n\
|
||||||
|
</kml>",
|
||||||
|
get_kml_timestamp(timestamp),
|
||||||
|
longitude,
|
||||||
|
latitude,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_kml_sent_timestamp(
|
||||||
|
context: &Context,
|
||||||
|
chat_id: u32,
|
||||||
|
timestamp: i64,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
sql::execute(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
||||||
|
params![timestamp, chat_id as i32],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_msg_location_id(context: &Context, msg_id: u32, location_id: u32) -> Result<(), Error> {
|
||||||
|
sql::execute(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
"UPDATE msgs SET location_id=? WHERE id=?;",
|
||||||
|
params![location_id, msg_id as i32],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(
|
||||||
|
context: &Context,
|
||||||
|
chat_id: u32,
|
||||||
|
contact_id: u32,
|
||||||
|
locations: &[Location],
|
||||||
|
independent: i32,
|
||||||
|
) -> Result<u32, Error> {
|
||||||
|
ensure!(chat_id > 9, "Invalid chat id");
|
||||||
|
context.sql.prepare2(
|
||||||
|
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
|
||||||
|
"INSERT INTO locations\
|
||||||
|
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
||||||
|
VALUES (?,?,?,?,?,?,?);",
|
||||||
|
|mut stmt_test, mut stmt_insert, conn| {
|
||||||
|
let mut newest_timestamp = 0;
|
||||||
|
let mut newest_location_id = 0;
|
||||||
|
|
||||||
|
for location in locations {
|
||||||
|
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||||
|
|
||||||
|
if 0 != independent || !exists {
|
||||||
|
stmt_insert.execute(params![
|
||||||
|
location.timestamp,
|
||||||
|
contact_id as i32,
|
||||||
|
chat_id as i32,
|
||||||
|
location.latitude,
|
||||||
|
location.longitude,
|
||||||
|
location.accuracy,
|
||||||
|
independent,
|
||||||
|
])?;
|
||||||
|
|
||||||
|
if location.timestamp > newest_timestamp {
|
||||||
|
newest_timestamp = location.timestamp;
|
||||||
|
newest_location_id = sql::get_rowid2_with_conn(
|
||||||
|
context,
|
||||||
|
conn,
|
||||||
|
"locations",
|
||||||
|
"timestamp",
|
||||||
|
location.timestamp,
|
||||||
|
"from_id",
|
||||||
|
contact_id as i32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(newest_location_id)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: &Job) {
|
||||||
|
let now = time();
|
||||||
|
let mut continue_streaming: libc::c_int = 1;
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
0, " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
|
||||||
|
);
|
||||||
|
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.query_map(
|
||||||
|
"SELECT id, locations_send_begin, locations_last_sent \
|
||||||
|
FROM chats \
|
||||||
|
WHERE locations_send_until>?;",
|
||||||
|
params![now],
|
||||||
|
|row| {
|
||||||
|
let chat_id: i32 = row.get(0)?;
|
||||||
|
let locations_send_begin: i64 = row.get(1)?;
|
||||||
|
let locations_last_sent: i64 = row.get(2)?;
|
||||||
|
continue_streaming = 1;
|
||||||
|
|
||||||
|
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||||
|
if now - locations_last_sent < (60 - 3) {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|rows| {
|
||||||
|
context.sql.prepare(
|
||||||
|
"SELECT id \
|
||||||
|
FROM locations \
|
||||||
|
WHERE from_id=? \
|
||||||
|
AND timestamp>=? \
|
||||||
|
AND timestamp>? \
|
||||||
|
AND independent=0 \
|
||||||
|
ORDER BY timestamp;",
|
||||||
|
|mut stmt_locations, _| {
|
||||||
|
for (chat_id, locations_send_begin, locations_last_sent) in
|
||||||
|
rows.filter_map(|r| match r {
|
||||||
|
Ok(Some(v)) => Some(v),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
// TODO: do I need to reset?
|
||||||
|
if !stmt_locations
|
||||||
|
.exists(params![1, locations_send_begin, locations_last_sent,])
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
// if there is no new location, there's nothing to send.
|
||||||
|
// however, maybe we want to bypass this test eg. 15 minutes
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// pending locations are attached automatically to every message,
|
||||||
|
// so also to this empty text message.
|
||||||
|
// DC_CMD_LOCATION is only needed to create a nicer subject.
|
||||||
|
//
|
||||||
|
// for optimisation and to avoid flooding the sending queue,
|
||||||
|
// we could sending these messages only if we're really online.
|
||||||
|
// the easiest way to determine this, is to check for an empty message queue.
|
||||||
|
// (might not be 100%, however, as positions are sent combined later
|
||||||
|
// and dc_set_location() is typically called periodically, this is ok)
|
||||||
|
let mut msg = dc_msg_new(context, Viewtype::Text);
|
||||||
|
msg.hidden = true;
|
||||||
|
msg.param.set_int(Param::Cmd, 9);
|
||||||
|
// TODO: handle cleanup on error
|
||||||
|
unsafe { chat::send_msg(context, chat_id as u32, &mut msg).unwrap() };
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap(); // TODO: Better error handling
|
||||||
|
|
||||||
|
if 0 != continue_streaming {
|
||||||
|
schedule_MAYBE_SEND_LOCATIONS(context, 0x1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
|
||||||
|
// this function is called when location-streaming _might_ have ended for a chat.
|
||||||
|
// the function checks, if location-streaming is really ended;
|
||||||
|
// if so, a device-message is added if not yet done.
|
||||||
|
|
||||||
|
let chat_id = job.foreign_id;
|
||||||
|
|
||||||
|
if let Ok((send_begin, send_until)) = context.sql.query_row(
|
||||||
|
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||||
|
params![chat_id as i32],
|
||||||
|
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||||
|
) {
|
||||||
|
if !(send_begin != 0 && time() <= send_until) {
|
||||||
|
// still streaming -
|
||||||
|
// may happen as several calls to dc_send_locations_to_chat()
|
||||||
|
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||||
|
if !(send_begin == 0 && send_until == 0) {
|
||||||
|
// not streaming, device-message already sent
|
||||||
|
if context.sql.execute(
|
||||||
|
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
||||||
|
params![chat_id as i32],
|
||||||
|
).is_ok() {
|
||||||
|
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||||
|
chat::add_device_msg(context, chat_id, stock_str);
|
||||||
|
context.call_cb(
|
||||||
|
Event::CHAT_MODIFIED,
|
||||||
|
chat_id as usize,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::test_utils::dummy_context;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_kml_parse() {
|
||||||
|
let context = dummy_context();
|
||||||
|
|
||||||
|
let xml =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
|
||||||
|
|
||||||
|
let kml = Kml::parse(&context.ctx, &xml).expect("parsing failed");
|
||||||
|
|
||||||
|
assert!(kml.addr.is_some());
|
||||||
|
assert_eq!(kml.addr.as_ref().unwrap(), "user@example.org",);
|
||||||
|
|
||||||
|
let locations_ref = &kml.locations;
|
||||||
|
assert_eq!(locations_ref.len(), 2);
|
||||||
|
|
||||||
|
assert!(locations_ref[0].latitude > 53.6f64);
|
||||||
|
assert!(locations_ref[0].latitude < 53.8f64);
|
||||||
|
assert!(locations_ref[0].longitude > 9.3f64);
|
||||||
|
assert!(locations_ref[0].longitude < 9.5f64);
|
||||||
|
assert!(locations_ref[0].accuracy > 31.9f64);
|
||||||
|
assert!(locations_ref[0].accuracy < 32.1f64);
|
||||||
|
assert_eq!(locations_ref[0].timestamp, 1551906597);
|
||||||
|
|
||||||
|
assert!(locations_ref[1].latitude > 63.6f64);
|
||||||
|
assert!(locations_ref[1].latitude < 63.8f64);
|
||||||
|
assert!(locations_ref[1].longitude > 19.3f64);
|
||||||
|
assert!(locations_ref[1].longitude < 19.5f64);
|
||||||
|
assert!(locations_ref[1].accuracy > 2.4f64);
|
||||||
|
assert!(locations_ref[1].accuracy < 2.6f64);
|
||||||
|
assert_eq!(locations_ref[1].timestamp, 1544739072);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user