Skip to content

Commit 7df140d

Browse files
bors-voyager[bot]PinkFloydedcarols10cents
committed
Merge #1436
1436: Add crate size on the crate detail page r=carols10cents a=PinkFloyded From #1393 Currently it looks like this: <img width="955" alt="crate_size_ui" src="https://user-images.githubusercontent.com/11929269/41512798-c7f2dede-72ad-11e8-98b4-aa4d29750678.png"> I'm new to Rust and Ember. So, let me know if something can be done better. Co-authored-by: PinkFloyded <[email protected]> Co-authored-by: Carol (Nichols || Goulding) <[email protected]>
2 parents c83866f + 4eedffd commit 7df140d

File tree

22 files changed

+267
-76
lines changed

22 files changed

+267
-76
lines changed

app/helpers/format-crate-size.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { helper } from '@ember/component/helper';
2+
3+
export function formatCrateSize(sizeInBytes) {
4+
if (sizeInBytes < 100000) {
5+
return +(sizeInBytes / 1000).toFixed(2) + ' kB';
6+
} else {
7+
return +(sizeInBytes / 1000000).toFixed(2) + ' MB';
8+
}
9+
}
10+
11+
export default helper(formatCrateSize);

app/models/version.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ export default DS.Model.extend({
2121
crateName: computed('crate', function() {
2222
return this.belongsTo('crate').id();
2323
}),
24+
crate_size: DS.attr('number'),
2425
});

app/styles/crate.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,25 @@
351351
font-weight: bold;
352352
margin-bottom: 40px;
353353
}
354+
355+
/*
356+
Since crate_size is a new field, older crates won't have it.
357+
Preserve behaviour for older crates. For newer ones, keep
358+
`Crate Size` closer to last updated.
359+
*/
360+
.date-with-small-margin-bot {
361+
font-weight: bold;
362+
margin-bottom: 20px;
363+
}
364+
.crate-size {
365+
color: $main-color-light;
366+
font-size: 90%;
367+
line-height: 25px;
368+
}
369+
.size {
370+
font-weight: bold;
371+
margin-bottom: 40px;
372+
}
354373
}
355374

356375
#crate-downloads {

