Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement image sending from sender #113

Closed

Conversation

bitfriend
Copy link
Contributor

I tested image sending.
I confirmed message history appears in chat.
But image is not displayed.
Because the feature that fetches the received image is not implemented on flutter & rust.
Now implementing it.

@bitfriend bitfriend closed this May 23, 2022
@bitfriend bitfriend force-pushed the issue77_file-transfter-in-chat branch from 25303cf to 269bfd7 Compare May 23, 2022 12:33
@bitfriend
Copy link
Contributor Author

Reformatted by rust code

@bitfriend bitfriend reopened this May 23, 2022
@bitfriend bitfriend closed this May 23, 2022
@bitfriend bitfriend force-pushed the issue77_file-transfter-in-chat branch from 5c0c00b to 269bfd7 Compare May 23, 2022 12:51
@bitfriend
Copy link
Contributor Author

Reformatted by rust code

@bitfriend bitfriend reopened this May 23, 2022
Copy link
Contributor

@gnunicorn gnunicorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't want to send using the newly, yet to be approved MSC but instead use the existing "attachment" types.

@@ -51,6 +52,9 @@ features = [
"anyhow",
]

[dependencies.ruma-common]
features = ["unstable-msc3552"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate you finding out there is an open msc... but there's also an already implemented version we should be doing instead...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. Will check it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pushed #0eb3936393570afddc6ba244aaa9ab4ba7809277 (Replace msc3552 with standard on image send)

Some(val) => UInt::new(val),
None => None,
};
let file = FileContent::plain(url, Some(Box::new(info)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than sending over a URI, we should be handing over the actual source-content to the SDK and use room.send_attachment to upload it to the server and send an appropriate image message...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. Will check it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pushed #0eb3936393570afddc6ba244aaa9ab4ba7809277 (Replace msc3552 with standard on image send)

Copy link
Contributor

@gnunicorn gnunicorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code looks fine, and seems to work, but the UI shows the now-outdated error message still:
Screenshot_1653421761

@@ -205,9 +203,11 @@ impl Room {
&self,
message: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

message isn't used anymore.

Suggested change
message: String,

@@ -139,7 +139,7 @@ object Conversation {
/// received over timeline().next()
fn send_plain_message(text_message: string) -> Future<Result<string>>;

fn send_image_message(message: string, uri: string, name: Option<string>, mimetype: Option<string>, size: Option<u64>) -> Future<Result<string>>;
fn send_image_message(message: string, uri: string, name: string, mimetype: string, size: u32, width: u32, height: u32) -> Future<Result<string>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

message isn't used anymore.

@bitfriend
Copy link
Contributor Author

The get_media_content function of matrix sdk returns image binary not file URL.
So I couldn't display image with URL on flutter side.
I implemented custom image message builder using Image.memory.

The user credential is needed before encrypted image can be decrypted.
So I implemented CURRENT_CLIENT variable globally, it keeps active matrix client when user does login.

And on rust side, I implemented the class that keeps image-specific info, excluding standard message info.
It is ImageDescription.

I used string_validator library to check if image URL is valid on flutter side.
Because the image that user just sent is displayed from local not remote.

@bitfriend
Copy link
Contributor Author

RUNTIME.block_on must be replaced with RUNTIME.spawn, before we can avoid deadlock when opening ChatScreen page.
I replaced it in bin_data of ImageDescription.
But I didn't replace in image_description of RoomMessage.
Now I can see bug that image is not displayed if above function replaced.
This is problem that should be fixed as soon.

Copy link
Contributor

@gnunicorn gnunicorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert from using a global client and instead pass a client: self.client.clone() when creating messages from the room.

Further comments inline.

@@ -47,6 +47,8 @@ impl std::ops::Deref for Client {
}
}

static mut CURRENT_CLIENT: Option<MatrixClient> = None;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can't do that, sorry. The client is hold by the surrounding SDK and the code may not assume that there is a global to store the current client. Why? Because the SDK is meant to allow for multi-client support (even if the UI doesn't implement it yet).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, correct.
It doesn't look smart for me too.
But I guess on rust side there is no way to manage the multiple clients and set active to some client.
Alternatively on flutter side there is currentClient of EffektioSdk.
If we go and back between rust and flutter, we can get delay.
Before flutter can deliver current client to rust, flutter must pass token to rust and rust must login with it, so it takes a bit of time to perform this login, because login is operation via network.
Really I tested it and confirmed delay when opening ChatScreen page.
So I just wanted to finish all operations in rust, without go and back between rust and flutter.

Okay.
Will work from currentClient of EffektioSdk of flutter side.

Copy link
Contributor

@gtalha07 gtalha07 May 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you tested it in release build?

Copy link
Contributor Author

@bitfriend bitfriend May 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I tested in debug build.
In that testing, 10 logins were needed when 10 image messages loaded.
Anyway, will find solution.

Comment on lines 91 to 96
_bin_data: Vec<u8>,
_name: String,
_mimetype: Option<String>,
_size: u64,
_width: Option<u64>,
_height: Option<u64>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in rust all fields are private, unless prefixed with pub. _-prefixes are only used for internal variables, that aren't actually needed. Let's stick to that model and stay with regular names instead.

Suggested change
_bin_data: Vec<u8>,
_name: String,
_mimetype: Option<String>,
_size: u64,
_width: Option<u64>,
_height: Option<u64>,
bin_data: Vec<u8>,
name: String,
mimetype: Option<String>,
size: u64,
width: Option<u64>,
height: Option<u64>,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, rust recognize _ as private property in struct.
By the way, ImageDescription must keep the getter functions about these fields.
So I appended the prefix _ to properties.

Copy link
Contributor

@gnunicorn gnunicorn May 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, _-prefixes are acceptable, but are unnecessary and make it needlessly harder to read, so I'd prefer we dropped them. Of course that doesn't mean we don't need the getter functions, that's totally okay. Was just talking about the style of these names...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood.
Will change property name and keep getter function name.

}

impl ImageDescription {
pub async fn bin_data(&self) -> Result<api::FfiBuffer<u8>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't copy here but only on request actually call to client and fetch the image and return it.

}

pub struct ImageDescription {
_bin_data: Vec<u8>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't hold this constantly in memory here.

Comment on lines 53 to 61
let bin_data = client
.get_media_content(
&MediaRequest {
source: content.source.clone(),
format: MediaFormat::File,
},
false,
)
.await?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't do this here but only upon request of the actual function call.

pub async fn image_description(&self) -> Result<ImageDescription> {
match &self.inner.content.msgtype {
MessageType::Image(content) => {
let client = Client::current_client().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than assuming a global, we should put client onto the room-message (and the image description), so we can do:

Suggested change
let client = Client::current_client().unwrap();
let client = self.client.clone();


object ImageDescription {

fn bin_data() -> Future<Result<buffer<u8>>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have a better name for this function? How about image_binary or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea

Comment on lines 97 to 115

fn msgtype() -> string;

fn image_description() -> Future<Result<ImageDescription>>;
}

object ImageDescription {

fn bin_data() -> Future<Result<buffer<u8>>>;

fn name() -> string;

fn mimetype() -> Option<string>;

fn size() -> u64;

fn width() -> Option<u64>;

fn height() -> Option<u64>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add doc comments to all new functions in this file.

@bitfriend
Copy link
Contributor Author

bitfriend commented May 28, 2022

On flutter side:

  1. In case that _insertMessage is wrapped into setState.
    What if the 1st image binary reaches before initState completes in ChatScreen page?
  2. In case that _insertMessage is not wrapped into setState.
    What if the fetching image binary reaches after initState completed in ChatScreen page?

Without mutex, it is impossible to solve these problems atomically.
So I added mutex to dependency and implemented mutex for state updating on flutter side.

PS:
As you see, now this app is handling the image caching in low level directly.
So mutex and image custom builder was introduced.
If it is possible to use remote URL (that means image is not encrypted), we could remove all these complicated things.
But we are targeting the encrypted images, so we must handle the image caching directly.

@bitfriend
Copy link
Contributor Author

Hi, Ben.
Your commit (211b4f5) to my branch is working well.
Thank you for great help again.

@bitfriend
Copy link
Contributor Author

bitfriend commented Jun 2, 2022

I used path.join to prepare file download path on flutter side.
This function depends on libandroidicu.so and I included this library into jniLibs.
I could get this library for only x86_64, not x86/arm64-v8a/armeabi-v7a.

I used fluttertoast to show error in case that user clicked the downloaded file and there is no associated with that file.

I moved the image/file reading from message to room.
Because room on rust side keeps client and ChatScreen.dart contains room object.
So we can keep messages.rs simple as original (I had introduced matrix client to read image/file in messages.rs).

@gnunicorn
Copy link
Contributor

I used fluttertoast to show error in case that user clicked the downloaded file and there is no associated with that file.

we've just removed fluttertoast (see #119 ), please use the default Material Snackbar.

I used path.join to prepare file download path on flutter side.
This function depends on libandroidicu.so and I included this library into jniLibs.

Let's not. Please use rust's path-stuff for anything localised - just add a new method.

@bitfriend
Copy link
Contributor Author

I used fluttertoast to show error in case that user clicked the downloaded file and there is no associated with that file.

we've just removed fluttertoast (see #119 ), please use the default Material Snackbar.

I used path.join to prepare file download path on flutter side.
This function depends on libandroidicu.so and I included this library into jniLibs.

Let's not. Please use rust's path-stuff for anything localised - just add a new method.

Great idea.
Will do.

@bitfriend
Copy link
Contributor Author

bitfriend commented Jun 14, 2022

ubuntu-latest was upgraded from 20220529 to 20220605.
Now we are getting the error unable to find library -lgcc.
As of 2.7.0, cargo-ndk stopped the support of NDK r22.
NDK r23+ occurs the error unable to find library -lgcc.
Two changes made to fix this bug.

  1. Downgrade cargo-ndk. Replace cargo install cargo-ndk with cargo install cargo-ndk --version 2.6.0 in Makefile.toml of root directory.
  2. Add this step prior to cargo make --profile release android-arm
      - uses: nttld/setup-ndk@v1
        id: install-ndk-22
        with:
          # FIXME: cargo-ndk doesn't work with latest
          ndk-version: r22
--- NEW <
      - name: Copy ndk utilities for cargo make
        run: |
          rmdir -r /usr/local/lib/android/sdk/ndk-bundle/toolchains
          mkdir -p /usr/local/lib/android/sdk/ndk-bundle/toolchains
          cp -R ${{ steps.install-ndk-22.outputs.ndk-path }}/toolchains/llvm /usr/local/lib/android/sdk/ndk-bundle/toolchains
--- NEW >

Copy link
Contributor

@gnunicorn gnunicorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bunch of minor API design and internal style remarks. Please fix at your own peril and merge.

Comment on lines +98 to +99
/// m.audio, m.emote, m.file, m.image, m.location, m.service_notice, m.text, m.video or m.key.verification.request
fn msgtype() -> string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might want to consider switching this into an Enum-type... but later...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make that into an issue for later, once the PR is merged.

Comment on lines +102 to +105
fn image_description() -> Result<ImageDescription>;

/// contains source data, name, mimetype and size
fn file_description() -> Result<FileDescription>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there is no image/file? I assume this is then None? Shouldn't this be Option rather than Result then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I was planning to user these functions for only file/image.
Will fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to abstract image description and file description into a single struct.
But they have different fields, so I couldn't abstract them.

Comment on lines +116 to +117
/// file size
fn size() -> u64;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... in byte?

Suggested change
/// file size
fn size() -> u64;
/// file size in byte
fn size() -> u64;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are correct.

/// MIME
fn mimetype() -> Option<string>;

/// file size
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// file size
/// file size in byte

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are correct

fn send_image_message(uri: string, name: string, mimetype: string, size: u32, width: u32, height: u32) -> Future<Result<string>>;

/// decrypted image file data
fn image_binary(event_id: string) -> Future<Result<buffer<u8>>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this function not on image or RoomMessage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flutter keeps room as member variable in UI.
Flutter doesn't keep room message as variable, because it may have to load so many messages in screen.
So I wanted to get image binary data from member variable.
Getting temporary variable for each message may be slow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, fair enough then. keep it the way it is.

},
};
use std::{fs::File, io::Write};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these are needed ...

Suggested change
use std::{fs::File, io::Write};

Comment on lines +215 to +217
size: u32,
width: u32,
height: u32,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't these be optionals, too? Or can we be sure that any flutter will always provide them for us?

[room.room_id().as_str().as_bytes(), event_id.as_bytes()].concat();
client
.store()
.set_custom_value(&key, path.to_str().unwrap().as_bytes().to_vec())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't unwrap in actual code (tests are fine). Instead always use .expect(MSG) with a message stating, why we can be sure this never panics. e.g. :

Suggested change
.set_custom_value(&key, path.to_str().unwrap().as_bytes().to_vec())
.set_custom_value(&key, path.to_str().expect("Path was generated from strings. Must be string").as_bytes().to_vec())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix

let path = client.store().get_custom_value(&key).await?;
match path {
Some(value) => {
let text = std::str::from_utf8(&value).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above. please don't unwrap. always expect.

Comment on lines +383 to +388
None => Ok("".to_owned()),
}
}
_ => Ok("".to_owned()),
},
_ => Ok("".to_owned()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure, these should all be None rather than Ok of an empty string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@bitfriend bitfriend closed this Jun 15, 2022
@bitfriend bitfriend deleted the issue77_file-transfter-in-chat branch June 15, 2022 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants