qair 0.7.0

Send/receive files with builtin HTTP server and terminal QR code.
Documentation
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Creates `file.1`, `file.2`, ... if `file` already existed.

use std::fs::{File, OpenOptions};
use std::io::ErrorKind;

/// Creates a file but changes filename to avoid overwriting files if necessary.
///
/// If there is no file with given filename, then the given filename is created and opened.
/// Otherwise, a filename of the form `givenfilename.1` is used instead, and if that already
/// exists, one of the form `givenfilename.2` and so on.
///
/// Returns an I/O error for errors like permission denied, and if the number would become larger
/// than what would fit in a signed 32bit number.
///
/// This function can be called safely even while other threads or programs are creating files.
pub fn create_and_adapt_filename_if_exists(filename: &str) -> Result<File, std::io::Error> {
    let mut counter: i32 = 0;
    let mut open_result = open_file_if_not_exists(filename);
    while is_already_exists_error(&open_result) && counter < std::i32::MAX {
        counter = counter + 1;
        // In the future we'll want to have "image.1.png" instead of "image.png.1".
        let filename_with_counter = format!("{}.{}", filename, counter);
        open_result = open_file_if_not_exists(&filename_with_counter);
    }
    open_result
}

/// Atomically open a file for writing, but only when it does not exists yet.
///
/// If it failed because it already existed, an error value is returned for which
/// `is_already_exists_error` returns true.
fn open_file_if_not_exists(filename: &str) -> Result<File, std::io::Error> {
    OpenOptions::new()
        .write(true)
        .create_new(true) // "true" confusingly means "do not create new if exists"
        .open(filename)
}

/// Returns whether given I/O result is an error that says the target filename already existed
fn is_already_exists_error(result: &Result<File, std::io::Error>) -> bool {
    match result {
        Err(err) => err.kind() == ErrorKind::AlreadyExists,
        Ok(_) => false,
    }
}