util.rs 30.7 KB
Newer Older
1
extern crate glib;
2 3 4 5 6
extern crate url;
extern crate reqwest;
extern crate regex;
extern crate serde_json;
extern crate cairo;
7 8
extern crate pango;
extern crate pangocairo;
Daniel García Moreno's avatar
Daniel García Moreno committed
9 10
extern crate gdk;
extern crate gdk_pixbuf;
11 12
extern crate mime;
extern crate tree_magic;
13 14 15
extern crate unicode_segmentation;

use self::unicode_segmentation::UnicodeSegmentation;
16

17 18
use self::pango::LayoutExt;

Daniel García Moreno's avatar
Daniel García Moreno committed
19
use self::gdk_pixbuf::Pixbuf;
20
use self::gdk_pixbuf::PixbufExt;
Daniel García Moreno's avatar
Daniel García Moreno committed
21 22
use self::gdk::ContextExt;

23 24 25 26 27 28
use self::regex::Regex;

use self::serde_json::Value as JsonValue;

use self::url::Url;
use std::io::Read;
29
use std::path::Path;
30
use std::path::PathBuf;
31
use std::collections::HashMap;
32 33

use std::fs::File;
34
use std::fs::create_dir_all;
35 36
use std::io::prelude::*;

37
use std::collections::HashSet;
38 39 40
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

41
use std::time::Duration as StdDuration;
42 43 44

use error::Error;
use types::Message;
45
use types::Room;
46
use types::Event;
47
use types::Member;
48

49
use self::reqwest::header::ContentType;
50
use self::mime::Mime;
51

52 53
use globals;

54

55
#[allow(dead_code)]
56 57 58 59 60 61
pub enum AvatarMode {
    Rect,
    Circle,
}


62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
macro_rules! semaphore {
    ($cv: expr, $blk: block) => {{
        let thread_count = $cv.clone();
        thread::spawn(move || {
            // waiting, less than 20 threads at the same time
            // this is a semaphore
            // TODO: use std::sync::Semaphore when it's on stable version
            // https://doc.rust-lang.org/1.1.0/std/sync/struct.Semaphore.html
            let &(ref num, ref cvar) = &*thread_count;
            {
                let mut start = num.lock().unwrap();
                while *start >= 20 {
                    start = cvar.wait(start).unwrap()
                }
                *start += 1;
            }

            $blk

            // freeing the cvar for new threads
            {
                let mut counter = num.lock().unwrap();
                *counter -= 1;
            }
            cvar.notify_one();
        });
    }}
}


92 93 94 95 96 97
#[macro_export]
macro_rules! identicon {
    ($userid: expr, $name: expr) => { draw_identicon($userid, $name, AvatarMode::Circle) }
}


98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
// from https://stackoverflow.com/a/43992218/1592377
#[macro_export]
macro_rules! clone {
    (@param _) => ( _ );
    (@param $x:ident) => ( $x );
    ($($n:ident),+ => move || $body:expr) => (
        {
            $( let $n = $n.clone(); )+
            move || $body
        }
    );
    ($($n:ident),+ => move |$($p:tt),+| $body:expr) => (
        {
            $( let $n = $n.clone(); )+
            move |$(clone!(@param $p),)+| $body
        }
    );
}
116

117 118 119 120 121 122 123
#[macro_export]
macro_rules! strn {
    ($p: expr) => (
        String::from($p)
    );
}

124 125 126 127 128 129 130
#[macro_export]
macro_rules! client_url {
    ($b: expr, $path: expr, $params: expr) => (
        build_url($b, &format!("/_matrix/client/r0/{}", $path), $params)
    )
}

131 132 133 134 135 136 137
#[macro_export]
macro_rules! scalar_url {
    ($b: expr, $path: expr, $params: expr) => (
        build_url($b, &format!("api/{}", $path), $params)
    )
}

138 139 140 141 142 143 144
#[macro_export]
macro_rules! media_url {
    ($b: expr, $path: expr, $params: expr) => (
        build_url($b, &format!("/_matrix/media/r0/{}", $path), $params)
    )
}

145 146 147 148 149 150 151 152 153 154
#[macro_export]
macro_rules! derror {
    ($from: path, $to: path) => {
        impl From<$from> for Error {
            fn from(_: $from) -> Error {
                $to
            }
        }
    };
}
155 156 157 158 159 160 161 162 163 164 165 166

#[macro_export]
macro_rules! bkerror {
    ($result: ident, $tx: ident, $type: expr) => {
        if let Err(e) = $result {
            $tx.send($type(e)).unwrap();
        }
    }
}

