#![cfg_attr(not(windows), allow(dead_code))]
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
pub fn popover_data_dir(cache_root: &Path) -> PathBuf {
cache_root.join("ai-usagebar").join("popover")
}
fn local_storage(user_data: &Path) -> PathBuf {
user_data
.join("EBWebView")
.join("Default")
.join("Local Storage")
}
pub fn legacy_local_storage(exe: &Path) -> PathBuf {
let mut name = exe.file_name().unwrap_or_default().to_os_string();
name.push(".WebView2");
local_storage(&exe.with_file_name(name))
}
pub fn adopt_legacy_local_storage(legacy: &Path, profile: &Path) -> io::Result<bool> {
let target = local_storage(profile);
if target.exists() || !legacy.is_dir() {
return Ok(false);
}
let staging = target.with_file_name("Local Storage.adopting");
if staging.exists() {
fs::remove_dir_all(&staging)?;
}
let copied = copy_dir(legacy, &staging).and_then(|()| fs::rename(&staging, &target));
if let Err(error) = copied {
let _ = fs::remove_dir_all(&staging);
return Err(error);
}
Ok(true)
}
fn copy_dir(from: &Path, to: &Path) -> io::Result<()> {
fs::create_dir_all(to)?;
for entry in fs::read_dir(from)? {
let entry = entry?;
let kind = entry.file_type()?;
let dest = to.join(entry.file_name());
if kind.is_dir() {
copy_dir(&entry.path(), &dest)?;
} else if kind.is_file() {
fs::copy(entry.path(), dest)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::fs;
use std::path::Path;
use super::{
adopt_legacy_local_storage, legacy_local_storage, local_storage, popover_data_dir,
};
#[test]
fn popover_profile_lives_under_the_cache_root() {
let root = Path::new("cache-root");
assert_eq!(
popover_data_dir(root),
root.join("ai-usagebar").join("popover")
);
}
#[test]
fn popover_profile_never_depends_on_the_exe_dir() {
let dir = popover_data_dir(Path::new("cache-root"));
assert!(dir.starts_with("cache-root"));
assert!(!dir.to_string_lossy().contains("WebView2"));
}
#[test]
fn legacy_local_storage_is_webview2s_default_next_to_the_exe() {
let exe = Path::new("install").join("ai-usagebar-tray.exe");
assert_eq!(
legacy_local_storage(&exe),
Path::new("install")
.join("ai-usagebar-tray.exe.WebView2")
.join("EBWebView")
.join("Default")
.join("Local Storage")
);
}
fn seed_legacy(root: &Path) -> std::path::PathBuf {
let legacy = legacy_local_storage(&root.join("install").join("ai-usagebar-tray.exe"));
fs::create_dir_all(legacy.join("leveldb")).unwrap();
fs::write(legacy.join("leveldb").join("000003.log"), b"layout").unwrap();
fs::write(legacy.join("leveldb").join("CURRENT"), b"MANIFEST-000001\n").unwrap();
legacy
}
#[test]
fn adoption_copies_the_old_local_storage_into_an_empty_profile() {
let tmp = tempfile::tempdir().unwrap();
let legacy = seed_legacy(tmp.path());
let profile = popover_data_dir(&tmp.path().join("cache"));
assert!(adopt_legacy_local_storage(&legacy, &profile).unwrap());
let adopted = local_storage(&profile).join("leveldb");
assert_eq!(fs::read(adopted.join("000003.log")).unwrap(), b"layout");
assert_eq!(
fs::read(adopted.join("CURRENT")).unwrap(),
b"MANIFEST-000001\n"
);
assert!(
!local_storage(&profile)
.with_file_name("Local Storage.adopting")
.exists()
);
assert_eq!(
fs::read(legacy.join("leveldb").join("000003.log")).unwrap(),
b"layout",
"the old profile is left as it was"
);
}
#[test]
fn adoption_never_touches_a_profile_that_already_has_local_storage() {
let tmp = tempfile::tempdir().unwrap();
let legacy = seed_legacy(tmp.path());
let profile = popover_data_dir(&tmp.path().join("cache"));
let current = local_storage(&profile).join("leveldb");
fs::create_dir_all(¤t).unwrap();
fs::write(current.join("000003.log"), b"newer").unwrap();
assert!(!adopt_legacy_local_storage(&legacy, &profile).unwrap());
assert_eq!(fs::read(current.join("000003.log")).unwrap(), b"newer");
assert!(!current.join("CURRENT").exists());
}
#[test]
fn adoption_does_nothing_without_an_old_profile() {
let tmp = tempfile::tempdir().unwrap();
let legacy = legacy_local_storage(&tmp.path().join("install").join("ai-usagebar-tray.exe"));
let profile = popover_data_dir(&tmp.path().join("cache"));
assert!(!adopt_legacy_local_storage(&legacy, &profile).unwrap());
assert!(!local_storage(&profile).exists());
}
#[test]
fn adoption_replaces_a_staging_folder_left_by_a_failed_attempt() {
let tmp = tempfile::tempdir().unwrap();
let legacy = seed_legacy(tmp.path());
let profile = popover_data_dir(&tmp.path().join("cache"));
let staging = local_storage(&profile).with_file_name("Local Storage.adopting");
fs::create_dir_all(&staging).unwrap();
fs::write(staging.join("partial"), b"half").unwrap();
assert!(adopt_legacy_local_storage(&legacy, &profile).unwrap());
assert!(!staging.exists());
assert!(!local_storage(&profile).join("partial").exists());
assert!(
local_storage(&profile)
.join("leveldb")
.join("CURRENT")
.exists()
);
}
}