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
#[macro_use]
extern crate quick_error;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use std::env;
use std::fs::File;
use std::path::{Path, PathBuf};

#[cfg(not(target_os = "windows"))]
fn get_config_path() -> Option<PathBuf> {
    let home = match env::home_dir() {
        Some(path) => path,
        None => return None,
    };
    let path = Path::new(&home).join(".dropbox/info.json");
    if path.is_file() {
        Some(path)
    } else {
        None
    }
}

#[cfg(target_os = "windows")]
fn get_config_path() -> Option<PathBuf> {
    const CFG_PATH_SUFFIX: &'static str = "Dropbox/info.json";
    let mut appdata = String::new();
    let mut localappdata = String::new();
    for (key, value) in env::vars() {
        if key == "APPDATA" {
            appdata = value;
        } else if key == "LOCALAPPDATA" {
            localappdata = value;
        }
        if appdata != "" && localappdata != "" {
            break;
        }
    }

    let roamingpath = Path::new(&appdata).join(CFG_PATH_SUFFIX);
    let localpath = Path::new(&localappdata).join(CFG_PATH_SUFFIX);
    if roamingpath.is_file() {
        Some(roamingpath)
    } else if localpath.is_file() {
        Some(localpath)
    } else {
        None
    }
}

#[derive(Debug, Deserialize)]
struct Account {
    path: String,
    host: i64,
    is_team: bool,
    subscription_type: String,
}

#[derive(Debug, Deserialize)]
pub struct Info {
    personal: Option<Account>,
    business: Option<Account>,
}

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        NotConfiguredError {
            description("Dropbox not configured")
        }
        CantReadConfigError{
            from(std::io::Error)
            description("can't read configuration")
        }
        InvalidConfigError{
            from(serde_json::Error)
            description("configuration invalid")
        }
        AccountNotConfiguredError {
            description("account type not configured")
        }
    }
}

/// Reads info from Dropbox configuration files.
pub fn read_info() -> Result<Info, Error> {
    let cfg_path = match get_config_path() {
        Some(path) => path,
        None => return Err(Error::NotConfiguredError),
    };
    let file = File::open(cfg_path)?;
    let info: Info = serde_json::from_reader(file).unwrap();
    Ok(info)
}

fn get_dir(account_type: &str) -> Result<String, Error> {
    let info = read_info()?;
    let data = match account_type {
        "personal" => info.personal,
        "business" => info.business,
        _ => unreachable!(),
    };
    if let Some(account) = data {
        Ok(account.path.clone())
    } else {
        Err(Error::AccountNotConfiguredError)
    }
}

/// Gets the personal directory path.
pub fn personal_dir() -> Result<String, Error> {
    get_dir("personal")
}

/// Gets the business directory path.
pub fn business_dir() -> Result<String, Error> {
    get_dir("business")
}

pub struct SmartPath {
    local_root: PathBuf,
    target: PathBuf,
}

impl SmartPath {
    fn new<S>(root: S, path: S) -> SmartPath
    where
        S: Into<String>,
    {
        let raw_target = PathBuf::from(path.into());
        let target: PathBuf = if raw_target.starts_with("/") {
            raw_target.strip_prefix("/").unwrap().into()
        } else {
            raw_target.clone()
        };
        SmartPath {
            local_root: PathBuf::from(root.into()),
            target: target,
        }
    }

    /// Creates a new `SmartPath`, the `path` parameter represent a target inside the Dropbox
    /// directory (personal).
    pub fn new_personal<S>(path: S) -> Result<SmartPath, Error>
    where
        S: Into<String>,
    {
        let root = personal_dir()?;
        Ok(SmartPath::new(root, path.into()))
    }

    /// Creates a new `SmartPath`, the `path` parameter represent a target inside the Dropbox
    /// directory (business).
    pub fn new_business<S>(path: S) -> Result<SmartPath, Error>
    where
        S: Into<String>,
    {
        let root = business_dir()?;
        Ok(SmartPath::new(root, path.into()))
    }

    /// Gets the local absolute path of the target.
    pub fn local(&self) -> PathBuf {
        if self.target.components().count() > 0 {
            self.local_root.join(&self.target)
        } else {
            self.local_root.clone()
        }
    }

    /// Gets the remote path of the target.
    pub fn remote(&self) -> PathBuf {
        PathBuf::from("/").join(&self.target)
    }
}