#[macro_export]
macro_rules! get {
167 168 169
    ($url: expr, $attrs: expr, $okcb: expr, $errcb: expr, $timeout: expr) => {
        query!("get", $url, $attrs, $okcb, $errcb, $timeout)
    };
170 171 172 173 174 175 176 177 178 179
    ($url: expr, $attrs: expr, $okcb: expr, $errcb: expr) => {
        query!("get", $url, $attrs, $okcb, $errcb)
    };
    ($url: expr, $okcb: expr, $errcb: expr) => {
        query!("get", $url, $okcb, $errcb)
    };
}

#[macro_export]
macro_rules! post {
180 181 182
    ($url: expr, $attrs: expr, $okcb: expr, $errcb: expr, $timeout: expr) => {
        query!("post", $url, $attrs, $okcb, $errcb, $timeout)
    };
183 184 185 186 187 188 189 190 191 192
    ($url: expr, $attrs: expr, $okcb: expr, $errcb: expr) => {
        query!("post", $url, $attrs, $okcb, $errcb)
    };
    ($url: expr, $okcb: expr, $errcb: expr) => {
        query!("post", $url, $okcb, $errcb)
    };
}

#[macro_export]
macro_rules! query {
193
    ($method: expr, $url: expr, $attrs: expr, $okcb: expr, $errcb: expr, $timeout: expr) => {
194
        thread::spawn(move || {
195
            let js = json_q($method, $url, $attrs, $timeout);
196 197 198 199 200 201 202 203 204 205 206

            match js {
                Ok(r) => {
                    $okcb(r)
                },
                Err(err) => {
                    $errcb(err)
                }
            }
        });
    };
207 208 209
    ($method: expr, $url: expr, $attrs: expr, $okcb: expr, $errcb: expr) => {
        query!($method, $url, $attrs, $okcb, $errcb, globals::TIMEOUT);
    };
210
    ($method: expr, $url: expr, $okcb: expr, $errcb: expr) => {
211
        let attrs = json!(null);
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
        query!($method, $url, &attrs, $okcb, $errcb)
    };
}

#[allow(unused_macros)]
#[macro_export]
macro_rules! media {
    ($base: expr, $url: expr, $dest: expr) => {
        dw_media($base, $url, false, $dest, 0, 0)
    };
    ($base: expr, $url: expr) => {
        dw_media($base, $url, false, None, 0, 0)
    };
}

#[macro_export]
macro_rules! thumb {
    ($base: expr, $url: expr) => {
        dw_media($base, $url, true, None, 64, 64)
    };
    ($base: expr, $url: expr, $size: expr) => {
        dw_media($base, $url, true, None, $size, $size)
    };
    ($base: expr, $url: expr, $w: expr, $h: expr) => {
        dw_media($base, $url, true, None, $w, $h)
    };
}

240 241 242 243 244 245 246 247 248 249 250
pub fn evc(events: &JsonValue, t: &str, field: &str) -> String {
    if let Some(arr) = events.as_array() {
        return match arr.iter().find(|x| x["type"] == t) {
            Some(js) => String::from(js["content"][field].as_str().unwrap_or("")),
            None => String::new(),
        };
    }

    String::new()
}

