continuwuity/src/service/media/migrations.rs
Jason Volk 946ca364e0 Database Refactor
combine service/users data w/ mod unit

split sliding sync related out of service/users

instrument database entry points

remove increment crap from database interface

de-wrap all database get() calls

de-wrap all database insert() calls

de-wrap all database remove() calls

refactor database interface for async streaming

add query key serializer for database

implement Debug for result handle

add query deserializer for database

add deserialization trait for option handle

start a stream utils suite

de-wrap/asyncify/type-query count_one_time_keys()

de-wrap/asyncify users count

add admin query users command suite

de-wrap/asyncify users exists

de-wrap/partially asyncify user filter related

asyncify/de-wrap users device/keys related

asyncify/de-wrap user auth/misc related

asyncify/de-wrap users blurhash

asyncify/de-wrap account_data get; merge Data into Service

partial asyncify/de-wrap uiaa; merge Data into Service

partially asyncify/de-wrap transaction_ids get; merge Data into Service

partially asyncify/de-wrap key_backups; merge Data into Service

asyncify/de-wrap pusher service getters; merge Data into Service

asyncify/de-wrap rooms alias getters/some iterators

asyncify/de-wrap rooms directory getters/iterator

partially asyncify/de-wrap rooms lazy-loading

partially asyncify/de-wrap rooms metadata

asyncify/dewrap rooms outlier

asyncify/dewrap rooms pdu_metadata

dewrap/partially asyncify rooms read receipt

de-wrap rooms search service

de-wrap/partially asyncify rooms user service

partial de-wrap rooms state_compressor

de-wrap rooms state_cache

de-wrap room state et al

de-wrap rooms timeline service

additional users device/keys related

de-wrap/asyncify sender

asyncify services

refactor database to TryFuture/TryStream

refactor services for TryFuture/TryStream

asyncify api handlers

additional asyncification for admin module

abstract stream related; support reverse streams

additional stream conversions

asyncify state-res related

Signed-off-by: Jason Volk <jason@zemos.net>
2024-10-25 00:32:30 -04:00

167 lines
4.8 KiB
Rust

use std::{
collections::HashSet,
ffi::{OsStr, OsString},
fs::{self},
path::PathBuf,
sync::Arc,
time::Instant,
};
use conduit::{
debug, debug_info, debug_warn, error, info,
utils::{stream::TryIgnore, ReadyExt},
warn, Config, Result,
};
use crate::{globals, Services};
/// Migrates a media directory from legacy base64 file names to sha2 file names.
/// All errors are fatal. Upon success the database is keyed to not perform this
/// again.
pub(crate) async fn migrate_sha256_media(services: &Services) -> Result<()> {
let db = &services.db;
let config = &services.server.config;
warn!("Migrating legacy base64 file names to sha256 file names");
let mediaid_file = &db["mediaid_file"];
// Move old media files to new names
let mut changes = Vec::<(PathBuf, PathBuf)>::new();
mediaid_file
.raw_keys()
.ignore_err()
.ready_for_each(|key| {
let old = services.media.get_media_file_b64(key);
let new = services.media.get_media_file_sha256(key);
debug!(?key, ?old, ?new, num = changes.len(), "change");
changes.push((old, new));
})
.await;
// move the file to the new location
for (old_path, path) in changes {
if old_path.exists() {
tokio::fs::rename(&old_path, &path).await?;
if config.media_compat_file_link {
tokio::fs::symlink(&path, &old_path).await?;
}
}
}
// Apply fix from when sha256_media was backward-incompat and bumped the schema
// version from 13 to 14. For users satisfying these conditions we can go back.
if services.globals.db.database_version().await == 14 && globals::migrations::DATABASE_VERSION == 13 {
services.globals.db.bump_database_version(13)?;
}
db["global"].insert(b"feat_sha256_media", &[]);
info!("Finished applying sha256_media");
Ok(())
}
/// Check is run on startup for prior-migrated media directories. This handles:
/// - Going back and forth to non-sha256 legacy binaries (e.g. upstream).
/// - Deletion of artifacts in the media directory which will then fall out of
/// sync with the database.
pub(crate) async fn checkup_sha256_media(services: &Services) -> Result<()> {
use crate::media::encode_key;
debug!("Checking integrity of media directory");
let db = &services.db;
let media = &services.media;
let config = &services.server.config;
let mediaid_file = &db["mediaid_file"];
let mediaid_user = &db["mediaid_user"];
let dbs = (mediaid_file, mediaid_user);
let timer = Instant::now();
let dir = media.get_media_dir();
let files: HashSet<OsString> = fs::read_dir(dir)?
.filter_map(|ent| ent.map_or(None, |ent| Some(ent.path().into_os_string())))
.collect();
for key in media.db.get_all_media_keys().await {
let new_path = media.get_media_file_sha256(&key).into_os_string();
let old_path = media.get_media_file_b64(&key).into_os_string();
if let Err(e) = handle_media_check(&dbs, config, &files, &key, &new_path, &old_path).await {
error!(
media_id = ?encode_key(&key), ?new_path, ?old_path,
"Failed to resolve media check failure: {e}"
);
}
}
debug_info!(
elapsed = ?timer.elapsed(),
"Finished checking media directory"
);
Ok(())
}
async fn handle_media_check(
dbs: &(&Arc<database::Map>, &Arc<database::Map>), config: &Config, files: &HashSet<OsString>, key: &[u8],
new_path: &OsStr, old_path: &OsStr,
) -> Result<()> {
use crate::media::encode_key;
let (mediaid_file, mediaid_user) = dbs;
let new_exists = files.contains(new_path);
let old_exists = files.contains(old_path);
let old_is_symlink = || async {
tokio::fs::symlink_metadata(old_path)
.await
.map_or(false, |md| md.is_symlink())
};
if config.prune_missing_media && !old_exists && !new_exists {
error!(
media_id = ?encode_key(key), ?new_path, ?old_path,
"Media is missing at all paths. Removing from database..."
);
mediaid_file.remove(key);
mediaid_user.remove(key);
}
if config.media_compat_file_link && !old_exists && new_exists {
debug_warn!(
media_id = ?encode_key(key), ?new_path, ?old_path,
"Media found but missing legacy link. Fixing..."
);
tokio::fs::symlink(&new_path, &old_path).await?;
}
if config.media_compat_file_link && !new_exists && old_exists {
debug_warn!(
media_id = ?encode_key(key), ?new_path, ?old_path,
"Legacy media found without sha256 migration. Fixing..."
);
debug_assert!(
old_is_symlink().await,
"Legacy media not expected to be a symlink without an existing sha256 migration."
);
tokio::fs::rename(&old_path, &new_path).await?;
tokio::fs::symlink(&new_path, &old_path).await?;
}
if !config.media_compat_file_link && old_exists && old_is_symlink().await {
debug_warn!(
media_id = ?encode_key(key), ?new_path, ?old_path,
"Legacy link found but compat disabled. Cleansing symlink..."
);
debug_assert!(
new_exists,
"sha256 migration into new file expected prior to cleaning legacy symlink here."
);
tokio::fs::remove_file(&old_path).await?;
}
Ok(())
}