用Rust保持Windows聚焦图片
简介
Windows聚焦(Windows Spotlight)是Windows 10里新增的一个锁屏壁纸功能,它会自动下载并随机更换锁屏壁纸。Windows聚焦的照片精美震撼,但遗憾的是Windows聚焦只能在锁屏时使用,无法在桌面背景中使用。
本文使用Rust语言开发了一个Windows聚焦图片的保持工具,可自动保持Windows聚焦图片。
程序源代码可以访问https://github.com/leexgone/spotlight_save下载。
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背景壁纸中设置幻灯片放映,指定存储照片的目录即可。