251
pub fn get_rooms_from_json(r: &JsonValue, userid: &str, baseu: &Url) -> Result<Vec<Room>, Error> {
252 253 254
    let rooms = &r["rooms"];

    let join = rooms["join"].as_object().ok_or(Error::BackendError)?;
255
    let leave = rooms["leave"].as_object().ok_or(Error::BackendError)?;
256
    let invite = rooms["invite"].as_object().ok_or(Error::BackendError)?;
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
    let global_account = &r["account_data"]["events"].as_array();

    // getting the list of direct rooms
    let mut direct: HashSet<String> = HashSet::new();
    match global_account.unwrap_or(&vec![]).iter().find(|x| x["type"] == "m.direct") {
        Some(js) => {
            if let Some(content) = js["content"].as_object() {
                for i in content.keys() {
                    for room in content[i].as_array().unwrap_or(&vec![]) {
                        if let Some(roomid) = room.as_str() {
                            direct.insert(roomid.to_string());
                        }
                    }
                }
            }
        },
        None => {}
    };
275

276
    let mut rooms: Vec<Room> = vec![];
277 278
    for k in join.keys() {
        let room = join.get(k).ok_or(Error::BackendError)?;
279
        let stevents = &room["state"]["events"];
280
        let timeline = &room["timeline"];
281
        let dataevs = &room["account_data"]["events"];
282
        let name = calculate_room_name(stevents, userid)?;
283
        let mut r = Room::new(k.clone(), name);
284

285 286 287
        r.avatar = Some(evc(stevents, "m.room.avatar", "url"));
        r.alias = Some(evc(stevents, "m.room.canonical_alias", "alias"));
        r.topic = Some(evc(stevents, "m.room.topic", "topic"));
288
        r.direct = direct.contains(k);
289
        r.notifications = room["unread_notifications"]["notification_count"]
290 291
            .as_i64()
            .unwrap_or(0) as i32;
292 293 294
        r.highlight = room["unread_notifications"]["highlight_count"]
            .as_i64()
            .unwrap_or(0) as i32;
295

296 297
        for ev in dataevs.as_array() {
            for tag in ev.iter().filter(|x| x["type"] == "m.tag") {
298
                if let Some(_) = tag["content"]["tags"]["m.favourite"].as_object() {
299 300 301 302 303
                    r.fav = true;
                }
            }
        }

304 305 306
        if let Some(evs) = timeline["events"].as_array() {
            let ms = Message::from_json_events_iter(k.clone(), evs.iter());
            r.messages.extend(ms);
307 308
        }

309 310 311 312 313 314 315 316 317 318 319
        let mevents = stevents.as_array().unwrap()
            .iter()
            .filter(|x| x["type"] == "m.room.member");

        for ev in mevents {
            let member = parse_room_member(ev);
            if let Some(m) = member {
                r.members.insert(m.uid.clone(), m.clone());
            }
        }

320 321 322
        // power levels info
        r.power_levels = get_admins(stevents);

323
        rooms.push(r);
324 325
    }

326
    // left rooms
327 328 329 330 331 332
    for k in leave.keys() {
        let mut r = Room::new(k.clone(), None);
        r.left = true;
        rooms.push(r);
    }

333 334 335 336 337
    // invitations
    for k in invite.keys() {
        let room = invite.get(k).ok_or(Error::BackendError)?;
        let stevents = &room["invite_state"]["events"];
        let name = calculate_room_name(stevents, userid)?;
338
        let mut r = Room::new(k.clone(), name);
339 340 341 342 343
        r.inv = true;

        r.avatar = Some(evc(stevents, "m.room.avatar", "url"));
        r.alias = Some(evc(stevents, "m.room.canonical_alias", "alias"));
        r.topic = Some(evc(stevents, "m.room.topic", "topic"));
344
        r.direct = direct.contains(k);
345

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
        if let Some(arr) = stevents.as_array() {
            if let Some(ev) = arr.iter()
                                 .find(|x| x["membership"] == "invite" && x["state_key"] == userid) {
                if let Ok((alias, avatar)) = get_user_avatar(baseu, ev["sender"].as_str().unwrap_or_default()) {
                    r.inv_sender = Some(
                        Member {
                            alias: Some(alias),
                            avatar: Some(avatar),
                            uid: strn!(userid),
                        }
                    );
                }
            }
        }

361 362 363
        rooms.push(r);
    }

364
    Ok(rooms)
365 366
}

367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
pub fn get_admins(stevents: &JsonValue) -> HashMap<String, i32> {
    let mut admins = HashMap::new();

    let plevents = stevents.as_array().unwrap()
        .iter()
        .filter(|x| x["type"] == "m.room.power_levels");

    for ev in plevents {
        if let Some(users) = ev["content"]["users"].as_object() {
            for u in users.keys() {
                let level = users[u].as_i64().unwrap_or_default();
                admins.insert(u.to_string(), level as i32);
            }
        }
    }

    admins
}

386 387 388 389 390
pub fn get_rooms_timeline_from_json(baseu: &Url,
                                    r: &JsonValue,
                                    tk: String,
                                    prev_batch: String)
                                    -> Result<Vec<Message>, Error> {
391 392 393 394 395 396
    let rooms = &r["rooms"];
    let join = rooms["join"].as_object().ok_or(Error::BackendError)?;

    let mut msgs: Vec<Message> = vec![];
    for k in join.keys() {
        let room = join.get(k).ok_or(Error::BackendError)?;
397 398 399 400 401 402 403 404 405 406 407 408 409 410

        if let (Some(true), Some(pb)) = (room["timeline"]["limited"].as_bool(),
                                         room["timeline"]["prev_batch"].as_str()) {
            let pbs = pb.to_string();
            let fill_the_gap = fill_room_gap(baseu,
                                             tk.clone(),
                                             k.clone(),
                                             prev_batch.clone(),
                                             pbs.clone())?;
            for m in fill_the_gap {
                msgs.push(m);
            }
        }

411 412
        let timeline = room["timeline"]["events"].as_array();
        if timeline.is_none() {
413
            continue;
414 415
        }

416 417 418
        let events = timeline.unwrap().iter();
        let ms = Message::from_json_events_iter(k.clone(), events);
        msgs.extend(ms);
419 420 421 422 423
    }

    Ok(msgs)
}