app/templates/crate/version.hbs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,21 @@
104104
<div class='top'>
105105
<div>
106106
<div class='last-update'><span class='small'>Last Updated</span></div>
107-
<div class='date'>{{moment-from-now crate.updated_at}}</div>
107+
<div class='{{if currentVersion.crate_size 'date-with-small-margin-bot' 'date'}}'>{{moment-from-now crate.updated_at}}</div>
108108
{{#each crate.annotated_badges as |badge|}}
109109
<p>
110110
{{component badge.component_name badge=badge}}
111111
</p>
112112
{{/each}}
113113
</div>
114114

115+
{{#if currentVersion.crate_size}}
116+
<div>
117+
<div class='crate-size'><span class='small'>Crate Size</span></div>
118+
<div class='size'>{{format-crate-size currentVersion.crate_size}}</div>
119+
</div>
120+
{{/if}}
121+
115122
<div class="authors">
116123
<h3>Authors</h3>
117124
<ul>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE versions
2+
DROP COLUMN crate_size;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE versions
2+
ADD COLUMN crate_size integer;

mirage/factories/version.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ export default Factory.extend({
2727
let crate = server.schema.crates.find(version.crate);
2828
crate.update({ versions: crate.versions.concat(parseInt(version.id, 10)) });
2929
},
30+
crate_size: () => faker.random.number({ max: 10000000 }),
3031
});

mirage/fixtures/versions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default [
1313
updated_at: '2016-12-27T08:40:00Z',
1414
yanked: false,
1515
license: 'MIT',
16+
crate_size: 912355,
1617
_authors: [
1718
'Daniel Fagnan <[email protected]>',
1819
'Jason E. Aten',
@@ -34,6 +35,7 @@ export default [
3435
},
3536
id: 40905,
3637
num: '0.6.1',
38+
crate_size: 8123545,
3739
updated_at: '2016-12-27T08:40:00Z',
3840
yanked: false,
3941
license: 'Apache-2.0',

mirage/serializers/version.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default BaseSerializer.extend({
1313
'updated_at',
1414
'yanked',
1515
'license',
16+
'crate_size',
1617
],
1718

1819
links(version) {

src/bin/update-downloads.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ mod test {
145145
&HashMap::new(),
146146
None,
147147
None,
148+
None,
148149
).unwrap();
149150
let version = version.save(&conn, &[]).unwrap();
150151
(krate, version)

src/controllers/krate/publish.rs

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Functionality related to publishing a new crate or version of a crate.
22
3-
use std::cmp;
43
use std::collections::HashMap;
54
use std::sync::Arc;
65

@@ -9,7 +8,7 @@ use serde_json;
98

109
use git;
1110
use render;
12-
use util::{internal, ChainError};
11+
use util::{internal, ChainError, Maximums};
1312
use util::{read_fill, read_le_u32};
1413

1514
use controllers::prelude::*;
@@ -26,6 +25,20 @@ use views::{EncodableCrate, EncodableCrateUpload};
2625
/// --status` command, via crates.io's front end, or email.
2726
pub fn publish(req: &mut dyn Request) -> CargoResult<Response> {
2827
let app = Arc::clone(req.app());
28+
29+
// The format of the req.body() of a publish request is as follows:
30+
//
31+
// metadata length
32+
// metadata in JSON about the crate being published
33+
// .crate tarball length
34+
// .crate tarball file
35+
//
36+
// - The metadata is read and interpreted in the parse_new_headers function.
37+
// - The .crate tarball length is read in this function in order to save the size of the file
38+
// in the version record in the database.
39+
// - Then the .crate tarball length is passed to the upload_crate function where the actual
40+
// file is read and uploaded.
41+
2942
let (new_crate, user) = parse_new_headers(req)?;
3043

3144
let name = &*new_crate.name;
@@ -88,23 +101,43 @@ pub fn publish(req: &mut dyn Request) -> CargoResult<Response> {
88101
)));
89102
}
90103

91-
let length = req
104+
// Length of the .crate tarball, which appears after the metadata in the request body.
105+
// TODO: Not sure why we're using the total content length (metadata + .crate file length)
106+
// to compare against the max upload size... investigate that and perhaps change to use
107+
// this file length.
108+
let file_length = read_le_u32(req.body())?;
109+
110+
let content_length = req
92111
.content_length()
93112
.chain_error(|| human("missing header: Content-Length"))?;
94-
let max = krate
95-
.max_upload_size
96-
.map(|m| m as u64)
97-
.unwrap_or(app.config.max_upload_size);
98-
if length > max {
99-
return Err(human(&format_args!("max upload size is: {}", max)));
113+
114+
let maximums = Maximums::new(
115+
krate.max_upload_size,
116+
app.config.max_upload_size,
117+
app.config.max_unpack_size,
118+
);
119+
120+
if content_length > maximums.max_upload_size {
121+
return Err(human(&format_args!(
122+
"max upload size is: {}",
123+
maximums.max_upload_size
124+
)));
100125
}
101126

102127
// This is only redundant for now. Eventually the duplication will be removed.
103128
let license = new_crate.license.clone();
104129

105130
// Persist the new version of this crate
106-
let version = NewVersion::new(krate.id, vers, &features, license, license_file)?
107-
.save(&conn, &new_crate.authors)?;
131+
let version = NewVersion::new(
132+
krate.id,
133+
vers,
134+
&features,
135+
license,
136+
license_file,
137+
// Downcast is okay because the file length must be less than the max upload size
138+
// to get here, and max upload sizes are way less than i32 max
139+
Some(file_length as i32),
140+
)?.save(&conn, &new_crate.authors)?;
108141

109142
// Link this new version to all dependencies
110143
let git_deps = dependency::add_dependencies(&conn, &new_crate.deps, version.id)?;
@@ -134,11 +167,10 @@ pub fn publish(req: &mut dyn Request) -> CargoResult<Response> {
134167
// Upload the crate, return way to delete the crate from the server
135168
// If the git commands fail below, we shouldn't keep the crate on the
136169
// server.
137-
let max_unpack = cmp::max(app.config.max_unpack_size, max);
138-
let (cksum, mut crate_bomb, mut readme_bomb) = app
139-
.config
140-
.uploader
141-
.upload_crate(req, &krate, readme, max, max_unpack, vers)?;
170+
let (cksum, mut crate_bomb, mut readme_bomb) =
171+
app.config
172+
.uploader
173+
.upload_crate(req, &krate, readme, file_length, maximums, vers)?;
142174
version.record_readme_rendering(&conn)?;
143175

144176
let mut hex_cksum = String::new();
@@ -195,12 +227,12 @@ pub fn publish(req: &mut dyn Request) -> CargoResult<Response> {
195227
/// information.
196228
fn parse_new_headers(req: &mut dyn Request) -> CargoResult<(EncodableCrateUpload, User)> {
197229
// Read the json upload request
198-
let amt = u64::from(read_le_u32(req.body())?);
230+
let metadata_length = u64::from(read_le_u32(req.body())?);
199231
let max = req.app().config.max_upload_size;
200-
if amt > max {
232+
if metadata_length > max {
201233
return Err(human(&format_args!("max upload size is: {}", max)));
202234
}
203-
let mut json = vec![0; amt as usize];
235+
let mut json = vec![0; metadata_length as usize];
204236
read_fill(req.body(), &mut json)?;
205237
let json = String::from_utf8(json).map_err(|_| human("json body was not valid utf-8"))?;
206238
let new: EncodableCrateUpload = serde_json::from_str(&json)

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#![deny(warnings)]
77
#![deny(missing_debug_implementations, missing_copy_implementations)]
88
#![deny(bare_trait_objects)]
9-
#![recursion_limit = "128"]
9+
#![recursion_limit = "256"]
1010
#![allow(unknown_lints, proc_macro_derive_resolution_fallback)] // This can be removed after diesel-1.4
1111

1212
extern crate ammonia;

src/models/version.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub struct Version {
2727
pub features: serde_json::Value,
2828
pub yanked: bool,
2929
pub license: Option<String>,
30+
pub crate_size: Option<i32>,
3031
}
3132

3233
#[derive(Insertable, Debug)]
@@ -36,6 +37,7 @@ pub struct NewVersion {
3637
num: String,
3738
features: serde_json::Value,
3839
license: Option<String>,
40+
crate_size: Option<i32>,
3941
}
4042

4143
impl Version {
@@ -49,6 +51,7 @@ impl Version {
4951
features,
5052
yanked,
5153
license,
54+
crate_size,
5255
..
5356
} = self;
5457
let num = num.to_string();
@@ -69,6 +72,7 @@ impl Version {
6972
version_downloads: format!("/api/v1/crates/{}/{}/downloads", crate_name, num),
7073
authors: format!("/api/v1/crates/{}/{}/authors", crate_name, num),
7174
},
75+
crate_size,
7276
}
7377
}
7478

@@ -117,6 +121,7 @@ impl NewVersion {
117121
features: &HashMap<String, Vec<String>>,
118122
license: Option<String>,
119123
license_file: Option<&str>,
124+
crate_size: Option<i32>,
120125
) -> CargoResult<Self> {
121126
let features = serde_json::to_value(features)?;
122127

@@ -125,6 +130,7 @@ impl NewVersion {
125130
num: num.to_string(),
126131
features,
127132
license,
133+
crate_size,
128134
};
129135

130136
new_version.validate_license(license_file)?;
@@ -200,6 +206,7 @@ impl Queryable<versions::SqlType, Pg> for Version {
200206
serde_json::Value,
201207
bool,
202208
Option<String>,
209+
Option<i32>,
203210
);
204211

205212
fn build(row: Self::Row) -> Self {
@@ -213,6 +220,7 @@ impl Queryable<versions::SqlType, Pg> for Version {
213220
features: row.6,
214221
yanked: row.7,
215222
license: row.8,
223+
crate_size: row.9,
216224
}
217225
}
218226
}

src/schema.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,12 @@ table! {
864864
///
865865
/// (Automatically generated by Diesel.)
866866
license -> Nullable<Varchar>,
867+
/// The `crate_size` column of the `versions` table.
868+
///
869+
/// Its SQL type is `Nullable<Int4>`.
870+
///
871+
/// (Automatically generated by Diesel.)
872+
crate_size -> Nullable<Int4>,
867873
}
868874
}
869875

src/tests/all.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use models::{Crate, CrateDownload, CrateOwner, Dependency, Keyword, Team, User,
4545
use models::{NewCategory, NewCrate, NewTeam, NewUser, NewVersion};
4646
use schema::*;
4747
use views::krate_publish as u;
48-
use views::EncodableCrate;
48+
use views::{EncodableCrate, EncodableKeyword, EncodableVersion};
4949

5050
macro_rules! t {
5151
($e:expr) => {
@@ -125,6 +125,13 @@ struct Warnings {
125125
struct CrateMeta {
126126
total: i32,
127127
}
128+
#[derive(Deserialize)]
129+
struct CrateResponse {
130+
#[serde(rename = "crate")]
131+
krate: EncodableCrate,
132+
versions: Vec<EncodableVersion>,
133+
keywords: Vec<EncodableKeyword>,
134+
}
128135

129136
fn app() -> (
130137
record::Bomb,
@@ -160,7 +167,7 @@ fn app() -> (
160167
gh_client_secret: env::var("GH_CLIENT_SECRET").unwrap_or_default(),
161168
db_url: env("TEST_DATABASE_URL"),
162169
env: cargo_registry::Env::Test,
163-
max_upload_size: 1000,
170+
max_upload_size: 3000,
164171
max_unpack_size: 2000,
165172
mirror: Replica::Primary,
166173
api_protocol: api_protocol,
@@ -321,6 +328,7 @@ impl<'a> VersionBuilder<'a> {
321328
&self.features,
322329
license,
323330
self.license_file,
331+
None,
324332
)?.save(connection, &[])?;
325333

326334
if self.yanked {
@@ -493,9 +501,9 @@ impl<'a> CrateBuilder<'a> {
493501
}
494502
}
495503

496-
fn new_version(crate_id: i32, num: &str) -> NewVersion {
504+
fn new_version(crate_id: i32, num: &str, crate_size: Option<i32>) -> NewVersion {
497505
let num = semver::Version::parse(num).unwrap();
498-
NewVersion::new(crate_id, &num, &HashMap::new(), None, None).unwrap()
506+
NewVersion::new(crate_id, &num, &HashMap::new(), None, None, crate_size).unwrap()
499507
}
500508

501509
fn krate(name: &str) -> Crate {
@@ -516,6 +524,28 @@ fn krate(name: &str) -> Crate {
516524
}
517525
}
518526

527+
fn new_crate(name: &str, version: &str) -> u::NewCrate {
528+
u::NewCrate {
529+
name: u::CrateName(name.to_string()),
530+
vers: u::CrateVersion(semver::Version::parse(version).unwrap()),
531+
features: HashMap::new(),
532+
deps: Vec::new(),
533+
authors: vec!["foo".to_string()],
534+
description: Some("desc".to_string()),
535+
homepage: None,
536+
documentation: None,
537+
readme: None,
538+
readme_file: None,
539+
keywords: None,
540+
categories: None,
541+
license: Some("MIT".to_string()),
542+
license_file: None,
543+
repository: None,
544+
badges: None,
545+
links: None,
546+
}
547+
}
548+
519549
fn sign_in_as(req: &mut Request, user: &User) {
520550
req.mut_extensions().insert(user.clone());
521551
req.mut_extensions()

0 commit comments

Comments
 (0)