2
\$\begingroup\$

Windows 10 has an interesting feature where it will display "fun facts" and images on the lock screen. Occasionally, these images are something I would want to use for a background.

These images are stored in %LocalAppData%\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets.

As I wasn't certain if _cw5n1h2txyewy would be constant on the Microsoft.Windows.ContentDeliveryManager folder, I wanted to just search the %LocalAppData%\Packages directory for Microsoft.Windows.ContentDeliveryManager*, and pull the first directory that matches that pattern.

Then, in that directory, I wanted to copy the images and read a "magic number" from each image, which can vary but determines the image type. In this case, I use the first 4 bytes as 0x89 0x50 0x4E 0x47 (or 0x89 then PNG), or bytes 6 through 9 as 0x4A 0x46 0x49 0x46 (JFIF) or 0x45 0x78 0x69 0x66 (Exif). The first condition would add the png extension, the second would add jpg.

The first function is my detect_file_type, which evaluates the file contents and determines what image type it is:

fn detect_file_type(data : &Vec<u8>) -> Option<String> {
    if data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47 { // ?PNG
        Some("png".to_string())
    } else if data[6] == 0x4A && data[7] == 0x46 && data[8] == 0x49 && data[9] == 0x46 { // JFIF
        Some("jpg".to_string())
    } else if data[6] == 0x45 && data[7] == 0x78 && data[8] == 0x69 && data[9] == 0x66 { // Exif
        Some("jpg".to_string())
    } else { 
        None
    }
}

This is rather simple.

Next, I wanted my directory search function:

fn find_dir(search : &str, path : PathBuf) -> Option<PathBuf> {
    for entry in fs::read_dir(path).unwrap() {
        let path = entry.unwrap().path();
        if path.is_dir() {
            if path.file_name().unwrap().to_str().unwrap().starts_with(search) {
                return Some(path);
            }
        }
    }
    return None;
}

Again, rather simple. We stuck to Option with both of these so that we can handle the error cases (if we need to).

And finally, we have all the critical work:

fn main() {
    let args = 
        App::new("Pull Windows Lock-Screen Pictures")
            .version("0.1")
            .about("Loads the images used for the main Windows 10 Lock-Screen backgrounds to the specified folder (or './out' if not specified).")
            .arg(Arg::with_name("destination")
                .help("The destination directory ('./out' by default)")
                .takes_value(true)
                .required(false))
            .get_matches();

    let my_dirs = Directories::with_prefix("windows_lock_screen_pictures", "Windows_Lock_Screen_Pictures").unwrap();
    let home = my_dirs.bin_home().parent().unwrap().parent().unwrap().join("Packages");
    let dir = find_dir("Microsoft.Windows.ContentDeliveryManager", home).unwrap().join("LocalState").join("Assets");

    for entry in fs::read_dir(dir).unwrap() {
        let path = entry.unwrap().path();
        if !path.is_dir() {
            let data = fs::read(&path).unwrap();
            let path_str = path.display().to_string();
            let file = path.file_name().unwrap().to_str().unwrap().to_string();

            let path_ext =
                match detect_file_type(&data) {
                    Some(path_ext) => {
                        let mut res = ".".to_string();
                        res.push_str(&path_ext);
                        res
                    },
                    _ => "".to_string()
                };

            let mut base_dest_dir = "".to_string();
            let mut default = std::env::current_dir().unwrap().to_str().unwrap().to_string();
            default.push_str("\\out\\");
            let dest_dir = args.value_of("destination").unwrap_or(&default);
            base_dest_dir.push_str(dest_dir);

            if !Path::new(&base_dest_dir).exists() {
                fs::create_dir(Path::new(&base_dest_dir)).expect("Could not create directory");
            }

            base_dest_dir.push_str(&file);
            base_dest_dir.push_str(&path_ext);

            println!("{} -> {}", path_str, base_dest_dir);
            fs::write(Path::new(&base_dest_dir), data).expect("Could not write file");
        }
    }
}

Overall, we kept things rather small, but still allowed flexibility (and most of the safety we needed).

Our whole program is as follows:

extern crate clap; extern crate dirs;