424
pub fn get_rooms_notifies_from_json(r: &JsonValue) -> Result<Vec<(String, i32, i32)>, Error> {
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
    let rooms = &r["rooms"];
    let join = rooms["join"].as_object().ok_or(Error::BackendError)?;

    let mut out: Vec<(String, i32, i32)> = vec![];
    for k in join.keys() {
        let room = join.get(k).ok_or(Error::BackendError)?;
        let n = room["unread_notifications"]["notification_count"]
            .as_i64()
            .unwrap_or(0) as i32;
        let h = room["unread_notifications"]["highlight_count"]
            .as_i64()
            .unwrap_or(0) as i32;

        out.push((k.clone(), n, h));
    }

    Ok(out)
}

444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
pub fn parse_sync_events(r: &JsonValue) -> Result<Vec<Event>, Error> {
    let rooms = &r["rooms"];
    let join = rooms["join"].as_object().ok_or(Error::BackendError)?;

    let mut evs: Vec<Event> = vec![];
    for k in join.keys() {
        let room = join.get(k).ok_or(Error::BackendError)?;
        let timeline = room["timeline"]["events"].as_array();
        if timeline.is_none() {
            return Ok(evs);
        }

        let events = timeline.unwrap()
            .iter()
            .filter(|x| x["type"] != "m.room.message");

        for ev in events {
461
            //println!("ev: {:#?}", ev);
462 463 464 465 466 467 468 469 470 471 472 473 474
            evs.push(Event {
                room: k.clone(),
                sender: strn!(ev["sender"].as_str().unwrap_or("")),
                content: ev["content"].clone(),
                stype: strn!(ev["type"].as_str().unwrap_or("")),
                id: strn!(ev["id"].as_str().unwrap_or("")),
            });
        }
    }

    Ok(evs)
}

475
pub fn get_media(url: &str) -> Result<Vec<u8>, Error> {
476 477
    let client = reqwest::Client::new();
    let mut conn = client.get(url);
478 479 480 481 482 483 484 485
    let mut res = conn.send()?;

    let mut buffer = Vec::new();
    res.read_to_end(&mut buffer)?;

    Ok(buffer)
}

486
pub fn put_media(url: &str, file: Vec<u8>) -> Result<JsonValue, Error> {
487 488
    let client = reqwest::Client::new();
    let mut conn = client.post(url);
489 490
    let mime: Mime = (&tree_magic::from_u8(&file)).parse().unwrap();

491
    conn.body(file);
492 493

    conn.header(ContentType(mime));
494 495 496 497 498 499 500 501 502

    let mut res = conn.send()?;

    match res.json() {
        Ok(js) => Ok(js),
        Err(_) => Err(Error::BackendError),
    }
}

503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
pub fn resolve_media_url(
    base: &Url,
    url: &str,
    thumb: bool,
    w: i32,
    h: i32,
) -> Result<Url, Error> {
    let re = Regex::new(r"mxc://(?P<server>[^/]+)/(?P<media>.+)")?;
    let caps = re.captures(url).ok_or(Error::BackendError)?;
    let server = String::from(&caps["server"]);
    let media = String::from(&caps["media"]);

    let mut params: Vec<(&str, String)> = vec![];
    let path: String;

    if thumb {
        params.push(("width", format!("{}", w)));
        params.push(("height", format!("{}", h)));
        params.push(("method", strn!("scale")));
        path = format!("thumbnail/{}/{}", server, media);
    } else {
        path = format!("download/{}/{}", server, media);
    }

    media_url!(base, &path, params)
}

530 531 532 533 534 535 536
pub fn dw_media(base: &Url,
                url: &str,
                thumb: bool,
                dest: Option<&str>,
                w: i32,
                h: i32)
                -> Result<String, Error> {
537 538 539 540 541
    let re = Regex::new(r"mxc://(?P<server>[^/]+)/(?P<media>.+)")?;
    let caps = re.captures(url).ok_or(Error::BackendError)?;
    let server = String::from(&caps["server"]);
    let media = String::from(&caps["media"]);

542 543
    let mut params: Vec<(&str, String)> = vec![];
    let path: String;
544 545

    if thumb {
546 547 548 549
        params.push(("width", format!("{}", w)));
        params.push(("height", format!("{}", h)));
        params.push(("method", strn!("scale")));
        path = format!("thumbnail/{}/{}", server, media);
550
    } else {
551
        path = format!("download/{}/{}", server, media);
552 553
    }

554
    let url = media_url!(base, &path, params)?;
555

556
    let fname = match dest {
557 558
        None if thumb => { cache_dir_path("thumbs", &media)?  }
        None => { cache_dir_path("medias", &media)?  }
559
        Some(d) => String::from(d),
560 561
    };

562 563 564 565
    download_file(url.as_str(), fname, dest)
}

