1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 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
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/**
 * Database layer.
 */
#[cfg(test)]
use mocktopus::macros::*;


/**
 * Handles app MySql integation
 */
#[cfg_attr(test, mockable)]
pub mod db {
    use std::sync::Mutex;
    use lazy_static::lazy_static;
    use crate::model::*;
    use crate::environment;

    /**
     * Database connection pool structure
     */
    pub struct Dbconn {
        conn: mysql::Pool,
    }

    impl Dbconn {
        /**
         * Creates a auto reconnecting pool
         */
        pub fn get_conn(&self) -> mysql::PooledConn {
            self.conn.get_conn().unwrap()   
        } 
        pub fn new() -> Dbconn {
            let environment = environment::db_environment_values();
            let url = format!("mysql://{}:{}@{}/{}", environment.user, environment.pass, environment.host, environment.db_name);
            let pool = mysql::Pool::new_manual(1, 1, url).expect("error creating pool");
            Dbconn {
                conn: pool
            }
        }
    }
    
    //lazy initialized singleton reference to the connection.
    lazy_static! { static ref DB: Mutex<Dbconn> = Mutex::new(Dbconn::new()) ;}

    /**
     * Sets the location of a tracker to null of exists by id
     * 
     * # Arguments
     * `tracker_id` - a String representing a tracker id
     */
    pub fn unregister_tracker(tracker_id: &String) -> Result<(), String> {
        match DB.lock().unwrap().get_conn().prep_exec("update rfid_tracker set location = null where id = ?", vec![tracker_id]) {
            Ok(_) => Ok(()),
            Err(e) => e.print_err_get_mess::<()>()
        }
    }

    /**
     * Sets the location of a tracker by id
     * 
     * # Arguments
     * `receiver_id` - a String representing a receiver id
     * `tracker_id` - a String representing a tracker id
     */
    pub fn register_tracker_to_receiver(receiver_id: &String, tracker_id: &String) -> Result<(), String> {
        let db_receiver = match get_receiver_by_id(receiver_id) {
            Ok(Some(val)) => val,
            Ok(None) => return Ok(()),
            Err(e) => return e.print_err_get_mess()
        };
        match DB.lock().unwrap().get_conn().prep_exec("update rfid_tracker set location = ? where id = ?", (db_receiver.location, tracker_id)) {
            Ok(_) => Ok(()),
            Err(e) =>  e.print_err_get_mess()
        }
    }

    /**
     * Returns an Tracker if exists by id
     * 
     * # Arguments
     * `tracker_id` - an String representing a tracker id
     */
    pub fn get_tracker_by_id(tracker_id: &String) -> Result<Option<Tracker>, String> {
        match DB.lock().unwrap().get_conn().first_exec(
            "select id, location from rfid_tracker where id = ?", (tracker_id,)) {
                Ok(Some((id, location))) => Ok(Some(Tracker{id, location})),
                Ok(None) => Ok(None),
                Err(e) => e.print_err_get_mess()
        }
    }

    /**
     * Returns a Receiver if exists by id
     * 
     * # Arguments
     * `receiver_id` - a String representing a receiver id
     */
    pub fn get_receiver_by_id(receiver_id: &String) -> Result<Option<Receiver>, String> {
        match DB.lock().unwrap().get_conn().first_exec(
            "select id, location from rfid_receiver where id = ?", (receiver_id,)) {
                Ok(Some((id, location))) => Ok(Some(Receiver{id, location})),
                Ok(None) => Ok(None),
                Err(e) => e.print_err_get_mess()
        }
    }

     /**
     * Returns the location if exists of the display by id
     * 
     * # Arguments
     * `display_id` - an i32 representing display id
     */
    pub fn get_display_location(display_id: i32) -> Option<i32> {
        match DB.lock().unwrap().get_conn().first_exec(
        "select location from display where id = ?", (display_id,)) {
            Ok(val) => val,
            Err(e) => {println!("{}", e); return None}
        }
    }

    /**
     * Returns Display if exists by id
     * 
     * # Arguments
     * `display_id` - an i32 representing a display id
     */
    pub fn get_display_by_id(display_id: i32) ->  Result<Option<Display>, String> {
        match DB.lock().unwrap().get_conn().first_exec(
            "select id, location from display where id = ?", (display_id,)) {
                Ok(Some((id, location))) => Ok(Some(Display{id, location})),
                Err(e) => e.print_err_get_mess(),
                _ => Ok(None)
            }
    }