use std::fs::{self};
use std::path::{Path,PathBuf};
use clap::{App,Arg};
use dirs::{Directories};

fn detect_file_type(data : &Vec<u8>) -> Option<String> {
    if data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47 { // ?PNG
        Some("png".to_string())
    } else if data[6] == 0x4A && data[7] == 0x46 && data[8] == 0x49 && data[9] == 0x46 { // JFIF
        Some("jpg".to_string())
    } else if data[6] == 0x45 && data[7] == 0x78 && data[8] == 0x69 && data[9] == 0x66 { // Exif
        Some("jpg".to_string())
    } else { 
        None
    }
}

fn find_dir(search : &str, path : PathBuf) -> Option<PathBuf> {
    for entry in fs::read_dir(path).unwrap() {
        let path = entry.unwrap().path();
        if path.is_dir() {
            if path.file_name().unwrap().to_str().unwrap().starts_with(search) {
                return Some(path);
            }
        }
    }
    return None;
}

fn main() {
    let args = 
        App::new("Pull Windows Lock-Screen Pictures")
            .version("0.1")
            .about("Loads the images used for the main Windows 10 Lock-Screen backgrounds to the specified folder (or './out' if not specified).")
            .arg(Arg::with_name("destination")
                .help("The destination directory ('./out' by default)")
                .takes_value(true)
                .required(false))
            .get_matches();

    let my_dirs = Directories::with_prefix("windows_lock_screen_pictures", "Windows_Lock_Screen_Pictures").unwrap();
    let home = my_dirs.bin_home().parent().unwrap().parent().unwrap().join("Packages");
    let dir = find_dir("Microsoft.Windows.ContentDeliveryManager", home).unwrap().join("LocalState").join("Assets");

    for entry in fs::read_dir(dir).unwrap() {
        let path = entry.unwrap().path();
        if !path.is_dir() {
            let data = fs::read(&path).unwrap();
            let path_str = path.display().to_string();
            let file = path.file_name().unwrap().to_str().unwrap().to_string();

            let path_ext =
                match detect_file_type(&data) {
                    Some(path_ext) => {
                        let mut res = ".".to_string();
                        res.push_str(&path_ext);
                        res
                    },
                    _ => "".to_string()
                };

            let mut base_dest_dir = "".to_string();
            let mut default = std::env::current_dir().unwrap().to_str().unwrap().to_string();
            default.push_str("\\out\\");
            let dest_dir = args.value_of("destination").unwrap_or(&default);
            base_dest_dir.push_str(dest_dir);

            if !Path::new(&base_dest_dir).exists() {
                fs::create_dir(Path::new(&base_dest_dir)).expect("Could not create directory");
            }

            base_dest_dir.push_str(&file);
            base_dest_dir.push_str(&path_ext);

            println!("{} -> {}", path_str, base_dest_dir);
            fs::write(Path::new(&base_dest_dir), data).expect("Could not write file");
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

A couple of general notes.

  • The app directory should be consistent. It’s a UWP app and this is part of the app package family name. Your implementation is fine though.
  • I would take advantage of some strong type instead of ”jpg” and ”png” strings.
  • Speaking of file type detection, you might want to use a library like tree_magic
  • You really shouldn’t unwrap() unless you’re absolutely sure it shouldn’t fail. It’s really user unfriendly and should be reserved for developer errors. Most of your unwraps could fail due to file permissions. In a small app like this, I’d probably go with expect to give a friendlier error message. In something larger, the linked doc contains all the methods you’d expect on a result type.

Some things I like:

  • You quickly embraced idioms, like pattern matching and omitting the return keyword.

Confusion:

We’ve been reviewing each other’s code for a long time. I’m consistently surprised by your propensity to in-line dense logic in preference to extracting methods. I don’t mind the dense logic, I just think you have a general opportunity to name things and raise the level of abstraction. For this app, I expect the code to read like this, hiding away the lower level details.

let outdir = parse_args().or_else(“./out”);
let files = get_files();
copy_files(files, outdir);
\$\endgroup\$
  • \$\begingroup\$ The expect didn't even dawn on me and I used it in two places there already...lol \$\endgroup\$ – Der Kommissar Apr 20 at 1:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.