pub fn download_file(url: &str, fname: String, dest: Option<&str>) -> Result<String, Error> {
566 567 568
    let pathname = fname.clone();
    let p = Path::new(&pathname);
    if p.is_file() {
569 570 571 572 573
        if dest.is_none() {
            return Ok(fname);
        }

        let moddate = p.metadata()?.modified()?;
574 575
        // one minute cached
        if moddate.elapsed()?.as_secs() < 60 {
576 577
            return Ok(fname);
        }
578 579
    }

580
    let mut file = File::create(&fname)?;
581
    let buffer = get_media(url)?;
582 583 584 585 586
    file.write_all(&buffer)?;

    Ok(fname)
}

587
pub fn json_q(method: &str, url: &Url, attrs: &JsonValue, timeout: u64) -> Result<JsonValue, Error> {
588
    let mut clientb = reqwest::ClientBuilder::new();
589
    let client = match timeout {
590
        0 => clientb.timeout(None).build()?,
591 592
        n => clientb.timeout(StdDuration::from_secs(n)).build()?
    };
593 594

    let mut conn = match method {
595 596 597 598
        "post" => client.post(url.as_str()),
        "put" => client.put(url.as_str()),
        "delete" => client.delete(url.as_str()),
        _ => client.get(url.as_str()),
599 600
    };

601 602 603 604 605
    if !attrs.is_null() {
        conn.json(attrs);
    }

    let mut res = conn.send()?;
606 607 608 609 610

    //let mut content = String::new();
    //res.read_to_string(&mut content);
    //cb(content);

611 612 613 614 615 616 617
    if !res.status().is_success() {
        return match res.json() {
            Ok(js) => Err(Error::MatrixError(js)),
            Err(err) => Err(Error::ReqwestError(err))
        }
    }

618 619 620 621 622 623 624 625 626 627 628 629
    let json: Result<JsonValue, reqwest::Error> = res.json();
    match json {
        Ok(js) => {
            let js2 = js.clone();
            if let Some(error) = js.as_object() {
                if error.contains_key("errcode") {
                    println!("ERROR: {:#?}", js2);
                    return Err(Error::MatrixError(js2));
                }
            }
            Ok(js)
        }
630 631 632 633
        Err(_) => Err(Error::BackendError),
    }
}

634
pub fn get_user_avatar(baseu: &Url, userid: &str) -> Result<(String, String), Error> {
635
    let url = client_url!(baseu, &format!("profile/{}", userid), vec![])?;
636
    let attrs = json!(null);
637

638
    match json_q("get", &url, &attrs, globals::TIMEOUT) {
639
        Ok(js) => {
640 641 642 643 644 645
            let name = match js["displayname"].as_str() {
                Some(n) if n.is_empty() => userid.to_string(),
                Some(n) => n.to_string(),
                None => userid.to_string(),
            };

646
            match js["avatar_url"].as_str() {
647 648 649
                Some(url) => {
                    let dest = cache_path(userid)?;
                    let img = dw_media(baseu, &url, true, Some(&dest), 64, 64)?;
650
                    Ok((name.clone(), img))
651
                },
652
                None => Ok((name.clone(), identicon!(userid, name)?)),
653 654
            }
        }
655
        Err(_) => Ok((String::from(userid), identicon!(userid, String::from(&userid[1..2]))?)),
656 657 658 659
    }
}

pub fn get_room_st(base: &Url, tk: &str, roomid: &str) -> Result<JsonValue, Error> {
660
    let url = client_url!(base, &format!("rooms/{}/state", roomid), vec![("access_token", strn!(tk))])?;
661

662
    let attrs = json!(null);
663
    let st = json_q("get", &url, &attrs, globals::TIMEOUT)?;
664 665 666 667 668 669 670 671 672
    Ok(st)
}

