reuse_notifications/
lib.rs

1//! Allows to reuse notifications created by `notify-rust` , replacing the contents
2//! of an already existing notification, instead of creating a new one.
3//!
4//! # Examples
5//!
6//! ## Example for a single notification
7//!
8//! ```no_run
9//! use notify_rust::*;
10//! use reuse_notification::ReuseNotification;
11//!
12//! Notification::new()
13//!     .summary("Firefox News")
14//!     .body("This will almost look like a real firefox notification.")
15//!     .icon("firefox")
16//!     .timeout(Timeout::Milliseconds(6000))
17//!     .reuse() // <-- instead of `show()`
18//!     .unwrap();
19//! ```
20//!
21//! ## Example for different reusable notifications
22//!
23//! In order to overwrite a specific notification, provide a string to `.reuse()`.
24//!
25//! Future calls to `.reuse()` with the same string will replace the contents
26//! of the old notification with the new ones in the new notification instance.
27//!
28//! ```no_run
29//! use notify_rust::*;
30//! use reuse_notification::ReuseNotification;
31//!
32//! Notification::new()
33//!     .summary("Firefox News")
34//!     .body("This will almost look like a real firefox notification.")
35//!     .icon("firefox")
36//!     .timeout(Timeout::Milliseconds(6000))
37//!     .reuse("firefox_notification") // <-- instead of `show()`
38//!     .unwrap();
39//!
40//! Notification::new()
41//!     .summary("Other News")
42//!     .body("This will almost look like a real firefox notification.")
43//!     .icon("firefox")
44//!     .timeout(Timeout::Milliseconds(6000))
45//!     .reuse("other_notification") // <-- instead of `show()`
46//!     .unwrap();
47//!
48//! Notification::new()
49//!     .summary("Firefox News 2")
50//!     .body("This will reuse the previous 'firefox notification'.")
51//!     .icon("firefox")
52//!     .timeout(Timeout::Milliseconds(6000))
53//!     .reuse("firefox_notification") // <-- instead of `show()`
54//!     .unwrap();
55//! ```
56//!
57
58extern crate notify_rust;
59extern crate rand;
60
61use notify_rust::error::Result;
62use notify_rust::Notification;
63use notify_rust::NotificationHandle;
64use rand::Rng;
65use std::env::temp_dir;
66use std::fs::File;
67use std::io::prelude::*;
68use std::path::PathBuf;
69
70
71/// Allows to reuse notifications created via `notfy-rust`.
72pub trait ReuseNotification {
73    fn reuse(&mut self) -> Result<NotificationHandle>;
74
75    fn reuse_with(&mut self, identifier: &str) -> Result<NotificationHandle>;
76}
77
78impl ReuseNotification for Notification {
79    fn reuse(&mut self) -> Result<NotificationHandle> {
80        let internal_id = self.appname.clone();
81        ReuseNotification::display_reusing_id(self, &internal_id)
82    }
83
84    fn reuse_with(&mut self, identifier: &str) -> Result<NotificationHandle> {
85        let mut internal_id = self.appname.clone();
86        internal_id.push_str(identifier);
87        ReuseNotification::display_reusing_id(self, &internal_id)
88    }
89}
90
91impl ReuseNotification {
92    fn display_reusing_id(notification: &mut Notification, file_suffix: &String) -> Result<NotificationHandle> {
93        let temp_file = temp_file(file_suffix);
94        let stored_id = stored_id(&temp_file);
95        notification.id(stored_id)
96            .show()
97            .map(|notification_handle| {
98                // Sometimes the id set is ignored by the notification server.
99                // Let's overwrite the stored notification id if does not match the server's.
100                if notification_handle.id() != stored_id {
101                    store_new_notification_id(&temp_file, notification_handle.id());
102                }
103                notification_handle
104            })
105    }
106}
107
108fn temp_file(suffix: &String) -> PathBuf {
109    let mut file_name = "notification_id_".to_owned();
110    file_name.push_str(suffix);
111    let mut temp_file = temp_dir();
112    temp_file.push(file_name);
113    temp_file
114}
115
116fn stored_id(temp_file: &PathBuf) -> u32 {
117    if temp_file.exists() {
118        read_notification_id(temp_file)
119    } else {
120        store_new_notification_id(temp_file, random_id())
121    }
122}
123
124fn read_notification_id(temp_file: &PathBuf) -> u32 {
125    match File::open(temp_file) {
126        Ok(mut file) => {
127            let mut s = String::new();
128            match file.read_to_string(&mut s) {
129                Ok(_) => s.parse::<u32>().unwrap_or(fallback()),
130                Err(_) => fallback()
131            }
132        }
133        Err(_) => fallback()
134    }
135}
136
137fn store_new_notification_id(temp_file: &PathBuf, id: u32) -> u32 {
138    File::create(temp_file)
139        .and_then(|mut file| file.write_all(id.to_string().as_bytes()).map(|_| id))
140        .unwrap_or(fallback())
141}
142
143// If there are any problems while reading or writing the notification id to disk,
144// we'll use a fallback in order not to disrupt the rest of the application
145fn fallback() -> u32 {
146    random_id()
147}
148
149fn random_id() -> u32 {
150    let mut rng = rand::thread_rng();
151    rng.gen::<u32>()
152}