    /**
     * Returns the aggregated weight of all the interests for trackers in this location
     * and turns into a reverse weight sorted tuple of (interest, weight).
     * 
     * # Arguments
     * `location` - an i32 representing a physical location  
     */
    pub fn get_interests_at_location(location: i32) -> Result<Option<Vec<(i32, f32)>>, String> {

        let selected_p: Result<Vec<(i32, f32)>, mysql::error::Error> =  DB.lock().unwrap().get_conn().prep_exec(
            "select interest, sum(weight) as weight from rfid_tracker, tracker_interest where 
            location = ? and tracker = id
            group by interest
            order by weight desc;", (location,)).map(|result| {
               result.map(|x| x.unwrap()).map(|row| {
               let (i, w) = mysql::from_row(row);
               (i, w)
                }).collect()
            });
            match selected_p {
                Err(e) => e.print_err_get_mess(),
                _ => {let res = selected_p.unwrap(); 
                    match res.len() {
                        0 => Ok(None),
                        _ => Ok(Some(res))
                    }
                }
            }
    }

    /**
     * Returns an AdvertVideo if exists
     * 
     * # Arguments
     * `video_id` - an i32 representing a video id
     */
    pub fn get_advertisement_video_by_id(video_id: i32) -> Result<Option<AdvertVideo>, String> {
        match DB.lock().unwrap().get_conn().first_exec("SELECT interest, url, length_sec
        FROM advertisement_video where id = ?", (video_id,)) {
            Ok(Some((interest, url, length_sec))) => Ok(Some(AdvertVideo{interest, url, length_sec})),
            Err(e) => e.print_err_get_mess(),
            _ => Ok(None)
        }
    }

    /**
     * Returns an Order if exists
     * 
     * # Arguments
     * `order_id` - an String representing an order id
     */
    pub fn get_order_by_id(order_id: &String) -> Result<Option<Order>, String> {
        match DB.lock().unwrap().get_conn().first_exec("SELECT id, credits, user
        FROM orders where id = ?", (order_id,)) {
            Ok(Some((id, credits, user))) => Ok(Some(Order{id, credits, user})),
            Err(e) => e.print_err_get_mess(),
            _ => Ok(None)
        }
    }

     /**
     * Inserts a played_video row in the database
     * 
     * # Arguments
     * `video_id` - an i32 representing a video id
     * `time_epoch` - time of play in epoch seconds
     * `order_id` - a String representing an order id
     */
    pub fn insert_played_video(video_id: i32, time_epoch: u64, order_id: &String) -> Result<(), String> {
        match DB.lock().unwrap().get_conn().prep_exec("INSERT INTO played_video (video, time_epoch, `order`) values(?, ?, ?)", (video_id, time_epoch, order_id)) {
            Ok(_) => Ok(()),
            Err(e) => e.print_err_get_mess::<()>()
        }
    } 

    /**
     * Decrements the credit field for orders row matching id
     * 
     * # Arguments
     * `order_id` - id of the order to decrement
     * `credits` - i32 amount of credits to withdraw  
     */
    pub fn draw_credits_for_order(order_id: &String, credits: i32) -> Result<(), String>{
        match DB.lock().unwrap().get_conn().prep_exec("UPDATE orders set credits = credits - ? where id = ?", (credits, order_id)) {
            Ok(_) => Ok(()),
            Err(e) => e.print_err_get_mess::<()>()
        }
    } 

    /**
     * Returns all elligible videos for the interests contained in the Vec<i32> interests with interest_id's.
     * Only returns videos that are payed for, and matches one of the interests given
     * 
     * # Arguments
     * `interests` - A vector of integers representing interests
     */
    pub fn find_eligible_videos_by_interest(interests: Vec<i32>) ->  Result<Option<Vec<AdvertVideoOrder>>, String> {
        let q_marks = &interests.iter().fold(String::from(""), |a, _b| format!("{}, ?", a))[1..];
        let prep_q = format!(
            "SELECT  advertisement_order.video as video_id, interest, url, length_sec, orders FROM advertisement_video, advertisement_order, orders
            where interest in ({})
            and advertisement_order.video = advertisement_video.id
            and advertisement_order.orders = orders.id
            and orders.credits > 0", q_marks);
        println!("{}", prep_q);

        let selected_p: Result<Vec<AdvertVideoOrder>, mysql::error::Error> =  DB.lock().unwrap().get_conn().prep_exec(
            prep_q, interests).map(|result| {
               result.map(|x| x.unwrap()).map(|row| {
               let (video_id, interest, url, length_sec, order) = mysql::from_row(row);
               AdvertVideoOrder{video_id, interest, url, length_sec, order}
                }).collect()
            });
        match selected_p {
            Err(e) => e.print_err_get_mess(),
            _ => {let res = selected_p.unwrap(); 
                match res.len() {
                    0 => Ok(None),
                    _ => Ok(Some(res))
                }
            }
        }
    }

    trait PrintErr {
        fn print_err_get_mess<T>(&self) -> Result<T, String>;
    }

    impl PrintErr for mysql::error::Error {   
        fn print_err_get_mess<T>(&self) -> Result<T, String> {
            eprintln!("ERROR: {}", &self);
            panic!("{}", &self);
            //Err(format!("{}", &self))
        }
    }

    impl PrintErr for String {
        fn print_err_get_mess<T>(&self) -> Result<T, String> {
            eprintln!("ERROR: {}", &self);
            panic!("{}", &self);
            //Err(format!("{}", &self))
        }
    }
}