Re-work some in-creation file handling

This effectively reverts
https://github.com/deltachat/deltachat-core-rust/pull/964 for chat.rs,
which in that PR was thought to fix something.  So maybe something is
still broken?  But after improving tests the previous code seems to be
correct.

- Update Python bindings to not always use dc_prepare_msg path when
  sending messages with attachements.  When using dc_prepare_msg the
  blobs need to be created in the blobdir since they will not get
  copied and many tests where not doing this.

- Add a test that ensures that calling dc_prepare_msg with a
  file **not** in the blobdir fails.

- Add a test that ensures that calling dc_send_msg directly with a
  file **not** in the blobdir copies the file to the blobdir.  This
  test cheats a little by knowing what the filename in the blobdir
  will be which is implementation-dependent and thus a bit brittle.
  But for now it proves correct behaviour so let's go with this.

- Improve the test_forward_increation test to ensure that the
  in-creation file only has it's final state before calling
  dc_send_msg.  This checks the correct file data is sent out and not
  the preparing data, this fails with the chat.rs changes in
  #964 (reverted here to make this work again).  Also fix the test to
  actually create the in-creation file in the blobdir.

- Fix test_send_file_twice_unicode_filename_mangling to not use
  in-creation.  It was not creating it's files in the blobdir and that
  is an error when using in-creation and it didn't seem it was trying
  to test something about the in-creation logic (which is tested in
  test_increation.py already).

- Fix Message._msgtate code which presumably was not used before?

- Rename `BlobObject::create_from_path` to
  `BlobObject::new_from_path`.  All the `BlobObject::create*` calls
  now always create new files which is much more consistent.  APIs
  should do what is obious.
This commit is contained in:
Floris Bruynooghe
2019-12-08 20:20:17 +01:00
committed by holger krekel
parent a99b96e36e
commit 2c4dbe6e68
9 changed files with 98 additions and 37 deletions

View File

@@ -109,6 +109,30 @@ class Chat(object):
# ------ chat messaging API ------------------------------ # ------ chat messaging API ------------------------------
def send_msg(self, msg):
"""send a message by using a ready Message object.
:param msg: a :class:`deltachat.message.Message` instance
previously returned by
e.g. :meth:`deltachat.message.Message.new_empty` or
:meth:`prepare_file`.
:raises ValueError: if message can not be sent.
:returns: a :class:`deltachat.message.Message` instance as
sent out. This is the same object as was passed in, which
has been modified with the new state of the core.
"""
if msg.is_out_preparing():
assert msg.id != 0
# get a fresh copy of dc_msg, the core needs it
msg = Message.from_db(self.account, msg.id)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
# modify message in place to avoid bad state for the caller
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
return msg
def send_text(self, text): def send_text(self, text):
""" send a text message and return the resulting Message instance. """ send a text message and return the resulting Message instance.
@@ -130,9 +154,12 @@ class Chat(object):
:raises ValueError: if message can not be send/chat does not exist. :raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance :returns: the resulting :class:`deltachat.message.Message` instance
""" """
msg = self.prepare_message_file(path=path, mime_type=mime_type) msg = Message.new_empty(self.account, view_type="file")
self.send_prepared(msg) msg.set_file(path, mime_type)
return msg sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
return Message.from_db(self.account, sent_id)
def send_image(self, path): def send_image(self, path):
""" send an image message and return the resulting Message instance. """ send an image message and return the resulting Message instance.
@@ -142,9 +169,12 @@ class Chat(object):
:returns: the resulting :class:`deltachat.message.Message` instance :returns: the resulting :class:`deltachat.message.Message` instance
""" """
mime_type = mimetypes.guess_type(path)[0] mime_type = mimetypes.guess_type(path)[0]
msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image") msg = Message.new_empty(self.account, view_type="image")
self.send_prepared(msg) msg.set_file(path, mime_type)
return msg sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
if sent_id == 0:
raise ValueError("message could not be sent")
return Message.from_db(self.account, sent_id)
def prepare_message(self, msg): def prepare_message(self, msg):
""" create a new prepared message. """ create a new prepared message.

View File

