用Rust保存Windows聚焦图片

用Rust保持Windows聚焦图片

简介

Windows聚焦(Windows Spotlight)是Windows 10里新增的一个锁屏壁纸功能,它会自动下载并随机更换锁屏壁纸。Windows聚焦的照片精美震撼,但遗憾的是Windows聚焦只能在锁屏时使用,无法在桌面背景中使用。
Windows聚焦

本文使用Rust语言开发了一个Windows聚焦图片的保持工具,可自动保持Windows聚焦图片。

程序源代码可以访问https://github.com/leexgone/spotlight_save下载。

Windows聚焦存储

Windows聚焦自动下载图片,下载的图片会临时存储在用户目录下:
Windows聚焦图片存储目录

Windows聚焦的存储目录为:C:\Users\XXX\AppData\Local\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets

其中,XXX为用户名,该目录下字母与数字组成的无扩展名的文件即为图片文件。

程序设计思路

这里我希望能够开发一个程序,自动扫描Windows聚焦存储目录下的文件,发现有新的图片时拷贝存储到指定的图片目录中,图片的存储目录,可以在Windows背景图片中作为幻灯片放映的目录使用。

程序会被配置到Windows计划任务中,在开机时或定期执行,如果存储的图片较多的话,我们希望能够把历史图片归档到子目录中。

最终实现的工具命令如下:

PS> spotlight_save.exe -help
spotlight_save 1.0.0
Steven Lee <leexgone@163.com>
Save splotlight images in Win10.

USAGE:
    spotlight_save.exe [FLAGS] [DIR]

FLAGS:
    -a, --archive    Archive images by year
    -h, --help       Prints help information
    -V, --version    Prints version information
    -v, --verbose    Use verbose output

ARGS:
    <DIR>    Target image dir. Default dir is '${HOME}/Pictures/Spotlight/'

代码开发

创建一个Rust工程,在Cargo.toml中引入我们需要的第三方包:

[dependencies]

clap = "2.33"
home = "0.5"
image = "0.23"
chrono = "0.4"

main.rs文件中编写主逻辑代码:

use std::process;

use spotlight_save::Config;

fn main() {
    let config = Config::new().unwrap_or_else(|err| {
        eprintln!("Error when parsing arguments: {}", err);
        process::exit(1);
    });

    if let Err(e) = spotlight_save::run(config) {
        eprintln!("Error when saving images: {}", e);
        process::exit(1);
    }
}

lib.rs中编写我们的主要逻辑代码:

use std::{error::Error, fmt::Display, fs, path::PathBuf};

use chrono::{DateTime, Duration, Local};
use clap::{App, Arg};
use image::{GenericImageView, io::Reader};

#[derive(Debug)]
pub struct Config {
    target: PathBuf,
    verbose: bool,
    archive: bool,
}

impl Display for Config {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "[target = {}, verbose = {}, archive = {}]", self.target.display(), self.verbose, self.archive)
    }
}

impl Config {
    pub fn new() -> Result<Config, String> {
        let matches = App::new("spotlight_save")
                        .version("1.0.0")
                        .author("Steven Lee <leexgone@163.com>")
                        .about("Save splotlight images in Win10.")
                        .arg(Arg::with_name("DIR")
                            .help("Target image dir. Default dir is '${HOME}/Pictures/Spotlight/'")
                            .index(1))
                        .arg(Arg::with_name("verbose")
                            .short("v")
                            .long("verbose")
                            .help("Use verbose output"))
                        .arg(Arg::with_name("archive")
                            .short("a")
                            .long("archive")
                            .help("Archive images by year"))
                        .get_matches();

        let verbose = matches.is_present("verbose");
        let archive = matches.is_present("archive");

        let target = if let Some(dir) = matches.value_of("DIR") {
            PathBuf::from(dir)
        } else {
            let home_dir = home::home_dir().unwrap();
            let picture_dir = home_dir.join("Pictures");
            if !picture_dir.is_dir() {
                let msg = format!("Can not find dir '{}'", picture_dir.display());
                return Err(msg);
            }

            picture_dir.join("Spotlight")
        };

        if !target.is_dir() {
            let msg = format!("target dir '{}' does not exist", target.display());
            return Err(msg);
        }

        Ok(Config {
            target,
            verbose,
            archive,
        })
    }
}