pub fn get_room_avatar(base: &Url, tk: &str, userid: &str, roomid: &str) -> Result<String, Error> {
    let st = get_room_st(base, tk, roomid)?;
    let events = st.as_array().ok_or(Error::BackendError)?;

    // we look for members that aren't me
    let filter = |x: &&JsonValue| {
673
        (x["type"] == "m.room.member" && x["content"]["membership"] == "join" &&
674 675 676 677 678
         x["sender"] != userid)
    };
    let members = events.iter().filter(&filter);
    let mut members2 = events.iter().filter(&filter);

679
    let m1 = match members2.next() {
680
        Some(m) => m["content"]["avatar_url"].as_str().unwrap_or(""),
681
        None => "",
682 683 684
    };

    let mut fname = match members.count() {
685
        1 => thumb!(&base, m1).unwrap_or_default(),
686
        _ => String::new(),
687 688 689
    };

    if fname.is_empty() {
690 691 692 693
        let roomname = match calculate_room_name(&st, userid)?{
            Some(ref name) => { name.clone() },
            None => { "X".to_string() },
        };
694
        fname = identicon!(roomid, roomname)?;
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
    }

    Ok(fname)
}

struct Color {
    r: i32,
    g: i32,
    b: i32,
}

pub fn calculate_hash<T: Hash>(t: &T) -> u64 {
    let mut s = DefaultHasher::new();
    t.hash(&mut s);
    s.finish()
}

712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
pub fn get_initials(name: String) -> Result<String, Error> {
        let name = name.trim_right_matches(" (IRC)");
        let mut words = name.unicode_words();
        let first = words
            .next()
            .and_then(|w| UnicodeSegmentation::graphemes(w, true).next())
            .unwrap_or_default();
        let second = words
            .next()
            .and_then(|w| UnicodeSegmentation::graphemes(w, true).next())
            .unwrap_or_default();
        let initials = format!( "{}{}", first, second);

        Ok(initials)
}

728
pub fn draw_identicon(fname: &str, name: String, mode: AvatarMode) -> Result<String, Error> {
729 730 731 732 733 734 735 736 737 738 739
    // Our color palette with a darker and a muted variant for each one
    let colors = [
        [Color { r: 206, g: 77, b: 205, }, Color { r: 251, g: 224, b: 251, }],
        [Color { r: 121, g: 81, b: 192, }, Color { r: 231, g: 218, b: 251, }],
        [Color { r: 78, g: 99, b: 201, }, Color { r: 207, g: 215, b: 248, }],
        [Color { r: 66, g: 160, b: 243, }, Color { r: 214, g: 234, b: 252, }],
        [Color { r: 70, g: 189, b: 158, }, Color { r: 212, g: 248, b: 239, }],
        [Color { r: 117, g: 184, b: 45, }, Color { r: 220, g: 247, b: 191, }],
        [Color { r: 235, g: 121, b: 10, }, Color { r: 254, g: 235, b: 218, }],
        [Color { r: 227, g: 61, b: 34,  }, Color { r: 251, g: 219, b: 211, }],
        [Color { r: 109, g: 109, b: 109, }, Color { r: 219, g: 219, b: 219  }],
740
    ];
741

742
    let fname = cache_path(fname)?;
743

744
    let image = cairo::ImageSurface::create(cairo::Format::ARgb32, 60, 60)?;
745 746
    let g = cairo::Context::new(&image);

747 748 749
    let color_index = calculate_hash(&fname) as usize % colors.len() as usize;
    let bg_c = &colors[color_index][0];
    g.set_source_rgba(bg_c.r as f64 / 256., bg_c.g as f64 / 256., bg_c.b as f64 / 256., 1.);
750 751

    match mode {
752
        AvatarMode::Rect => g.rectangle(0., 0., 60., 60.),
753
        AvatarMode::Circle => {
754
            g.arc(30.0, 30.0, 30.0, 0.0, 2.0 * 3.14159);
755 756 757
            g.fill();
        }
    };
758

759 760
    let fg_c = &colors[color_index][1];
    g.set_source_rgba(fg_c.r as f64 / 256., fg_c.g as f64 / 256., fg_c.b as f64 / 256., 1.);
761

762 763 764 765
    if !name.is_empty() {
        let initials = get_initials(name)?.to_uppercase();

        let layout = pangocairo::functions::create_layout(&g).unwrap();
766
        let fontdesc = pango::FontDescription::from_string("Cantarell Ultra-Bold 26");
767 768 769 770 771 772 773 774 775 776 777
        layout.set_font_description(&fontdesc);
        layout.set_text(&initials);
        // Move to center of the background shape we drew,
        // offset by half the size of the glyph
        let bx = image.get_width();
        let by = image.get_height();
        let (ox, oy) = layout.get_pixel_size();
        g.translate((bx - ox) as f64/2., (by - oy) as f64/2.);
        // Finally draw the glyph
        pangocairo::functions::show_layout(&g, &layout);
    }
778 779 780 781 782 783 784

    let mut buffer = File::create(&fname)?;
    image.write_to_png(&mut buffer)?;

    Ok(fname)
}

