osm-git

A WIP POC based on the idea from https://blog.andygol.co.ua/en/2023/05/07/osm-2-0-api-using-git/
git clone git://archive.git.mtrnord.blog/MTRNord/osm-git.git
Log | Files | Refs | LICENSE

commit 345ee1e632502e2956af92dff02d5c2955eb421c
parent 95d2e15e370d51c762a37eda4613acbef229ac4e
Author: MTRNord <mtrnord1@gmail.com>
Date:   Mon, 22 May 2023 16:25:41 +0200

Add support for files downloaded using torrents

Diffstat:
MCargo.lock | 29+++++++++++++++++++++++++++++
MCargo.toml | 1+
Msrc/main.rs | 207++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/osm/osm_data.rs | 126+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
4 files changed, 219 insertions(+), 144 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -866,6 +866,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "walkdir", ] [[package]] @@ -1133,6 +1134,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1595,6 +1605,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] name = "want" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1741,6 +1761,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -20,3 +20,4 @@ time = { version = "0.3.21", features = ["formatting", "parsing"] } tokio = { version = "1.28.1", features = ["full"] } tracing = "0.1.37" tracing-subscriber = "0.3.17" +walkdir = "2.3.3" diff --git a/src/main.rs b/src/main.rs @@ -43,6 +43,11 @@ struct Cli { /// This is to avoid causing a lot of load on the OSM servers #[arg(long, default_value = "500")] wait_time: u64, + /// If we should use torrent files from the cache instead for changesets + /// + /// These need to be downloaded manually and be put into the cache_folder/changesets/torrents folder + #[arg(long)] + use_torrents: bool, } #[tokio::main] @@ -79,100 +84,105 @@ async fn main() -> Result<()> { )?; info!("Git repository initialized"); - // Main download loop - let mut changeset_position_top = cli.start_changesets[0..3].parse::<u64>()?; - let mut changeset_position_middle = cli.start_changesets[4..7].parse::<u64>()?; - let mut changeset_position_bottom = cli.start_changesets[8..11].parse::<u64>()?; - let mut changeset_position_middle_incremented = false; - let mut changeset_position_top_incremented = false; + if !cli.use_torrents { + // Main download loop + let mut changeset_position_top = cli.start_changesets[0..3].parse::<u64>()?; + let mut changeset_position_middle = cli.start_changesets[4..7].parse::<u64>()?; + let mut changeset_position_bottom = cli.start_changesets[8..11].parse::<u64>()?; + let mut changeset_position_middle_incremented = false; + let mut changeset_position_top_incremented = false; - loop { - // Check for cache and use it if it exists - let cache_file_path = format!( - "{}/changesets/{:03}/{:03}/{:03}.osm.gz", - cli.cache_path, - changeset_position_top, - changeset_position_middle, - changeset_position_bottom - ); - - if std::path::Path::new(&cache_file_path).exists() { - info!( - "We already got the changeset file at {}. Skipping", - cache_file_path + loop { + // Check for cache and use it if it exists + let cache_file_path = format!( + "{}/changesets/{:03}/{:03}/{:03}.osm.gz", + cli.cache_path, + changeset_position_top, + changeset_position_middle, + changeset_position_bottom ); - // Increment the changeset position - changeset_position_bottom += 1; - changeset_position_middle_incremented = false; - changeset_position_top_incremented = false; - } else { - { - // First we download the changeset files - let changeset_url = format!( - "{}/{:03}/{:03}/{:03}.osm.gz", - cli.changeset_server, - changeset_position_top, - changeset_position_middle, - changeset_position_bottom + if std::path::Path::new(&cache_file_path).exists() { + info!( + "We already got the changeset file at {}. Skipping", + cache_file_path ); - info!("Downloading changeset file from {}", changeset_url); - let changeset_response: reqwest::Response = - client.get(&changeset_url).send().await?; - if changeset_response.status() == reqwest::StatusCode::NOT_FOUND { - warn!("Changeset file not found at {}", changeset_url); - // We've reached the end of the changesets for this bottom position. - // If we incremented top and failed again, we're done. - if changeset_position_top_incremented { - info!("Finished or failed downloading changesets"); - info!( - "Changeset position: {} {} {}", - changeset_position_top, - changeset_position_middle, - changeset_position_bottom - ); - warn!("Response body: {:?}", changeset_response.text().await?); - // TODO: We want to have an endless loop here optionally so that we can keep trying to download changesets. - break; - } - // We reset bottom to 0 and increment middle. - // We also mark middle as incremented so that we increment top on the next failure. - if !changeset_position_middle_incremented && changeset_position_bottom != 0 { - changeset_position_bottom = 0; - changeset_position_middle += 1; - changeset_position_middle_incremented = true; - changeset_position_top_incremented = false; - } else { - changeset_position_middle_incremented = false; - changeset_position_top += 1; - changeset_position_top_incremented = true; - changeset_position_middle = 0; - changeset_position_bottom = 0; + + // Increment the changeset position + changeset_position_bottom += 1; + changeset_position_middle_incremented = false; + changeset_position_top_incremented = false; + } else { + { + // First we download the changeset files + let changeset_url = format!( + "{}/{:03}/{:03}/{:03}.osm.gz", + cli.changeset_server, + changeset_position_top, + changeset_position_middle, + changeset_position_bottom + ); + info!("Downloading changeset file from {}", changeset_url); + let changeset_response: reqwest::Response = + client.get(&changeset_url).send().await?; + if changeset_response.status() == reqwest::StatusCode::NOT_FOUND { + warn!("Changeset file not found at {}", changeset_url); + // We've reached the end of the changesets for this bottom position. + // If we incremented top and failed again, we're done. + if changeset_position_top_incremented { + info!("Finished or failed downloading changesets"); + info!( + "Changeset position: {} {} {}", + changeset_position_top, + changeset_position_middle, + changeset_position_bottom + ); + warn!("Response body: {:?}", changeset_response.text().await?); + // TODO: We want to have an endless loop here optionally so that we can keep trying to download changesets. + break; + } + // We reset bottom to 0 and increment middle. + // We also mark middle as incremented so that we increment top on the next failure. + if !changeset_position_middle_incremented && changeset_position_bottom != 0 + { + changeset_position_bottom = 0; + changeset_position_middle += 1; + changeset_position_middle_incremented = true; + changeset_position_top_incremented = false; + } else { + changeset_position_middle_incremented = false; + changeset_position_top += 1; + changeset_position_top_incremented = true; + changeset_position_middle = 0; + changeset_position_bottom = 0; + } + continue; } - continue; - } - let changeset_data = changeset_response.bytes().await?; - info!("Caching changeset file to disk"); - std::fs::create_dir_all(std::path::Path::new(&cache_file_path).parent().unwrap())?; - std::fs::write(&cache_file_path, &changeset_data)?; - info!("Changeset file downloaded"); - }; + let changeset_data = changeset_response.bytes().await?; + info!("Caching changeset file to disk"); + std::fs::create_dir_all( + std::path::Path::new(&cache_file_path).parent().unwrap(), + )?; + std::fs::write(&cache_file_path, &changeset_data)?; + info!("Changeset file downloaded"); + }; - // TODO: We need to dynamically do this based on the data instead. Otherwise we dont have ram larrge enough - // let file = File::open(cache_file_path)?; - // let changeset_data = unsafe { Mmap::map(&file)? }; + // TODO: We need to dynamically do this based on the data instead. Otherwise we dont have ram larrge enough + // let file = File::open(cache_file_path)?; + // let changeset_data = unsafe { Mmap::map(&file)? }; - // let parsed_changeset = parse_changeset(&changeset_data)?; - // info!("Changeset file parsed"); - // changesets.extend(parsed_changeset); + // let parsed_changeset = parse_changeset(&changeset_data)?; + // info!("Changeset file parsed"); + // changesets.extend(parsed_changeset); - // Increment the changeset position - changeset_position_bottom += 1; - changeset_position_middle_incremented = false; - changeset_position_top_incremented = false; + // Increment the changeset position + changeset_position_bottom += 1; + changeset_position_middle_incremented = false; + changeset_position_top_incremented = false; - // Wait a few seconds before downloading the next changeset file - tokio::time::sleep(Duration::from_millis(cli.wait_time)).await; + // Wait a few seconds before downloading the next changeset file + tokio::time::sleep(Duration::from_millis(cli.wait_time)).await; + } } } @@ -188,17 +198,20 @@ async fn main() -> Result<()> { // Check for cache and use it if it exists let cache_file_path = format!( "{}/replication/{:03}/{:03}/{:03}.osm.gz", - cli.cache_path, - changeset_position_top, - changeset_position_middle, - changeset_position_bottom + cli.cache_path, data_position_top, data_position_middle, data_position_bottom ); if std::path::Path::new(&cache_file_path).exists() { info!("Using cached data file at {}", cache_file_path); let file = File::open(&cache_file_path)?; let data = unsafe { Mmap::map(&file)? }; - convert_objects_to_git(&repository, &author, &data, cli.cache_path.clone())?; + convert_objects_to_git( + &repository, + &author, + &data, + cli.cache_path.clone(), + cli.use_torrents, + )?; info!("Data file parsed"); // Increment the data position @@ -260,7 +273,13 @@ async fn main() -> Result<()> { let file = File::open(cache_file_path)?; let data = unsafe { Mmap::map(&file)? }; - convert_objects_to_git(&repository, &author, &data, cli.cache_path.clone())?; + convert_objects_to_git( + &repository, + &author, + &data, + cli.cache_path.clone(), + cli.use_torrents, + )?; // Increment the data position data_position_bottom += 1; @@ -273,12 +292,6 @@ async fn main() -> Result<()> { } info!( - "Downloaded changesets until {} {} {}", - changeset_position_top, - changeset_position_middle, - changeset_position_bottom - 1 - ); - info!( "Downloaded data until {} {} {}", data_position_top, data_position_middle, diff --git a/src/osm/osm_data.rs b/src/osm/osm_data.rs @@ -17,6 +17,7 @@ use std::{ }; use time::{format_description::well_known::Iso8601, OffsetDateTime}; use tracing::{debug, error, info, warn}; +use walkdir::WalkDir; use crate::git::commit; @@ -472,6 +473,7 @@ pub fn convert_objects_to_git( committer: &Signature, data: &[u8], cache_folder: String, + use_torrents: bool, ) -> Result<()> { // If the file is empty we skip it if data.is_empty() { @@ -848,8 +850,14 @@ pub fn convert_objects_to_git( for changeset in changeset_list { // Find the changeset within the files of the cache - let changeset = - find_changesets_in_cache(cache_folder.clone(), *changeset, None, None, None)?; + let changeset = find_changesets_in_cache( + cache_folder.clone(), + use_torrents, + *changeset, + None, + None, + None, + )?; if let Some(changeset) = changeset { // Get comment tag if it exists and trim it @@ -947,6 +955,7 @@ pub fn convert_objects_to_git( /// The changeset if found fn find_changesets_in_cache( cache_folder: String, + use_torrents: bool, changeset_id: u64, changeset_position_top: Option<u64>, changeset_position_middle: Option<u64>, @@ -956,57 +965,80 @@ fn find_changesets_in_cache( // // Each file has to be parsed using "parse_changeset(&changeset_data)?;" - let changeset_folder = format!("{}/changesets", cache_folder); - let mut changeset_position_top = changeset_position_top.unwrap_or(0); - let mut changeset_position_middle = changeset_position_middle.unwrap_or(0); - let mut changeset_position_bottom = changeset_position_bottom.unwrap_or(0); - let changeset_file = format!( - "{:03}/{:03}/{:03}.osm.gz", - changeset_position_top, changeset_position_middle, changeset_position_bottom - ); - let changeset_path = format!("{}/{}", changeset_folder, changeset_file); - - if !Path::new(&changeset_path).exists() { - return Ok(None); - } + let changeset_folder = if use_torrents { + format!("{}/changesets/torrents", cache_folder) + } else { + format!("{}/changesets", cache_folder) + }; + + if !use_torrents { + let mut changeset_position_top = changeset_position_top.unwrap_or(0); + let mut changeset_position_middle = changeset_position_middle.unwrap_or(0); + let mut changeset_position_bottom = changeset_position_bottom.unwrap_or(0); + let changeset_file = format!( + "{:03}/{:03}/{:03}.osm.gz", + changeset_position_top, changeset_position_middle, changeset_position_bottom + ); + let changeset_path = format!("{}/{}", changeset_folder, changeset_file); + + if !Path::new(&changeset_path).exists() { + return Ok(None); + } - let mut changeset_file = File::open(changeset_path)?; - let mut changeset_data = Vec::new(); - changeset_file.read_to_end(&mut changeset_data)?; + let mut changeset_file = File::open(changeset_path)?; + let mut changeset_data = Vec::new(); + changeset_file.read_to_end(&mut changeset_data)?; - let changesets = parse_changeset(&changeset_data)?; + let changesets = parse_changeset(&changeset_data)?; - // Check if the file has the correct changeset id in vector otherwise we recurse to the next file - if changesets.iter().any(|c| c.id == changeset_id) { - return Ok(changesets.into_iter().find(|c| c.id == changeset_id)); - } + // Check if the file has the correct changeset id in vector otherwise we recurse to the next file + if changesets.iter().any(|c| c.id == changeset_id) { + return Ok(changesets.into_iter().find(|c| c.id == changeset_id)); + } - // We recurse to the next file since we found no changeset with the correct id + // We recurse to the next file since we found no changeset with the correct id - if changeset_position_top == 999 - && changeset_position_middle == 999 - && changeset_position_bottom == 999 - { - // Uhhhhhh?! - return Ok(None); - } + if changeset_position_top == 999 + && changeset_position_middle == 999 + && changeset_position_bottom == 999 + { + // Uhhhhhh?! + return Ok(None); + } - if changeset_position_middle == 999 && changeset_position_bottom == 999 { - changeset_position_middle = 0; - changeset_position_bottom = 0; - changeset_position_top += 1; - } + if changeset_position_middle == 999 && changeset_position_bottom == 999 { + changeset_position_middle = 0; + changeset_position_bottom = 0; + changeset_position_top += 1; + } - if changeset_position_bottom == 999 { - changeset_position_bottom = 0; - changeset_position_middle += 1; - } + if changeset_position_bottom == 999 { + changeset_position_bottom = 0; + changeset_position_middle += 1; + } - find_changesets_in_cache( - cache_folder, - changeset_id, - Some(changeset_position_top), - Some(changeset_position_middle), - Some(changeset_position_bottom), - ) + find_changesets_in_cache( + cache_folder, + use_torrents, + changeset_id, + Some(changeset_position_top), + Some(changeset_position_middle), + Some(changeset_position_bottom), + ) + } else { + let mut changeset: Option<Changeset> = None; + + for entry in WalkDir::new(changeset_folder) { + let mut changeset_file = File::open(entry?.path())?; + let mut changeset_data = Vec::new(); + changeset_file.read_to_end(&mut changeset_data)?; + + let changesets = parse_changeset(&changeset_data)?; + if changesets.iter().any(|c| c.id == changeset_id) { + changeset = changesets.into_iter().find(|c| c.id == changeset_id); + } + } + + Ok(changeset) + } }