macro_rules! log {
    ($enabled:expr) => {
        {if $enabled { println!(); }}
    };
    ($enabled:expr, $($arg:tt)*) => {
        {if $enabled { println!($($arg)*); }}
    };
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    save_images(&config)?;
    if config.archive {
        archive_images(&config)?;
    }

    Ok(())
}

fn get_spotlight_dir() -> Result<PathBuf, Box<dyn Error>> {
    let home_dir = home::home_dir().unwrap();
    let package_dir = home_dir.join("AppData\\Local\\Packages");

    for entry in package_dir.read_dir()? {
        let entry = entry?;
        let path = entry.path();

        if !path.is_dir() {
            continue;
        }

        let pathname = path.file_name().unwrap();
        let pathname = pathname.to_str().unwrap();

        if pathname.starts_with("Microsoft.Windows.ContentDeliveryManager_") {
            let image_dir = path.join("LocalState\\Assets");

            return  Ok(image_dir);
        }
    }

    let err = std::io::Error::new(std::io::ErrorKind::NotFound, String::from("Can not find Spotlight image dir"));
    Err(Box::new(err))
}

fn save_images(config: &Config) -> Result<(), Box<dyn Error>> {
    let spotlight_dir = get_spotlight_dir()?;
    log!(config.verbose, "Scan spotlight dir: {}", spotlight_dir.display());

    let mut count = 0;
    for entry in spotlight_dir.read_dir()? {
        let entry = entry?;
        let path = entry.path();
        if !path.is_file() {
            continue;
        }

        if save_image(config, &path) {
            count += 1;
        }
    }

    log!(config.verbose, "{} images saved!", count);

    Ok(())
}

fn save_image(config: &Config, filepath: &PathBuf) -> bool {
    log!(config.verbose, "Scan file: {}...", filepath.display());

    let reader = if let Ok(reader) = Reader::open(filepath) {
        reader
    } else {
        return false;
    };
    let reader = if let Ok(reader) = reader.with_guessed_format() {
        reader
    } else {
        return false;
    };
    let format = if let Some(format) = reader.format() {
        format
    } else {
        return false;
    };
    let image = if let Ok(image) = reader.decode() {
        image
    } else {
        return false;
    };

    if image.width() < image.height() || image.width() < 800 || image.height() < 600 {
        return false;
    }

    let ext = format.extensions_str().first().unwrap();
    let mut filename = String::from(filepath.file_name().unwrap().to_str().unwrap());
    filename.push_str(".");
    filename.push_str(*ext);

    let target_file = config.target.join(filename);
    if target_file.exists() {
        return false;
    }

    log!(config.verbose, "Saving image: {} ...", target_file.display());

    fs::copy(filepath, target_file).is_ok()
}

fn archive_images(config: &Config) -> Result<(), Box<dyn Error>> {
    log!(config.verbose, "Archive images in dir: {}", config.target.display());

    let timeline = Local::today() - Duration::days(365);

    for entry in config.target.read_dir()? {
        let entry = entry?;
        let path = entry.path();
        if !path.is_file() {
            continue;
        }

        if let Ok(metadata) = entry.metadata() {
            let filetime = if let Ok(modified) = metadata.modified() {
                modified
            } else if let Ok(created) = metadata.created() {
                created
            } else {
                continue;
            };
            let filedate = DateTime::from(filetime).date();

            if filedate < timeline {    
                log!(config.verbose, "archive file: {} ...", path.display());
                
                let year = filedate.format("%Y").to_string();
                let dir = config.target.join(year);
                if !dir.exists() {
                    fs::create_dir(&dir)?;
                }
                let bak_file = dir.join(path.file_name().unwrap());
                fs::copy(&path, &bak_file)?;
                fs::remove_file(&path)?;
            }
        }
    }

    Ok(())
}

使用cargo build --release编译生成工具。

配置计划任务并应用为壁纸

为了定制保持图片,我们在Windows计划任务中配置一个计划任务执行程序,这里我选择开机时执行。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在Windows背景壁纸中设置幻灯片放映,指定存储照片的目录即可。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值