@@ -174,7 +174,7 @@ class Message(object):
@property @property
def _msgstate(self): def _msgstate(self):
if self.id == 0: if self.id == 0:
dc_msg = self.message._dc_msg dc_msg = self._dc_msg
else: else:
# load message from db to get a fresh/current state # load message from db to get a fresh/current state
dc_msg = ffi.gc( dc_msg = ffi.gc(

View File

@@ -456,10 +456,7 @@ class TestOnlineAccount:
msg1 = Message.new_empty(ac1, "file") msg1 = Message.new_empty(ac1, "file")
msg1.set_text("withfile") msg1.set_text("withfile")
msg1.set_file(p) msg1.set_file(p)
message = chat.prepare_message(msg1) chat.send_msg(msg1)
assert message.is_out_preparing()
assert message.text == "withfile"
chat.send_prepared(message)
lp.sec("ac2: receive message") lp.sec("ac2: receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED") ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")

View File

@@ -1,10 +1,49 @@
from __future__ import print_function from __future__ import print_function
import os.path
import shutil
import pytest
from filecmp import cmp from filecmp import cmp
from deltachat import const
from conftest import wait_configuration_progress, wait_msgs_changed from conftest import wait_configuration_progress, wait_msgs_changed
from deltachat import const
class TestOnlineInCreation: class TestOnlineInCreation:
def test_increation_not_blobdir(self, tmpdir, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
lp.sec("Creating in-creation file outside of blobdir")
assert tmpdir.strpath != ac1.get_blobdir()
src = tmpdir.join('file.txt').ensure(file=1)
with pytest.raises(Exception):
chat.prepare_message_file(src.strpath)
def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
lp.sec("Creating file outside of blobdir")
assert tmpdir.strpath != ac1.get_blobdir()
src = tmpdir.join('file.txt')
src.write("hello there\n")
chat.send_file(src.strpath)
blob_src = os.path.join(ac1.get_blobdir(), 'file.txt')
assert os.path.exists(blob_src), "file.txt not copied to blobdir"
def test_forward_increation(self, acfactory, data, lp): def test_forward_increation(self, acfactory, data, lp):
ac1 = acfactory.get_online_configuring_account() ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account()
@@ -17,7 +56,10 @@ class TestOnlineInCreation:
wait_msgs_changed(ac1, 0, 0) # why no chat id? wait_msgs_changed(ac1, 0, 0) # why no chat id?
lp.sec("create a message with a file in creation") lp.sec("create a message with a file in creation")
path = data.get_path("d.png") orig = data.get_path("d.png")
path = os.path.join(ac1.get_blobdir(), 'd.png')
with open(path, "x") as fp:
fp.write("preparing")
prepared_original = chat.prepare_message_file(path) prepared_original = chat.prepare_message_file(path)
assert prepared_original.is_out_preparing() assert prepared_original.is_out_preparing()
wait_msgs_changed(ac1, chat.id, prepared_original.id) wait_msgs_changed(ac1, chat.id, prepared_original.id)
@@ -38,6 +80,7 @@ class TestOnlineInCreation:
lp.sec("finish creating the file and send it") lp.sec("finish creating the file and send it")
assert prepared_original.is_out_preparing() assert prepared_original.is_out_preparing()
shutil.copyfile(orig, path)
chat.send_prepared(prepared_original) chat.send_prepared(prepared_original)
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered() assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
wait_msgs_changed(ac1, chat.id, prepared_original.id) wait_msgs_changed(ac1, chat.id, prepared_original.id)
@@ -59,11 +102,11 @@ class TestOnlineInCreation:
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev1[1] > const.DC_CHAT_ID_LAST_SPECIAL assert ev1[1] > const.DC_CHAT_ID_LAST_SPECIAL
received_original = ac2.get_message_by_id(ev1[2]) received_original = ac2.get_message_by_id(ev1[2])
assert cmp(received_original.filename, path, False) assert cmp(received_original.filename, orig, shallow=False)
lp.sec("wait2 for original or forwarded messages to arrive") lp.sec("wait2 for original or forwarded messages to arrive")
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev2[1] > const.DC_CHAT_ID_LAST_SPECIAL assert ev2[1] > const.DC_CHAT_ID_LAST_SPECIAL
assert ev2[1] != ev1[1] assert ev2[1] != ev1[1]
received_copy = ac2.get_message_by_id(ev2[2]) received_copy = ac2.get_message_by_id(ev2[2])
assert cmp(received_copy.filename, path, False) assert cmp(received_copy.filename, orig, shallow=False)

View File

@@ -65,8 +65,8 @@ commands =
[pytest] [pytest]
addopts = -v -rs addopts = -v -ra
python_files = tests/test_*.py python_files = tests/test_*.py
norecursedirs = .tox norecursedirs = .tox
xfail_strict=true xfail_strict=true
timeout = 60 timeout = 60

View File

@@ -159,7 +159,7 @@ impl<'a> BlobObject<'a> {
/// This merely delegates to the [BlobObject::create_and_copy] and /// This merely delegates to the [BlobObject::create_and_copy] and
/// the [BlobObject::from_path] methods. See those for possible /// the [BlobObject::from_path] methods. See those for possible
/// errors. /// errors.
pub fn create_from_path( pub fn new_from_path(
context: &Context, context: &Context,
src: impl AsRef<Path>, src: impl AsRef<Path>,
) -> std::result::Result<BlobObject, BlobError> { ) -> std::result::Result<BlobObject, BlobError> {
@@ -559,14 +559,14 @@ mod tests {
let src_ext = t.dir.path().join("external"); let src_ext = t.dir.path().join("external");
fs::write(&src_ext, b"boo").unwrap(); fs::write(&src_ext, b"boo").unwrap();
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap(); let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/external"); assert_eq!(blob.as_name(), "$BLOBDIR/external");
let data = fs::read(blob.to_abs_path()).unwrap(); let data = fs::read(blob.to_abs_path()).unwrap();
assert_eq!(data, b"boo"); assert_eq!(data, b"boo");
let src_int = t.ctx.get_blobdir().join("internal"); let src_int = t.ctx.get_blobdir().join("internal");
fs::write(&src_int, b"boo").unwrap(); fs::write(&src_int, b"boo").unwrap();
let blob = BlobObject::create_from_path(&t.ctx, &src_int).unwrap(); let blob = BlobObject::new_from_path(&t.ctx, &src_int).unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/internal"); assert_eq!(blob.as_name(), "$BLOBDIR/internal");
let data = fs::read(blob.to_abs_path()).unwrap(); let data = fs::read(blob.to_abs_path()).unwrap();
assert_eq!(data, b"boo"); assert_eq!(data, b"boo");
@@ -576,7 +576,7 @@ mod tests {
let t = dummy_context(); let t = dummy_context();
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html"); let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
fs::write(&src_ext, b"boo").unwrap(); fs::write(&src_ext, b"boo").unwrap();
let blob = BlobObject::create_from_path(&t.ctx, &src_ext).unwrap(); let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
assert_eq!( assert_eq!(
blob.as_name(), blob.as_name(),
"$BLOBDIR/autocrypt-setup-message-4137848473.html" "$BLOBDIR/autocrypt-setup-message-4137848473.html"

View File

@@ -725,20 +725,11 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
if msg.type_0 == Viewtype::Text { if msg.type_0 == Viewtype::Text {
// the caller should check if the message text is empty // the caller should check if the message text is empty
} else if msgtype_has_file(msg.type_0) { } else if msgtype_has_file(msg.type_0) {
let blob = if let Some(f) = msg.param.get_file(Param::File, context)? { let blob = msg
match f { .param
ParamsFile::Blob(blob) => blob, .get_blob(Param::File, context, !msg.is_increation())?
ParamsFile::FsPath(path) => { .ok_or_else(|| format_err!("Attachment missing for message of type #{}", msg.type_0))?;
// path is outside the blobdir, let's copy msg.param.set(Param::File, blob.as_name());
let blob = BlobObject::create_and_copy(context, path)?;
msg.param.set(Param::File, blob.as_name());
blob
}
}
} else {
bail!("Attachment missing for message of type #{}", msg.type_0);
};
if msg.type_0 == Viewtype::File || msg.type_0 == Viewtype::Image { if msg.type_0 == Viewtype::File || msg.type_0 == Viewtype::Image {
// Correct the type, take care not to correct already very special // Correct the type, take care not to correct already very special

View File

@@ -128,7 +128,7 @@ impl Context {
pub fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> { pub fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> {
match key { match key {
Config::Selfavatar if value.is_some() => { Config::Selfavatar if value.is_some() => {
let blob = BlobObject::create_from_path(&self, value.unwrap())?; let blob = BlobObject::new_from_path(&self, value.unwrap())?;
self.sql.set_raw_config(self, key, Some(blob.as_name())) self.sql.set_raw_config(self, key, Some(blob.as_name()))
} }
Config::InboxWatch => { Config::InboxWatch => {

View File

@@ -250,7 +250,7 @@ impl Params {
let file = ParamsFile::from_param(context, val)?; let file = ParamsFile::from_param(context, val)?;
let blob = match file { let blob = match file {
ParamsFile::FsPath(path) => match create { ParamsFile::FsPath(path) => match create {
true => BlobObject::create_from_path(context, path)?, true => BlobObject::new_from_path(context, path)?,
false => BlobObject::from_path(context, path)?, false => BlobObject::from_path(context, path)?,
}, },
ParamsFile::Blob(blob) => blob, ParamsFile::Blob(blob) => blob,