diff --git a/Cargo.lock b/Cargo.lock index b080fdcc2..4bd43b0f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3309,6 +3309,7 @@ version = "0.16.0" dependencies = [ "anyhow", "async-compat", + "chrono", "console_error_panic_hook", "extension-trait", "eyeball-im", @@ -3331,6 +3332,7 @@ dependencies = [ "serde", "serde_json", "similar-asserts", + "tempfile", "thiserror 2.0.17", "tokio", "tracing", diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index 6cb66d360..d90fe4000 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -45,6 +45,7 @@ experimental-element-recent-emojis = ["matrix-sdk/experimental-element-recent-em [dependencies] anyhow.workspace = true +chrono.workspace = true extension-trait = "1.0.2" eyeball-im.workspace = true futures-util.workspace = true @@ -127,6 +128,7 @@ paranoid-android = { version = "0.2.2", default-features = false } [dev-dependencies] similar-asserts.workspace = true +tempfile.workspace = true [build-dependencies] uniffi = { workspace = true, features = ["build"] } diff --git a/bindings/matrix-sdk-ffi/build.rs b/bindings/matrix-sdk-ffi/build.rs index 9c5fc799c..ef70f6be7 100644 --- a/bindings/matrix-sdk-ffi/build.rs +++ b/bindings/matrix-sdk-ffi/build.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::{ env, error::Error, diff --git a/bindings/matrix-sdk-ffi/src/authentication.rs b/bindings/matrix-sdk-ffi/src/authentication.rs index 1e7186f81..3e9bff002 100644 --- a/bindings/matrix-sdk-ffi/src/authentication.rs +++ b/bindings/matrix-sdk-ffi/src/authentication.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::{ collections::HashMap, fmt::{self, Debug}, diff --git a/bindings/matrix-sdk-ffi/src/chunk_iterator.rs b/bindings/matrix-sdk-ffi/src/chunk_iterator.rs index 2d877ccc4..656c3b396 100644 --- a/bindings/matrix-sdk-ffi/src/chunk_iterator.rs +++ b/bindings/matrix-sdk-ffi/src/chunk_iterator.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + //! A generic `ChunkIterator` that operates over a `Vec`. //! //! This type is not designed to work over FFI, but it can be embedded inside an diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index eb4f09653..2bca537ac 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::{ collections::HashMap, fmt::Debug, diff --git a/bindings/matrix-sdk-ffi/src/client_builder.rs b/bindings/matrix-sdk-ffi/src/client_builder.rs index c9c06cf48..9455ca4e9 100644 --- a/bindings/matrix-sdk-ffi/src/client_builder.rs +++ b/bindings/matrix-sdk-ffi/src/client_builder.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + // Allow UniFFI to use methods marked as `#[deprecated]`. #![allow(deprecated)] diff --git a/bindings/matrix-sdk-ffi/src/encryption.rs b/bindings/matrix-sdk-ffi/src/encryption.rs index ee00ef10d..f694d3553 100644 --- a/bindings/matrix-sdk-ffi/src/encryption.rs +++ b/bindings/matrix-sdk-ffi/src/encryption.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::sync::Arc; use futures_util::StreamExt; diff --git a/bindings/matrix-sdk-ffi/src/error.rs b/bindings/matrix-sdk-ffi/src/error.rs index e2283863f..74d055777 100644 --- a/bindings/matrix-sdk-ffi/src/error.rs +++ b/bindings/matrix-sdk-ffi/src/error.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::{collections::HashMap, error::Error, fmt, fmt::Display}; use matrix_sdk::{ diff --git a/bindings/matrix-sdk-ffi/src/event.rs b/bindings/matrix-sdk-ffi/src/event.rs index 3e5ef0e04..1230e6424 100644 --- a/bindings/matrix-sdk-ffi/src/event.rs +++ b/bindings/matrix-sdk-ffi/src/event.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use anyhow::{bail, Context}; use matrix_sdk::IdParseError; use matrix_sdk_ui::timeline::TimelineEventItemId; diff --git a/bindings/matrix-sdk-ffi/src/helpers.rs b/bindings/matrix-sdk-ffi/src/helpers.rs index 1dc2f4517..6dca626a0 100644 --- a/bindings/matrix-sdk-ffi/src/helpers.rs +++ b/bindings/matrix-sdk-ffi/src/helpers.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::sync::Arc; pub(crate) fn unwrap_or_clone_arc(arc: Arc) -> T { diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index 992f6b768..c55b165ae 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -30,7 +30,6 @@ mod store; mod sync_service; mod task_handle; mod timeline; -mod tracing; mod utd; mod utils; mod widget; diff --git a/bindings/matrix-sdk-ffi/src/live_location_share.rs b/bindings/matrix-sdk-ffi/src/live_location_share.rs index 940fb48ca..044b2a197 100644 --- a/bindings/matrix-sdk-ffi/src/live_location_share.rs +++ b/bindings/matrix-sdk-ffi/src/live_location_share.rs @@ -9,8 +9,9 @@ // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and +// See the License for that specific language governing permissions and // limitations under the License. + use crate::ruma::LocationContent; #[derive(uniffi::Record)] pub struct LastLocation { diff --git a/bindings/matrix-sdk-ffi/src/notification.rs b/bindings/matrix-sdk-ffi/src/notification.rs index 207b7bffa..b44c097c3 100644 --- a/bindings/matrix-sdk-ffi/src/notification.rs +++ b/bindings/matrix-sdk-ffi/src/notification.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::{collections::HashMap, sync::Arc}; use matrix_sdk_ui::notification_client::{ diff --git a/bindings/matrix-sdk-ffi/src/notification_settings.rs b/bindings/matrix-sdk-ffi/src/notification_settings.rs index c8c688da0..3f163d9c9 100644 --- a/bindings/matrix-sdk-ffi/src/notification_settings.rs +++ b/bindings/matrix-sdk-ffi/src/notification_settings.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::sync::{Arc, RwLock}; use matrix_sdk::{ diff --git a/bindings/matrix-sdk-ffi/src/platform.rs b/bindings/matrix-sdk-ffi/src/platform/mod.rs similarity index 92% rename from bindings/matrix-sdk-ffi/src/platform.rs rename to bindings/matrix-sdk-ffi/src/platform/mod.rs index 3718afc0d..a5b05cfb1 100644 --- a/bindings/matrix-sdk-ffi/src/platform.rs +++ b/bindings/matrix-sdk-ffi/src/platform/mod.rs @@ -1,10 +1,25 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::sync::OnceLock; #[cfg(feature = "sentry")] use std::sync::{atomic::AtomicBool, Arc}; +use ::tracing::info; #[cfg(feature = "sentry")] -use tracing::warn; -use tracing_appender::rolling::{RollingFileAppender, Rotation}; +use ::tracing::warn; +use tracing_appender::rolling::Rotation; #[cfg(feature = "sentry")] use tracing_core::Level; use tracing_core::Subscriber; @@ -23,9 +38,22 @@ use tracing_subscriber::{ EnvFilter, Layer, Registry, }; +use crate::error::ClientError; + +/// Default maximum total size of all log files combined (10MB). +const DEFAULT_MAX_TOTAL_SIZE_BYTES: u64 = 10 * 1024 * 1024; + +/// Default maximum age of log files in seconds (1 week). +const DEFAULT_MAX_AGE_SECONDS: u64 = 7 * 24 * 60 * 60; + +mod rolling_writer; +pub mod tracing; + +use rolling_writer::SizeAndDateRollingWriter; + +use self::tracing::LogLevel; #[cfg(feature = "sentry")] -use crate::tracing::BRIDGE_SPAN_NAME; -use crate::{error::ClientError, tracing::LogLevel}; +use self::tracing::BRIDGE_SPAN_NAME; // Adjusted version of tracing_subscriber::fmt::Format struct EventFormatter { @@ -153,7 +181,7 @@ type ReloadHandle = Handle< Layered, FieldsFormatterForFiles, EventFormatter, - RollingFileAppender, + SizeAndDateRollingWriter, >, Layered, >; @@ -218,21 +246,17 @@ fn make_file_layer( Layered, FieldsFormatterForFiles, EventFormatter, - RollingFileAppender, + SizeAndDateRollingWriter, > { - let mut builder = RollingFileAppender::builder() - .rotation(Rotation::HOURLY) - .filename_prefix(&file_configuration.file_prefix); - - if let Some(max_files) = file_configuration.max_files { - builder = builder.max_log_files(max_files as usize) - } - if let Some(file_suffix) = file_configuration.file_suffix { - builder = builder.filename_suffix(file_suffix) - } - - let writer = - builder.build(&file_configuration.path).expect("Failed to create a rolling file appender."); + let writer = SizeAndDateRollingWriter::new( + &file_configuration.path, + file_configuration.file_prefix, + file_configuration.file_suffix.unwrap_or_else(|| String::from(".log")), + Rotation::HOURLY, + file_configuration.max_total_size_bytes.unwrap_or(DEFAULT_MAX_TOTAL_SIZE_BYTES), + file_configuration.max_age_seconds.unwrap_or(DEFAULT_MAX_AGE_SECONDS), + ) + .expect("Failed to create a rolling file appender."); fmt::layer() .fmt_fields(FieldsFormatterForFiles::default()) @@ -254,13 +278,30 @@ pub struct TracingFileConfiguration { file_prefix: String, /// Optional suffix for the log file's names. + /// + /// Default is ".log" if not specified. file_suffix: Option, - /// Maximum number of rotated files. + /// Maximum total size of all log files combined in bytes. /// - /// If not set, there's no max limit, i.e. the number of log files is - /// unlimited. - max_files: Option, + /// When the total size of all log files with the configured prefix and + /// suffix exceeds this limit, the oldest files will be removed until + /// the total is below the limit. + /// + /// This is useful to prevent log files from consuming too much disk space + /// over time, even with multiple rotated files. + /// + /// Default: 10MB (10 * 1024 * 1024 bytes) if not specified. + max_total_size_bytes: Option, + + /// Maximum age of log files in seconds. + /// + /// Log files older than this age will be automatically removed during + /// cleanup. This is checked when the writer is created and during + /// rotation operations. + /// + /// Default: 1 week (7 * 24 * 60 * 60 seconds) if not specified. + max_age_seconds: Option, } #[derive(PartialEq, PartialOrd)] @@ -558,7 +599,7 @@ impl TracingConfiguration { } // Log the log levels 🧠. - tracing::info!(env_filter, "Logging has been set up"); + info!(env_filter, "Logging has been set up"); logging_ctx } diff --git a/bindings/matrix-sdk-ffi/src/platform/rolling_writer.rs b/bindings/matrix-sdk-ffi/src/platform/rolling_writer.rs new file mode 100644 index 000000000..935da5bac --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/platform/rolling_writer.rs @@ -0,0 +1,923 @@ +// Copyright 2026 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + +use std::{ + fs::{self, File, OpenOptions}, + io::{self, Write}, + path::{Path, PathBuf}, + sync::Mutex, + time::SystemTime, +}; + +use tracing_appender::rolling::Rotation; + +/// Custom rolling file appender that supports time-based rotation and +/// size-based cleanup. +/// +/// This writer automatically manages log files with the following behavior: +/// +/// # File Naming +/// +/// Log files are named using the pattern: `{prefix}.{timestamp}.{suffix}` +/// where the timestamp format depends on the rotation period: +/// - `MINUTELY`: `YYYY-MM-DD-HH-MM` +/// - `HOURLY`: `YYYY-MM-DD-HH` +/// - `DAILY`: `YYYY-MM-DD` +/// - `WEEKLY` or `NEVER`: `YYYY-Www` (ISO week number, e.g., `2024-W03`) +/// +/// # Automatic Rotation +/// +/// Files are rotated (a new file is created) when the configured time period +/// changes. For example, with hourly rotation, a new file is created when the +/// hour changes. Rotation is checked: +/// - During writer initialization (creates/opens file for current period) +/// - Before each write operation (only rotates if time period has changed) +/// +/// If a log file already exists for the current time period, it will be +/// reopened and appended to rather than creating a new file. +/// +/// # Automatic Cleanup +/// +/// The writer performs cleanup operations during initialization and rotation: +/// - **Size limit enforcement**: When total size of all log files exceeds +/// `max_total_size_bytes`, the oldest files are removed until under the limit +/// - **Age-based cleanup**: Files older than `max_age_seconds` (based on +/// filesystem modification time) are automatically removed +/// - **File filtering**: Only files matching both the configured prefix and +/// suffix are managed; other files in the directory are left untouched +/// +/// # Side Effects on Creation +/// +/// When `new()` is called, the following side effects occur: +/// 1. Creates the log directory if it doesn't exist (including parent +/// directories) +/// 2. Creates or opens a log file for the current time period (appends if +/// exists) +/// 3. Performs cleanup of old files based on age (by filesystem mtime) +/// 4. Enforces the total size limit by removing oldest files if needed +/// +/// # Thread Safety +/// +/// This writer is safe to use from multiple threads. Internal state is +/// protected by a mutex, ensuring that file operations and rotations are +/// properly synchronized. +pub(super) struct SizeAndDateRollingWriter { + config: WriterConfig, + state: Mutex>, +} + +/// Immutable configuration for the writer - shared without locks. +/// +/// This struct contains all configuration parameters that remain constant +/// throughout the writer's lifetime. Since these values never change, they +/// can be safely shared across threads without synchronization. +struct WriterConfig { + /// Directory where log files are created + base_path: PathBuf, + /// Prefix for log file names (e.g., "app" results in "app.2024-01-15.log") + file_prefix: String, + /// Suffix for log file names (typically ".log") + file_suffix: String, + /// Time period for automatic rotation (MINUTELY, HOURLY, DAILY, WEEKLY, or + /// NEVER which is treated as WEEKLY) + rotation: Rotation, + /// Maximum total size in bytes of all log files before cleanup + max_total_size_bytes: u64, + /// Maximum age in seconds before a log file is removed during cleanup + max_age_seconds: u64, +} + +/// Mutable state that requires synchronization. +/// +/// This struct contains the current log file handle and its path. These values +/// change when rotation occurs, so access must be protected by a mutex to +/// ensure thread safety. +/// +/// The state is wrapped in `Option` because it needs to be temporarily taken +/// during rotation operations to allow mutation while holding the lock. +struct WriterState { + /// The currently open log file handle for writing + current_file: File, + /// Path to the current log file (used to identify it during cleanup) + current_path: PathBuf, +} + +impl SizeAndDateRollingWriter { + /// Creates a new rolling writer with the specified configuration. + /// + /// # Arguments + /// + /// * `path` - Directory where log files will be created. Will be created if + /// it doesn't exist. + /// * `file_prefix` - Prefix for log file names (e.g., "app") + /// * `file_suffix` - Suffix for log file names (e.g., ".log") + /// * `rotation` - Time period for rotation (MINUTELY, HOURLY, DAILY, + /// WEEKLY, or NEVER which is treated as WEEKLY) + /// * `max_total_size_bytes` - Maximum total size of all log files. When + /// exceeded, oldest files are removed. + /// * `max_age_seconds` - Maximum age of log files in seconds. Files older + /// than this (by filesystem mtime) are removed during cleanup. + /// + /// # Side Effects + /// + /// This method performs several file system operations in order: + /// 1. Creates the directory at `path` if it doesn't exist + /// 2. Creates or reopens a log file for the current time period (appends if + /// exists) + /// 3. Scans the directory for existing log files matching the prefix/suffix + /// 4. Removes files older than `max_age_seconds` (by filesystem mtime) + /// 5. Removes oldest files if total size exceeds `max_total_size_bytes` + /// + /// # Errors + /// + /// Returns an error if: + /// - The directory cannot be created + /// - The directory cannot be read + /// - The log file cannot be created or opened + /// - File metadata cannot be read during cleanup + /// + /// # Examples + /// + /// ```ignore + /// let writer = SizeAndDateRollingWriter::new( + /// "/var/log/myapp", + /// "app".to_owned(), + /// ".log".to_owned(), + /// Rotation::HOURLY, + /// 100 * 1024 * 1024, // 100 MB + /// 7 * 24 * 60 * 60, // 7 days + /// )?; + /// ``` + pub(super) fn new( + path: impl AsRef, + file_prefix: String, + file_suffix: String, + rotation: Rotation, + max_total_size_bytes: u64, + max_age_seconds: u64, + ) -> io::Result { + let base_path = path.as_ref().to_path_buf(); + fs::create_dir_all(&base_path)?; + + let config = WriterConfig { + base_path, + file_prefix, + file_suffix, + rotation, + max_total_size_bytes, + max_age_seconds, + }; + + // Create initial state with first rotation + let mut state = None; + Self::rotate_internal(&config, &mut state, false)?; + + Ok(Self { config, state: Mutex::new(state) }) + } + + /// Extract the timestamp from the current filename. + fn extract_timestamp_from_path(config: &WriterConfig, current_path: &Path) -> Option { + let filename = current_path.file_name()?.to_str()?; + + // Strip prefix and suffix to get the timestamp + // Format: "prefix.timestamp.suffix" + let without_prefix = filename.strip_prefix(&format!("{}.", config.file_prefix))?; + let timestamp = without_prefix.strip_suffix(&config.file_suffix)?; + + Some(timestamp.to_owned()) + } + + /// Check if rotation is needed based on time period change. + fn should_rotate_by_time(config: &WriterConfig, current_path: &Path) -> bool { + let current_time = Self::format_rotation_timestamp(config); + let last_rotation_time = Self::extract_timestamp_from_path(config, current_path); + + // If we can't extract the timestamp, assume rotation is needed + match last_rotation_time { + Some(last_time) => current_time != last_time, + None => true, + } + } + + /// Format the current time as a timestamp string for the rotation period. + /// + /// Returns a timestamp string based on the configured rotation period + /// (e.g., "2024-01-15-14" for hourly rotation). + fn format_rotation_timestamp(config: &WriterConfig) -> String { + let now = chrono::Local::now(); + match config.rotation { + Rotation::MINUTELY => now.format("%Y-%m-%d-%H-%M").to_string(), + Rotation::HOURLY => now.format("%Y-%m-%d-%H").to_string(), + Rotation::DAILY => now.format("%Y-%m-%d").to_string(), + Rotation::WEEKLY | Rotation::NEVER => now.format("%Y-W%W").to_string(), + } + } + + /// Rotate the log file, creating a new file with a timestamp-based name. + /// + /// If `check_conditions` is true, rotation only happens if time or size + /// thresholds are met. Otherwise, rotation is forced. + /// + /// This method also handles initial state creation when called with None + /// state. + fn rotate_internal( + config: &WriterConfig, + state: &mut Option, + check_conditions: bool, + ) -> io::Result<()> { + // Check if rotation is needed (skip for uninitialized state) + if check_conditions { + if let Some(state) = state.as_ref() { + if !Self::should_rotate_by_time(config, &state.current_path) { + return Ok(()); + } + } + } + + let time_str = Self::format_rotation_timestamp(config); + + // Generate filename with timestamp + let filename = format!("{}.{}{}", config.file_prefix, time_str, config.file_suffix); + let new_path = config.base_path.join(filename); + + // Open or create file in append mode + let new_file = OpenOptions::new().create(true).append(true).open(&new_path)?; + + let new_state = WriterState { current_file: new_file, current_path: new_path }; + + // Clean up logs older than configured max age + Self::trim_old_logs_internal(config, &new_state)?; + + // Enforce total size limit by removing oldest files if needed + Self::enforce_total_size_limit_internal(config, &new_state)?; + + *state = Some(new_state); + + Ok(()) + } + + /// Get all log files matching our prefix and suffix, sorted by modification + /// time. + /// + /// Returns a vector of (path, modification_time) tuples, oldest first. + fn get_matching_log_files(config: &WriterConfig) -> io::Result> { + let mut files: Vec<_> = fs::read_dir(&config.base_path)? + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() { + let filename = path.file_name()?.to_str()?; + // Only process files matching our prefix and suffix + if filename.starts_with(&config.file_prefix) + && filename.ends_with(&config.file_suffix) + { + let metadata = fs::metadata(&path).ok()?; + let modified = metadata.modified().ok()?; + return Some((path, modified)); + } + } + None + }) + .collect(); + + // Sort by modification time (oldest first) + files.sort_by_key(|(_, modified)| *modified); + + Ok(files) + } + + /// Remove all log files older than the configured max age. + /// + /// Only files matching the configured prefix and suffix are removed. + /// Other files in the directory are left alone. + fn trim_old_logs_internal(config: &WriterConfig, state: &WriterState) -> io::Result<()> { + let now = SystemTime::now(); + let files = Self::get_matching_log_files(config)?; + + for (path, modified) in files { + // Skip the current file + if path == state.current_path { + continue; + } + + // Check if file is older than max age + if let Ok(duration) = now.duration_since(modified) { + if duration.as_secs() > config.max_age_seconds { + let _ = fs::remove_file(path); + } + } + } + + Ok(()) + } + + /// Enforce total size limit across all log files. + /// + /// If the total size of all matching log files exceeds + /// max_total_size_bytes, remove the oldest files until the total is + /// below the limit. + fn enforce_total_size_limit_internal( + config: &WriterConfig, + state: &WriterState, + ) -> io::Result<()> { + let max_total = config.max_total_size_bytes; + + let files = Self::get_matching_log_files(config)?; + + // Calculate total size of all log files + let mut total_size: u64 = 0; + for (path, _) in &files { + if let Ok(metadata) = fs::metadata(path) { + total_size += metadata.len(); + } + } + + // If under limit, nothing to do + if total_size <= max_total { + return Ok(()); + } + + // Remove oldest files until we're under the limit + // Files are already sorted by modification time (oldest first) + for (path, _) in files { + // Don't remove the current file + if path == state.current_path { + continue; + } + + if let Ok(metadata) = fs::metadata(&path) { + let file_size = metadata.len(); + let _ = fs::remove_file(&path); + total_size = total_size.saturating_sub(file_size); + + // Check if we're now under the limit + if total_size <= max_total { + break; + } + } + } + + Ok(()) + } +} + +impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for SizeAndDateRollingWriter { + type Writer = SizeAndDateRollingWriterHandle<'a>; + + fn make_writer(&'a self) -> Self::Writer { + SizeAndDateRollingWriterHandle { + config: &self.config, + state: &self.state, + _phantom: std::marker::PhantomData, + } + } +} + +pub(super) struct SizeAndDateRollingWriterHandle<'a> { + config: &'a WriterConfig, + state: &'a Mutex>, + _phantom: std::marker::PhantomData<&'a ()>, +} + +impl<'a> Write for SizeAndDateRollingWriterHandle<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut state = self.state.lock().unwrap(); + + // Check if rotation is needed + SizeAndDateRollingWriter::rotate_internal(self.config, &mut state, true)?; + + // Write to file (state must be initialized after rotation) + state.as_mut().unwrap().current_file.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + let mut state = self.state.lock().unwrap(); + if let Some(s) = state.as_mut() { + s.current_file.flush() + } else { + Ok(()) + } + } +} + +#[cfg(test)] +impl SizeAndDateRollingWriter { + /// Manually trigger log rotation if conditions are met. + /// + /// This will rotate the current log file if the time period has changed. + fn roll(&self) -> io::Result<()> { + let mut state = self.state.lock().unwrap(); + Self::rotate_internal(&self.config, &mut state, true) + } + + /// Manually trigger cleanup of old log files. + /// + /// This removes all log files older than the configured max age, keeping + /// only files that match the configured prefix and suffix. + fn trim(&self) -> io::Result<()> { + let state = self.state.lock().unwrap(); + if let Some(ref state) = *state { + Self::trim_old_logs_internal(&self.config, state) + } else { + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use std::io::Write; + + use tempfile::tempdir; + use tracing_subscriber::fmt::MakeWriter; + + use super::*; + + #[test] + fn test_rotation_file_naming() { + let temp_dir = tempdir().unwrap(); + let log_path = temp_dir.path(); + + let _writer = SizeAndDateRollingWriter::new( + log_path, + "app".to_owned(), + ".log".to_owned(), + Rotation::HOURLY, + 10 * 1024 * 1024, // 10MB total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + // Check file naming pattern + let log_files: Vec<_> = std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() { + let filename = path.file_name()?.to_str()?.to_owned(); + if filename.starts_with("app") && filename.ends_with(".log") { + Some(filename) + } else { + None + } + } else { + None + } + }) + .collect(); + + // Should have one file with proper naming + assert_eq!(log_files.len(), 1, "Expected exactly one log file"); + + // Check that files follow the expected naming pattern + for filename in &log_files { + assert!( + filename.starts_with("app."), + "Filename should start with prefix: {}", + filename + ); + assert!(filename.ends_with(".log"), "Filename should end with suffix: {}", filename); + } + } + + #[test] + fn test_prefix_suffix_filtering() { + let temp_dir = tempdir().unwrap(); + let log_path = temp_dir.path(); + + // Create some unrelated files + std::fs::write(log_path.join("other.txt"), "unrelated").unwrap(); + std::fs::write(log_path.join("app.txt"), "wrong suffix").unwrap(); + std::fs::write(log_path.join("test.log"), "wrong prefix").unwrap(); + + let writer = SizeAndDateRollingWriter::new( + log_path, + "app".to_owned(), + ".log".to_owned(), + Rotation::HOURLY, + 10 * 1024 * 1024, // 10MB total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + let mut handle = writer.make_writer(); + handle.write_all(b"test message\n").unwrap(); + handle.flush().unwrap(); + + // Trigger manual trim + writer.trim().unwrap(); + + // Check that unrelated files still exist + assert!(log_path.join("other.txt").exists(), "Unrelated file should not be removed"); + assert!(log_path.join("app.txt").exists(), "File with wrong suffix should not be removed"); + assert!(log_path.join("test.log").exists(), "File with wrong prefix should not be removed"); + + // App log file should still exist + let app_logs: Vec<_> = std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let filename = entry.file_name().to_str()?.to_owned(); + if filename.starts_with("app.") && filename.ends_with(".log") { + Some(filename) + } else { + None + } + }) + .collect(); + assert_eq!(app_logs.len(), 1, "Expected one app log file"); + } + + #[test] + fn test_manual_roll_and_trim() { + let temp_dir = tempdir().unwrap(); + let log_path = temp_dir.path(); + + let writer = SizeAndDateRollingWriter::new( + log_path, + "manual".to_owned(), + ".log".to_owned(), + Rotation::DAILY, + 10 * 1024 * 1024, // 10MB total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + // Manual roll should work + assert!(writer.roll().is_ok(), "Manual roll should succeed"); + + // Manual trim should work + assert!(writer.trim().is_ok(), "Manual trim should succeed"); + + // Should still have log file + let log_files: Vec<_> = std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.file_name()?.to_str()?.starts_with("manual") { + Some(path) + } else { + None + } + }) + .collect(); + assert!(!log_files.is_empty(), "Should have at least one log file"); + } + + #[test] + fn test_total_size_limit_removes_oldest_files() { + // This test verifies that when the total size of all log files exceeds + // max_total_size_bytes, the oldest files are removed + let temp_dir = tempdir().unwrap(); + let log_path = temp_dir.path(); + + // Manually create several log files with different timestamps (alphabetically + // sorted = oldest first) Total of 240 bytes + std::fs::write(log_path.join("total.2024-01-01-10-00.log"), "x".repeat(80)).unwrap(); + std::thread::sleep(std::time::Duration::from_millis(10)); + std::fs::write(log_path.join("total.2024-01-01-10-01.log"), "y".repeat(80)).unwrap(); + std::thread::sleep(std::time::Duration::from_millis(10)); + std::fs::write(log_path.join("total.2024-01-01-10-02.log"), "z".repeat(80)).unwrap(); + + let count_files = || { + std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.file_name()?.to_str()?.starts_with("total") { + Some(path) + } else { + None + } + }) + .count() + }; + + assert_eq!(count_files(), 3, "Should have 3 log files"); + + // Now create a new writer with 200 byte total limit + // Current total is 240 bytes. The writer will: + // 1. Create a new file with current timestamp + // 2. See total exceeds 200 bytes + // 3. Remove oldest file(s) until under limit + let _writer = SizeAndDateRollingWriter::new( + log_path, + "total".to_owned(), + ".log".to_owned(), + Rotation::MINUTELY, + 200, // 200 byte total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + // After writer creation, we should still have 3 files: + // - Oldest file (10-00) was removed + // - Two middle files (10-01, 10-02) remain + // - New current file was created + let remaining_files = count_files(); + assert_eq!( + remaining_files, 3, + "Should have 3 files: 2 old files + 1 new current file, but have {}", + remaining_files + ); + + // Calculate total size of remaining files + let total_size: u64 = std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.file_name()?.to_str()?.starts_with("total") { + Some(std::fs::metadata(&path).ok()?.len()) + } else { + None + } + }) + .sum(); + + // Total should be 160 bytes (80 + 80 + 0 for new file) + assert!(total_size <= 200, "Total size should be under 200 bytes, but is {}", total_size); + + // Verify the oldest file was removed by checking filenames + let filenames: Vec = std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() { + path.file_name()?.to_str().map(|s| s.to_owned()) + } else { + None + } + }) + .collect(); + + assert!( + !filenames.contains(&"total.2024-01-01-10-00.log".to_owned()), + "Oldest file should have been removed" + ); + assert!( + filenames.iter().any(|f| f.starts_with("total.2024-01-01-10-01")), + "Second file should still exist" + ); + assert!( + filenames.iter().any(|f| f.starts_with("total.2024-01-01-10-02")), + "Third file should still exist" + ); + } + + #[test] + fn test_time_based_rotation_logic() { + // Test that the rotation logic correctly identifies when rotation is needed + let temp_dir = tempdir().unwrap(); + let log_path = temp_dir.path(); + + let writer = SizeAndDateRollingWriter::new( + log_path, + "time".to_owned(), + ".log".to_owned(), + Rotation::DAILY, + 10 * 1024 * 1024, // 10MB total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + // Write some data + let mut handle = writer.make_writer(); + handle.write_all(b"initial log entry\n").unwrap(); + handle.flush().unwrap(); + + // Verify initial state - should have created one file + let initial_files: Vec<_> = std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.file_name()?.to_str()?.starts_with("time") { + Some(path.file_name()?.to_str()?.to_owned()) + } else { + None + } + }) + .collect(); + + assert_eq!(initial_files.len(), 1, "Should have one log file initially"); + + // Verify the file contains our data + let file_content = std::fs::read_to_string(log_path.join(&initial_files[0])).unwrap(); + assert!(file_content.contains("initial log entry")); + } + + #[test] + fn test_old_files_cleanup() { + // Test that files older than one week are removed + let temp_dir = tempdir().unwrap(); + let log_path = temp_dir.path(); + + // Create files with timestamps more than a week old + // We can't easily manipulate file mtimes without external crates, + // but we can verify the cleanup logic doesn't fail + std::fs::write(log_path.join("old.2020-01-01-10-00.log"), "old data").unwrap(); + + // Create a writer which will trigger cleanup + let writer = SizeAndDateRollingWriter::new( + log_path, + "old".to_owned(), + ".log".to_owned(), + Rotation::HOURLY, + 10 * 1024 * 1024, // 10MB total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + // The old file should still exist because we can't manipulate mtime easily + // But we verify that cleanup doesn't crash + writer.trim().unwrap(); + + // Verify we can still write + let mut handle = writer.make_writer(); + handle.write_all(b"new data").unwrap(); + handle.flush().unwrap(); + } + + #[test] + fn test_write_operations() { + // Test that writing works correctly across multiple writes + let temp_dir = tempdir().unwrap(); + let log_path = temp_dir.path(); + + let writer = SizeAndDateRollingWriter::new( + log_path, + "write".to_owned(), + ".log".to_owned(), + Rotation::HOURLY, + 10 * 1024 * 1024, // 10MB total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + // Write multiple times + for i in 0..5 { + let mut handle = writer.make_writer(); + let message = format!("Log entry {}\n", i); + handle.write_all(message.as_bytes()).unwrap(); + handle.flush().unwrap(); + } + + // Verify all writes went to the same file (same hour) + let log_files: Vec<_> = std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.file_name()?.to_str()?.starts_with("write") { + Some(path) + } else { + None + } + }) + .collect(); + + assert_eq!(log_files.len(), 1, "Should have one log file for same time period"); + + // Verify content + let content = std::fs::read_to_string(&log_files[0]).unwrap(); + for i in 0..5 { + assert!(content.contains(&format!("Log entry {}", i))); + } + } + + #[test] + fn test_reopening_existing_file() { + // Test that reopening appends to existing file within same time period + let temp_dir = tempdir().unwrap(); + let log_path = temp_dir.path(); + + // Create first writer and write + let writer1 = SizeAndDateRollingWriter::new( + log_path, + "reopen".to_owned(), + ".log".to_owned(), + Rotation::DAILY, + 10 * 1024 * 1024, // 10MB total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + let mut handle1 = writer1.make_writer(); + handle1.write_all(b"first write\n").unwrap(); + handle1.flush().unwrap(); + drop(writer1); + + // Create second writer (simulating restart within same day) + let writer2 = SizeAndDateRollingWriter::new( + log_path, + "reopen".to_owned(), + ".log".to_owned(), + Rotation::DAILY, + 10 * 1024 * 1024, // 10MB total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + let mut handle2 = writer2.make_writer(); + handle2.write_all(b"second write\n").unwrap(); + handle2.flush().unwrap(); + + // Should still have only one file + let log_files: Vec<_> = std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.file_name()?.to_str()?.starts_with("reopen") { + Some(path) + } else { + None + } + }) + .collect(); + + assert_eq!(log_files.len(), 1, "Should have one log file"); + + // Verify both writes are in the file + let content = std::fs::read_to_string(&log_files[0]).unwrap(); + assert!(content.contains("first write")); + assert!(content.contains("second write")); + } + + #[test] + fn test_total_size_limit_with_multiple_old_files() { + // Test that multiple old files are removed until under limit + let temp_dir = tempdir().unwrap(); + let log_path = temp_dir.path(); + + // Create 5 files, each 50 bytes (total 250 bytes) + for i in 0..5 { + let filename = format!("multi.2024-01-01-10-0{}.log", i); + std::fs::write(log_path.join(filename), "x".repeat(50)).unwrap(); + } + + let count_files = || { + std::fs::read_dir(log_path) + .unwrap() + .filter(|entry| { + if let Ok(entry) = entry { + if let Some(name) = entry.file_name().to_str() { + return name.starts_with("multi"); + } + } + false + }) + .count() + }; + + assert_eq!(count_files(), 5, "Should start with 5 files"); + + // Create writer with 100 byte limit (should keep only 2 old files + new + // current) + let _writer = SizeAndDateRollingWriter::new( + log_path, + "multi".to_owned(), + ".log".to_owned(), + Rotation::MINUTELY, + 100, // 100 byte total size limit + 7 * 24 * 60 * 60, // 1 week in seconds + ) + .unwrap(); + + // Should have removed oldest files + let remaining = count_files(); + assert!(remaining <= 3, "Should have removed multiple old files to stay under limit"); + + // Verify total size is under limit + let total_size: u64 = std::fs::read_dir(log_path) + .unwrap() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.file_name()?.to_str()?.starts_with("multi") { + Some(std::fs::metadata(&path).ok()?.len()) + } else { + None + } + }) + .sum(); + + assert!(total_size <= 100, "Total size should be under 100 bytes, but is {}", total_size); + } +} diff --git a/bindings/matrix-sdk-ffi/src/tracing.rs b/bindings/matrix-sdk-ffi/src/platform/tracing.rs similarity index 94% rename from bindings/matrix-sdk-ffi/src/tracing.rs rename to bindings/matrix-sdk-ffi/src/platform/tracing.rs index 26c039751..871fbb28f 100644 --- a/bindings/matrix-sdk-ffi/src/tracing.rs +++ b/bindings/matrix-sdk-ffi/src/platform/tracing.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + #[cfg(feature = "sentry")] use std::borrow::ToOwned; use std::{ diff --git a/bindings/matrix-sdk-ffi/src/qr_code.rs b/bindings/matrix-sdk-ffi/src/qr_code.rs index d833ddf99..0158f26ba 100644 --- a/bindings/matrix-sdk-ffi/src/qr_code.rs +++ b/bindings/matrix-sdk-ffi/src/qr_code.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::sync::Arc; use matrix_sdk::authentication::oauth::{ diff --git a/bindings/matrix-sdk-ffi/src/room/mod.rs b/bindings/matrix-sdk-ffi/src/room/mod.rs index 72f730589..5a22cafc5 100644 --- a/bindings/matrix-sdk-ffi/src/room/mod.rs +++ b/bindings/matrix-sdk-ffi/src/room/mod.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::{collections::HashMap, fs, path::PathBuf, pin::pin, sync::Arc}; use anyhow::{Context, Result}; diff --git a/bindings/matrix-sdk-ffi/src/room/power_levels.rs b/bindings/matrix-sdk-ffi/src/room/power_levels.rs index f206edffb..b14e21e69 100644 --- a/bindings/matrix-sdk-ffi/src/room/power_levels.rs +++ b/bindings/matrix-sdk-ffi/src/room/power_levels.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::collections::HashMap; use anyhow::Result; diff --git a/bindings/matrix-sdk-ffi/src/room/room_info.rs b/bindings/matrix-sdk-ffi/src/room/room_info.rs index f24ccde5a..a8ebe3c82 100644 --- a/bindings/matrix-sdk-ffi/src/room/room_info.rs +++ b/bindings/matrix-sdk-ffi/src/room/room_info.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::sync::Arc; use matrix_sdk::{EncryptionState, RoomState}; diff --git a/bindings/matrix-sdk-ffi/src/room_alias.rs b/bindings/matrix-sdk-ffi/src/room_alias.rs index 1ab5e5311..300747f0c 100644 --- a/bindings/matrix-sdk-ffi/src/room_alias.rs +++ b/bindings/matrix-sdk-ffi/src/room_alias.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use matrix_sdk::RoomDisplayName; /// Verifies the passed `String` matches the expected room alias format: diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs index 3ca3b3edd..2e77fd181 100644 --- a/bindings/matrix-sdk-ffi/src/room_directory_search.rs +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -1,4 +1,3 @@ -// Copyright 2024 Mauro Romito // Copyright 2024 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -10,7 +9,7 @@ // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and +// See the License for that specific language governing permissions and // limitations under the License. use std::{fmt::Debug, sync::Arc}; diff --git a/bindings/matrix-sdk-ffi/src/room_list.rs b/bindings/matrix-sdk-ffi/src/room_list.rs index 199810c1d..953497581 100644 --- a/bindings/matrix-sdk-ffi/src/room_list.rs +++ b/bindings/matrix-sdk-ffi/src/room_list.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + #![allow(deprecated)] use std::{fmt::Debug, mem::MaybeUninit, ptr::addr_of_mut, sync::Arc, time::Duration}; diff --git a/bindings/matrix-sdk-ffi/src/room_preview.rs b/bindings/matrix-sdk-ffi/src/room_preview.rs index 8b04d708e..b1384c502 100644 --- a/bindings/matrix-sdk-ffi/src/room_preview.rs +++ b/bindings/matrix-sdk-ffi/src/room_preview.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use anyhow::Context as _; use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, Client}; use ruma::room::{JoinRuleSummary, RoomType as RumaRoomType}; diff --git a/bindings/matrix-sdk-ffi/src/session_verification.rs b/bindings/matrix-sdk-ffi/src/session_verification.rs index 6e0144acd..54535fbad 100644 --- a/bindings/matrix-sdk-ffi/src/session_verification.rs +++ b/bindings/matrix-sdk-ffi/src/session_verification.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::sync::{Arc, RwLock}; use futures_util::StreamExt; diff --git a/bindings/matrix-sdk-ffi/src/store.rs b/bindings/matrix-sdk-ffi/src/store.rs index 5ac81a0ac..9703635e0 100644 --- a/bindings/matrix-sdk-ffi/src/store.rs +++ b/bindings/matrix-sdk-ffi/src/store.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + #[cfg(feature = "sqlite")] use std::path::PathBuf; diff --git a/bindings/matrix-sdk-ffi/src/task_handle.rs b/bindings/matrix-sdk-ffi/src/task_handle.rs index 958615341..d4fdfad66 100644 --- a/bindings/matrix-sdk-ffi/src/task_handle.rs +++ b/bindings/matrix-sdk-ffi/src/task_handle.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use matrix_sdk_common::executor::JoinHandle; use tracing::debug; diff --git a/bindings/matrix-sdk-ffi/src/timeline/configuration.rs b/bindings/matrix-sdk-ffi/src/timeline/configuration.rs index c0ef06562..7916fbd1e 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/configuration.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/configuration.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::sync::Arc; use matrix_sdk_ui::timeline::{ diff --git a/bindings/matrix-sdk-ffi/src/widget.rs b/bindings/matrix-sdk-ffi/src/widget.rs index 20beea119..ade321d5b 100644 --- a/bindings/matrix-sdk-ffi/src/widget.rs +++ b/bindings/matrix-sdk-ffi/src/widget.rs @@ -1,3 +1,17 @@ +// Copyright 2025 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for that specific language governing permissions and +// limitations under the License. + use std::sync::{Arc, Mutex}; use language_tags::LanguageTag;