785
pub fn calculate_room_name(roomst: &JsonValue, userid: &str) -> Result<Option<String>, Error> {
786 787 788 789

    // looking for "m.room.name" event
    let events = roomst.as_array().ok_or(Error::BackendError)?;
    if let Some(name) = events.iter().find(|x| x["type"] == "m.room.name") {
790
        if let Some(name) = name["content"]["name"].as_str() {
791
            if !name.to_string().is_empty() {
792
                return Ok(Some(name.to_string()));
793
            }
794
        }
795
    }
796

797 798
    // looking for "m.room.canonical_alias" event
    if let Some(name) = events.iter().find(|x| x["type"] == "m.room.canonical_alias") {
799
        if let Some(name) = name["content"]["alias"].as_str() {
800
            return Ok(Some(name.to_string()));
801
        }
802 803 804 805
    }

    // we look for members that aren't me
    let filter = |x: &&JsonValue| {
806 807 808 809 810 811
        (x["type"] == "m.room.member" &&
         (
          (x["content"]["membership"] == "join" && x["sender"] != userid) ||
          (x["content"]["membership"] == "invite" && x["state_key"] != userid)
         )
        )
812
    };
813
    let c = events.iter().filter(&filter);
814 815 816
    let members = events.iter().filter(&filter);
    let mut members2 = events.iter().filter(&filter);

817 818 819 820 821
    if c.count() == 0 {
        // we don't have information to calculate the name
        return Ok(None);
    }

822
    let m1 = match members2.next() {
823 824 825 826
        Some(m) => {
            let sender = m["sender"].as_str().unwrap_or("NONAMED");
            m["content"]["displayname"].as_str().unwrap_or(sender)
        },
827
        None => "",
828
    };
829
    let m2 = match members2.next() {
830 831 832 833
        Some(m) => {
            let sender = m["sender"].as_str().unwrap_or("NONAMED");
            m["content"]["displayname"].as_str().unwrap_or(sender)
        },
834
        None => "",
835 836 837 838 839 840
    };

    let name = match members.count() {
        0 => String::from("EMPTY ROOM"),
        1 => String::from(m1),
        2 => format!("{} and {}", m1, m2),
841
        _ => format!("{} and Others", m1),
842 843
    };

844
    Ok(Some(name))
845 846
}

847 848 849 850 851
/// Recursive function that tries to get at least @get Messages for the room.
///
/// The @limit is the first "limit" param in the GET request.
/// The @end param is used as "from" param in the GET request, so we'll get
/// messages before that.
852 853 854 855 856 857 858
pub fn get_initial_room_messages(baseu: &Url,
                                 tk: String,
                                 roomid: String,
                                 get: usize,
                                 limit: i32,
                                 end: Option<String>)
                                 -> Result<(Vec<Message>, String, String), Error> {
859

860 861 862 863
    let mut ms: Vec<Message> = vec![];
    let mut nstart;
    let mut nend;

864 865
    let mut params = vec![
        ("dir", strn!("b")),
866
        ("limit", format!("{}", limit)),
867 868 869
        ("access_token", tk.clone()),
    ];

870
    match end {
871
        Some(ref e) => { params.push(("from", e.clone())) }
872
        None => {}
873 874
    };

875
    let path = format!("rooms/{}/messages", roomid);
876
    let url = client_url!(baseu, &path, params)?;
877

878
    let r = json_q("get", &url, &json!(null), globals::TIMEOUT)?;
879 880
    nend = String::from(r["end"].as_str().unwrap_or(""));
    nstart = String::from(r["start"].as_str().unwrap_or(""));
881

882 883 884 885
    let array = r["chunk"].as_array();
    if array.is_none() || array.unwrap().len() == 0 {
        return Ok((ms, nstart, nend));
    }
886

887 888 889
    let evs = array.unwrap().iter().rev();
    let msgs = Message::from_json_events_iter(roomid.clone(), evs);
    ms.extend(msgs);
890

891
    if ms.len() < get {
892
        let (more, s, e) =
893
            get_initial_room_messages(baseu, tk, roomid, get, limit * 2, Some(nend))?;
894 895 896 897
        nstart = s;
        nend = e;
        for m in more.iter().rev() {
            ms.insert(0, m.clone());
898 899
        }
    }
900

901
    Ok((ms, nstart, nend))
902
}
903

