5
5
//! filesystem. This module is adding files into database and retrieving them.
6
6
7
7
8
- use std:: path:: Path ;
8
+ use std:: path:: { PathBuf , Path } ;
9
9
use postgres:: Connection ;
10
10
use rustc_serialize:: json:: { Json , ToJson } ;
11
- use std:: fs:: File ;
11
+ use std:: fs;
12
12
use std:: io:: Read ;
13
13
use error:: Result ;
14
14
use failure:: err_msg;
15
-
16
-
17
- fn file_path ( prefix : & str , name : & str ) -> String {
18
- match prefix. is_empty ( ) {
19
- true => name. to_owned ( ) ,
20
- false => format ! ( "{}/{}" , prefix, name) ,
21
- }
22
- }
15
+ use rusoto_s3:: { S3 , PutObjectRequest , GetObjectRequest , S3Client } ;
16
+ use rusoto_core:: region:: Region ;
17
+ use rusoto_credential:: EnvironmentProvider ;
23
18
24
19
25
20
fn get_file_list_from_dir < P : AsRef < Path > > ( path : P ,
26
- prefix : & str ,
27
- files : & mut Vec < String > )
21
+ files : & mut Vec < PathBuf > )
28
22
-> Result < ( ) > {
29
23
let path = path. as_ref ( ) ;
30
24
31
25
for file in try!( path. read_dir ( ) ) {
32
26
let file = try!( file) ;
33
27
34
28
if try!( file. file_type ( ) ) . is_file ( ) {
35
- file . file_name ( ) . to_str ( ) . map ( |name| files. push ( file_path ( prefix , name ) ) ) ;
29
+ files. push ( file . path ( ) ) ;
36
30
} else if try!( file. file_type ( ) ) . is_dir ( ) {
37
- file. file_name ( )
38
- . to_str ( )
39
- . map ( |name| get_file_list_from_dir ( file. path ( ) , & file_path ( prefix, name) , files) ) ;
31
+ try!( get_file_list_from_dir ( file. path ( ) , files) ) ;
40
32
}
41
33
}
42
34
43
35
Ok ( ( ) )
44
36
}
45
37
46
38
47
- pub fn get_file_list < P : AsRef < Path > > ( path : P ) -> Result < Vec < String > > {
39
+ pub fn get_file_list < P : AsRef < Path > > ( path : P ) -> Result < Vec < PathBuf > > {
48
40
let path = path. as_ref ( ) ;
49
- let mut files: Vec < String > = Vec :: new ( ) ;
41
+ let mut files = Vec :: new ( ) ;
50
42
51
43
if !path. exists ( ) {
52
44
return Err ( err_msg ( "File not found" ) ) ;
53
45
} else if path. is_file ( ) {
54
- path. file_name ( )
55
- . and_then ( |name| name. to_str ( ) )
56
- . map ( |name| files. push ( format ! ( "{}" , name) ) ) ;
46
+ files. push ( PathBuf :: from ( path. file_name ( ) . unwrap ( ) ) ) ;
57
47
} else if path. is_dir ( ) {
58
- try!( get_file_list_from_dir ( path, "" , & mut files) ) ;
48
+ try!( get_file_list_from_dir ( path, & mut files) ) ;
49
+ for file_path in & mut files {
50
+ // We want the paths in this list to not be {path}/bar.txt but just bar.txt
51
+ * file_path = PathBuf :: from ( file_path. strip_prefix ( path) . unwrap ( ) ) ;
52
+ }
59
53
}
60
54
61
55
Ok ( files)
62
56
}
63
57
58
+ pub struct Blob {
59
+ pub path : String ,
60
+ pub mime : String ,
61
+ pub date_updated : time:: Timespec ,
62
+ pub content : Vec < u8 > ,
63
+ }
64
+
65
+ pub fn get_path ( conn : & Connection , path : & str ) -> Option < Blob > {
66
+ let rows = conn. query ( "SELECT path, mime, date_updated, content
67
+ FROM files
68
+ WHERE path = $1" , & [ & path] ) . unwrap ( ) ;
69
+
70
+ if rows. len ( ) == 0 {
71
+ None
72
+ } else {
73
+ let row = rows. get ( 0 ) ;
74
+ let mut content = row. get ( 3 ) ;
75
+ if content == b"in-s3" {
76
+ let client = s3_client ( ) ;
77
+ content = client. and_then ( |c| c. get_object ( GetObjectRequest {
78
+ bucket : "rust-docs-rs" . into ( ) ,
79
+ key : path. into ( ) ,
80
+ ..Default :: default ( )
81
+ } ) . sync ( ) . ok ( ) ) . and_then ( |r| r. body ) . map ( |b| {
82
+ let mut b = b. into_blocking_read ( ) ;
83
+ let mut content = Vec :: new ( ) ;
84
+ b. read_to_end ( & mut content) . unwrap ( ) ;
85
+ content
86
+ } ) . unwrap ( ) ;
87
+ } ;
88
+
89
+ Some ( Blob {
90
+ path : row. get ( 0 ) ,
91
+ mime : row. get ( 1 ) ,
92
+ date_updated : row. get ( 2 ) ,
93
+ content,
94
+ } )
95
+ }
96
+ }
97
+
98
+ fn s3_client ( ) -> Option < S3Client > {
99
+ // If AWS keys aren't configured, then presume we should use the DB exclusively
100
+ // for file storage.
101
+ if std:: env:: var_os ( "AWS_ACCESS_KEY_ID" ) . is_none ( ) {
102
+ return None ;
103
+ }
104
+ Some ( S3Client :: new_with (
105
+ rusoto_core:: request:: HttpClient :: new ( ) . unwrap ( ) ,
106
+ EnvironmentProvider :: default ( ) ,
107
+ std:: env:: var ( "S3_ENDPOINT" ) . ok ( ) . map ( |e| Region :: Custom {
108
+ name : "us-west-1" . to_owned ( ) ,
109
+ endpoint : e,
110
+ } ) . unwrap_or ( Region :: UsWest1 ) ,
111
+ ) )
112
+ }
64
113
65
114
/// Adds files into database and returns list of files with their mime type in Json
66
115
pub fn add_path_into_database < P : AsRef < Path > > ( conn : & Connection ,
@@ -72,30 +121,34 @@ pub fn add_path_into_database<P: AsRef<Path>>(conn: &Connection,
72
121
try!( cookie. load :: < & str > ( & [ ] ) ) ;
73
122
74
123
let trans = try!( conn. transaction ( ) ) ;
124
+ let client = s3_client ( ) ;
125
+ let mut file_list_with_mimes: Vec < ( String , PathBuf ) > = Vec :: new ( ) ;
75
126
76
- let mut file_list_with_mimes: Vec < ( String , String ) > = Vec :: new ( ) ;
77
-
78
- for file_path_str in try!( get_file_list ( & path) ) {
127
+ for file_path in try!( get_file_list ( & path) ) {
79
128
let ( path, content, mime) = {
80
- let path = Path :: new ( path. as_ref ( ) ) . join ( & file_path_str ) ;
129
+ let path = Path :: new ( path. as_ref ( ) ) . join ( & file_path ) ;
81
130
// Some files have insufficient permissions (like .lock file created by cargo in
82
131
// documentation directory). We are skipping this files.
83
- let mut file = match File :: open ( path) {
132
+ let mut file = match fs :: File :: open ( path) {
84
133
Ok ( f) => f,
85
134
Err ( _) => continue ,
86
135
} ;
87
136
let mut content: Vec < u8 > = Vec :: new ( ) ;
88
137
try!( file. read_to_end ( & mut content) ) ;
138
+ let bucket_path = Path :: new ( prefix) . join ( & file_path)
139
+ . into_os_string ( ) . into_string ( ) . unwrap ( ) ;
140
+
89
141
let mime = {
90
142
let mime = try!( cookie. buffer ( & content) ) ;
91
143
// css's are causing some problem in browsers
92
144
// magic will return text/plain for css file types
93
145
// convert them to text/css
94
146
// do the same for javascript files
95
147
if mime == "text/plain" {
96
- if file_path_str. ends_with ( ".css" ) {
148
+ let e = file_path. extension ( ) . unwrap_or_default ( ) ;
149
+ if e == "css" {
97
150
"text/css" . to_owned ( )
98
- } else if file_path_str . ends_with ( ". js") {
151
+ } else if e == " js" {
99
152
"application/javascript" . to_owned ( )
100
153
} else {
101
154
mime. to_owned ( )
@@ -105,14 +158,42 @@ pub fn add_path_into_database<P: AsRef<Path>>(conn: &Connection,
105
158
}
106
159
} ;
107
160
108
- file_list_with_mimes. push ( ( mime. clone ( ) , file_path_str. clone ( ) ) ) ;
161
+ let content: Option < Vec < u8 > > = if let Some ( client) = & client {
162
+ let s3_res = client. put_object ( PutObjectRequest {
163
+ acl : Some ( "public-read" . into ( ) ) ,
164
+ bucket : "rust-docs-rs" . into ( ) ,
165
+ key : bucket_path. clone ( ) ,
166
+ body : Some ( content. clone ( ) . into ( ) ) ,
167
+ content_type : Some ( mime. clone ( ) ) ,
168
+ ..Default :: default ( )
169
+ } ) . sync ( ) ;
170
+ match s3_res {
171
+ // we've successfully uploaded the content, so steal it;
172
+ // we don't want to put it in the DB
173
+ Ok ( _) => None ,
174
+ // Since s3 was configured, we want to panic on failure to upload.
175
+ Err ( e) => {
176
+ panic ! ( "failed to upload to {}: {:?}" , bucket_path, e)
177
+ } ,
178
+ }
179
+ } else {
180
+ Some ( content. clone ( ) . into ( ) )
181
+ } ;
182
+
183
+ file_list_with_mimes. push ( ( mime. clone ( ) , file_path. clone ( ) ) ) ;
109
184
110
- ( file_path ( prefix, & file_path_str) , content, mime)
185
+ (
186
+ bucket_path,
187
+ content,
188
+ mime,
189
+ )
111
190
} ;
112
191
113
192
// check if file already exists in database
114
193
let rows = try!( conn. query ( "SELECT COUNT(*) FROM files WHERE path = $1" , & [ & path] ) ) ;
115
194
195
+ let content = content. unwrap_or_else ( || "in-s3" . to_owned ( ) . into ( ) ) ;
196
+
116
197
if rows. get ( 0 ) . get :: < usize , i64 > ( 0 ) == 0 {
117
198
try!( trans. query ( "INSERT INTO files (path, mime, content) VALUES ($1, $2, $3)" ,
118
199
& [ & path, & mime, & content] ) ) ;
@@ -130,14 +211,14 @@ pub fn add_path_into_database<P: AsRef<Path>>(conn: &Connection,
130
211
131
212
132
213
133
- fn file_list_to_json ( file_list : Vec < ( String , String ) > ) -> Result < Json > {
214
+ fn file_list_to_json ( file_list : Vec < ( String , PathBuf ) > ) -> Result < Json > {
134
215
135
216
let mut file_list_json: Vec < Json > = Vec :: new ( ) ;
136
217
137
218
for file in file_list {
138
219
let mut v: Vec < String > = Vec :: new ( ) ;
139
220
v. push ( file. 0 . clone ( ) ) ;
140
- v. push ( file. 1 . clone ( ) ) ;
221
+ v. push ( file. 1 . into_os_string ( ) . into_string ( ) . unwrap ( ) ) ;
141
222
file_list_json. push ( v. to_json ( ) ) ;
142
223
}
143
224
@@ -150,8 +231,7 @@ fn file_list_to_json(file_list: Vec<(String, String)>) -> Result<Json> {
150
231
mod test {
151
232
extern crate env_logger;
152
233
use std:: env;
153
- use super :: { get_file_list, add_path_into_database} ;
154
- use super :: super :: connect_db;
234
+ use super :: get_file_list;
155
235
156
236
#[ test]
157
237
fn test_get_file_list ( ) {
@@ -162,16 +242,6 @@ mod test {
162
242
assert ! ( files. unwrap( ) . len( ) > 0 ) ;
163
243
164
244
let files = get_file_list ( env:: current_dir ( ) . unwrap ( ) . join ( "Cargo.toml" ) ) . unwrap ( ) ;
165
- assert_eq ! ( files[ 0 ] , "Cargo.toml" ) ;
166
- }
167
-
168
- #[ test]
169
- #[ ignore]
170
- fn test_add_path_into_database ( ) {
171
- let _ = env_logger:: try_init ( ) ;
172
-
173
- let conn = connect_db ( ) . unwrap ( ) ;
174
- let res = add_path_into_database ( & conn, "example" , env:: current_dir ( ) . unwrap ( ) . join ( "src" ) ) ;
175
- assert ! ( res. is_ok( ) ) ;
245
+ assert_eq ! ( files[ 0 ] , std:: path:: Path :: new( "Cargo.toml" ) ) ;
176
246
}
177
247
}
0 commit comments