|
18 | 18 | use std::io::BufReader; |
19 | 19 | use std::path::Path; |
20 | 20 |
|
| 21 | +use async_compression::tokio::bufread::GzipDecoder; |
21 | 22 | use cfg_if::cfg_if; |
22 | 23 | use chrono::{DateTime, Utc}; |
23 | 24 | use futures::Stream; |
24 | 25 | use serde::de::DeserializeOwned; |
25 | 26 | use serde::Deserialize; |
| 27 | +use tokio::io::AsyncBufReadExt; |
26 | 28 | use tokio::io::AsyncRead; |
| 29 | +use tokio_stream::wrappers::LinesStream; |
27 | 30 | use tokio_stream::StreamExt; |
28 | 31 | use tokio_util::io::StreamReader; |
29 | 32 | use uuid::Uuid; |
30 | 33 |
|
31 | 34 | cfg_if! { |
32 | 35 | if #[cfg(not(feature = "bulk_caching"))] { |
33 | 36 | use bytes::Buf; |
| 37 | + use flate2::read::GzDecoder; |
34 | 38 | } |
35 | 39 | } |
36 | 40 |
|
37 | 41 | use crate::card::Card; |
38 | 42 | use crate::ruling::Ruling; |
39 | 43 | use crate::uri::Uri; |
40 | | -use crate::util::{streaming_deserializer, BULK_DATA_URL}; |
| 44 | +use crate::util::BULK_DATA_URL; |
| 45 | +use crate::Error; |
41 | 46 |
|
42 | 47 | /// Scryfall provides daily exports of our card data in bulk files. Each of |
43 | 48 | /// these files is represented as a bulk_data object via the API. URLs for files |
@@ -82,24 +87,14 @@ pub struct BulkDataFile<T> { |
82 | 87 | pub description: String, |
83 | 88 |
|
84 | 89 | /// The URI that hosts this bulk file for fetching. |
85 | | - pub download_uri: Uri<Vec<T>>, |
| 90 | + pub jsonl_download_uri: Uri<Vec<T>>, |
86 | 91 |
|
87 | 92 | /// The time when this file was last updated. |
88 | 93 | pub updated_at: DateTime<Utc>, |
89 | 94 |
|
90 | 95 | /// The size of this file in integer bytes. |
91 | 96 | pub compressed_size: Option<usize>, |
92 | 97 |
|
93 | | - /// The MIME type of this file. |
94 | | - pub content_type: String, |
95 | | - |
96 | | - /// The Content-Encoding encoding that will be used to transmit this file |
97 | | - /// when you download it. |
98 | | - pub content_encoding: String, |
99 | | - |
100 | | - /// The byte size of the bulk file. |
101 | | - pub size: usize, |
102 | | - |
103 | 98 | #[cfg(test)] |
104 | 99 | #[serde(rename = "object")] |
105 | 100 | _object: String, |
@@ -135,28 +130,29 @@ impl<T: DeserializeOwned> BulkDataFile<T> { |
135 | 130 |
|
136 | 131 | let file = tokio::fs::File::open(&cache_path).await?; |
137 | 132 |
|
138 | | - Ok(tokio::io::BufReader::new(file)) |
| 133 | + let raw_reader = tokio::io::BufReader::new(file); |
| 134 | + Ok(async_compression::tokio::bufread::GzipDecoder::new(raw_reader)) |
139 | 135 | } |
140 | 136 | } else { |
141 | 137 | async fn get_reader(&self) -> crate::Result<BufReader<impl std::io::Read + Send>> { |
142 | 138 |
|
143 | | - let response = self.download_uri.fetch_raw().await?; |
| 139 | + let response = self.jsonl_download_uri.fetch_raw().await?; |
144 | 140 | let body = response.bytes().await.map_err(|e| { |
145 | | - crate::Error::ReqwestError { error: Box::new(e), url: self.download_uri.inner().clone() } |
| 141 | + crate::Error::ReqwestError { error: Box::new(e), url: self.jsonl_download_uri.inner().clone() } |
146 | 142 | })?; |
147 | | - Ok(BufReader::new(body.reader())) |
| 143 | + Ok(BufReader::new(GzDecoder::new(body.reader()))) |
148 | 144 | } |
149 | 145 |
|
150 | 146 | async fn get_async_reader(&self) -> crate::Result<impl AsyncRead> { |
151 | | - let response = self.download_uri.fetch_raw().await?; |
| 147 | + let response = self.jsonl_download_uri.fetch_raw().await?; |
152 | 148 | let stream = response.bytes_stream() |
153 | 149 | .map(|bytes_result| { |
154 | 150 | bytes_result |
155 | | - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) |
| 151 | + .map_err(std::io::Error::other) |
156 | 152 | // .map(|bytes| bytes.to_vec()) |
157 | 153 | }); |
158 | 154 |
|
159 | | - Ok(StreamReader::new(stream)) |
| 155 | + Ok(GzipDecoder::new(StreamReader::new(stream))) |
160 | 156 | } |
161 | 157 | } |
162 | 158 | } |
@@ -190,14 +186,21 @@ impl<T: DeserializeOwned> BulkDataFile<T> { |
190 | 186 | T: Send + 'static, |
191 | 187 | { |
192 | 188 | let reader = self.get_async_reader().await?; |
193 | | - Ok(streaming_deserializer::create(reader)) |
| 189 | + |
| 190 | + Ok( |
| 191 | + LinesStream::new(tokio::io::BufReader::new(reader).lines()).map(|line_result| { |
| 192 | + line_result |
| 193 | + .map_err(Error::from) |
| 194 | + .and_then(|line| serde_json::from_str::<T>(&line).map_err(Error::from)) |
| 195 | + }), |
| 196 | + ) |
194 | 197 | } |
195 | 198 |
|
196 | 199 | /// Downloads this file, saving it to `path`. Overwrites the file if it |
197 | 200 | /// already exists. |
198 | 201 | pub async fn download(&self, path: impl AsRef<Path>) -> crate::Result<()> { |
199 | 202 | let path = path.as_ref(); |
200 | | - let response = self.download_uri.fetch_raw().await?; |
| 203 | + let response = self.jsonl_download_uri.fetch_raw().await?; |
201 | 204 |
|
202 | 205 | let body = response |
203 | 206 | .bytes_stream() |
@@ -256,8 +259,6 @@ pub async fn rulings() -> crate::Result<impl Stream<Item = crate::Result<Ruling> |
256 | 259 | mod tests { |
257 | 260 | use futures::StreamExt; |
258 | 261 |
|
259 | | - use crate::util::streaming_deserializer; |
260 | | - |
261 | 262 | #[tokio::test] |
262 | 263 | #[ignore] |
263 | 264 | async fn oracle_cards() { |
@@ -302,31 +303,4 @@ mod tests { |
302 | 303 | card.unwrap(); |
303 | 304 | } |
304 | 305 | } |
305 | | - |
306 | | - #[tokio::test] |
307 | | - async fn test_parse_list() { |
308 | | - use crate::ruling::Ruling; |
309 | | - let s = r#"[ |
310 | | - { |
311 | | - "object": "ruling", |
312 | | - "oracle_id": "0004ebd0-dfd6-4276-b4a6-de0003e94237", |
313 | | - "source": "wotc", |
314 | | - "published_at": "2004-10-04", |
315 | | - "comment": "If there are two of these on the battlefield, they do not add together. The result is that only two permanents can be untapped." |
316 | | - }, |
317 | | - { |
318 | | - "object": "ruling", |
319 | | - "oracle_id": "0007c283-5b7a-4c00-9ca1-b455c8dff8c3", |
320 | | - "source": "wotc", |
321 | | - "published_at": "2019-08-23", |
322 | | - "comment": "The “commander tax” increases based on how many times a commander was cast from the command zone. Casting a commander from your hand doesn’t require that additional cost, and it doesn’t increase what the cost will be the next time you cast that commander from the command zone." |
323 | | - } |
324 | | - ]"#; |
325 | | - let mut stream = |
326 | | - streaming_deserializer::create(s.as_bytes()).map(|r: crate::Result<Ruling>| r.unwrap()); |
327 | | - |
328 | | - while let Some(r) = stream.next().await { |
329 | | - drop(r) |
330 | | - } |
331 | | - } |
332 | 306 | } |
0 commit comments