904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
/// Recursive function that tries to get all messages in a room from a batch id to a batch id,
/// following the response pagination
pub fn fill_room_gap(baseu: &Url,
                     tk: String,
                     roomid: String,
                     from: String,
                     to: String)
                     -> Result<Vec<Message>, Error> {

    let mut ms: Vec<Message> = vec![];
    let nend;

    let mut params = vec![
        ("dir", strn!("f")),
        ("limit", format!("{}", globals::PAGE_LIMIT)),
        ("access_token", tk.clone()),
    ];

    params.push(("from", from.clone()));
    params.push(("to", to.clone()));

    let path = format!("rooms/{}/messages", roomid);
    let url = client_url!(baseu, &path, params)?;

    let r = json_q("get", &url, &json!(null), globals::TIMEOUT)?;
    nend = String::from(r["end"].as_str().unwrap_or(""));

    let array = r["chunk"].as_array();
    if array.is_none() || array.unwrap().len() == 0 {
        return Ok(ms);
    }

936 937 938
    let evs = array.unwrap().iter();
    let mevents = Message::from_json_events_iter(roomid.clone(), evs);
    ms.extend(mevents);
939 940 941 942 943 944 945 946 947 948

    // loading more until no more messages
    let more = fill_room_gap(baseu, tk, roomid, nend, to)?;
    for m in more.iter() {
        ms.insert(0, m.clone());
    }

    Ok(ms)
}

949
pub fn build_url(base: &Url, path: &str, params: Vec<(&str, String)>) -> Result<Url, Error> {
950
    let mut url = base.join(path)?;
951 952

    {
953 954 955 956 957 958 959
        // If len was 0 `?` would be appended without being needed.
        if params.len() >= 1 {
            let mut query = url.query_pairs_mut();
            query.clear();
            for (k, v) in params {
                query.append_pair(k, &v);
            }
960 961 962 963 964
        }
    }

    Ok(url)
}
Daniel García Moreno's avatar
Daniel García Moreno committed
965

966
pub fn circle_image(fname: String) -> Result<String, Error> {
967 968
    use std::f64::consts::PI;

969 970
    let pb = Pixbuf::new_from_file_at_scale(&fname, 40, -1, true)?;
    let image = cairo::ImageSurface::create(cairo::Format::ARgb32, 40, 40)?;
971
    let g = cairo::Context::new(&image);
972
    g.set_antialias(cairo::Antialias::Best);
973 974
    let hpos: f64 = (40.0 - (pb.get_height()) as f64) / 2.0;
    g.set_source_pixbuf(&pb, 0.0, hpos);
975

976
    g.arc(20.0, 20.0, 20.0, 0.0, 2.0 * PI);
977 978 979 980 981 982 983 984 985
    g.clip();

    g.paint();

    let mut buffer = File::create(&fname)?;
    image.write_to_png(&mut buffer)?;

    Ok(fname)
}
986 987

pub fn cache_path(name: &str) -> Result<String, Error> {
988 989 990 991 992 993
    let mut path = match glib::get_user_cache_dir() {
        Some(path) => path,
        None => PathBuf::from("/tmp"),
    };

    path.push("fractal");
994

995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
    if !path.exists() {
        create_dir_all(&path)?;
    }

    path.push(name);

    Ok(path.into_os_string().into_string()?)
}

pub fn cache_dir_path(dir: &str, name: &str) -> Result<String, Error> {
    let mut path = match glib::get_user_cache_dir() {
        Some(path) => path,
        None => PathBuf::from("/tmp"),
    };

    path.push("fractal");
    path.push(dir);

1013 1014 1015 1016
    if !path.exists() {
        create_dir_all(&path)?;
    }

1017
    path.push(name);
1018

1019
    Ok(path.into_os_string().into_string()?)
1020
}
1021 1022 1023 1024 1025 1026 1027 1028

pub fn get_user_avatar_img(baseu: &Url, userid: String, alias: String, avatar: String) -> Result<String, Error> {
    if avatar.is_empty() {
        return identicon!(&userid, alias);
    }

    let dest = cache_path(&userid)?;
    let img = dw_media(baseu, &avatar, true, Some(&dest), 64, 64)?;
1029
    Ok(img)
1030 1031 1032 1033 1034 1035 1036
}

pub fn parse_room_member(msg: &JsonValue) -> Option<Member> {
    let sender = msg["sender"].as_str().unwrap_or("");

    let c = &msg["content"];

1037 1038 1039 1040 1041
    let membership = c["membership"].as_str();
    if membership.is_none() || membership.unwrap() != "join" {
        return None;
    }

1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
    let displayname = match c["displayname"].as_str() {
        None => None,
        Some(s) => Some(strn!(s))
    };
    let avatar_url = match c["avatar_url"].as_str() {
        None => None,
        Some(s) => Some(strn!(s))
    };

    Some(Member {
        uid: strn!(sender),
        alias: displayname,
        avatar: avatar_url,
    })
}