nukeop/nuclear

View on GitHub
packages/scanner/src/lib.rs

Summary

Maintainability
Test Coverage
#![forbid(unsafe_code)]

mod error;
mod js;
mod local_track;
mod metadata;
mod profiling;
mod scanner;
mod thumbnails;
use error::ScannerError;

use js::set_properties_from_metadata;
use local_track::LocalTrack;
use neon::prelude::*;
use scanner::{extractor_from_path, visit_directory, visit_file};
use std::collections::LinkedList;
use thumbnails::create_thumbnails_dir;

fn handle_progress<'a>(
    cx: &mut FunctionContext<'a>,
    total_files_to_scan_num: usize,
    index: usize,
    filename: String,
    on_progress_callback: Handle<JsFunction>,
) -> JsResult<'a, JsValue> {
    let js_this = cx.undefined();
    let args = vec![
        cx.number(index as f64).upcast(),
        cx.number(total_files_to_scan_num as f64).upcast(),
        cx.string(filename.clone()).upcast(),
    ];
    on_progress_callback.call(cx, js_this, args)
}

fn handle_error<'a>(
    cx: &mut FunctionContext<'a>,
    error: &ScannerError,
    on_error_callback: Handle<JsFunction>,
) -> JsResult<'a, JsValue> {
    let js_this = cx.undefined();
    let on_error_args = vec![
        cx.string(error.path.clone()).upcast(),
        cx.string(error.message.clone()).upcast(),
    ];
    on_error_callback.call(cx, js_this, on_error_args)
}

fn scan_folders(mut cx: FunctionContext) -> JsResult<JsArray> {
    let folders: Handle<JsArray> = cx.argument(0)?;
    let supported_formats: Handle<JsArray> = cx.argument(1)?;
    let thumbnails_dir: Handle<JsString> = cx.argument(2)?;
    let thumbnails_dir_str = thumbnails_dir.value(&mut cx);
    let on_progress_callback: Handle<JsFunction> = cx.argument(3)?;
    let on_error_callback: Handle<JsFunction> = cx.argument(4)?;
    let result: Handle<JsArray> = cx.empty_array();

    // Copy all the starting folders to a queue, which holds all the folders left to scan
    let supported_formats_vec = supported_formats
        .to_vec(&mut cx)?
        .into_iter()
        .map(|format| format.to_string(&mut cx).unwrap().value(&mut cx))
        .collect::<Vec<String>>();
    let folders_vec = folders.to_vec(&mut cx)?;
    let mut dirs_to_scan_queue: LinkedList<String> = LinkedList::new();
    let mut files_to_scan_queue: LinkedList<String> = LinkedList::new();
    let total_files_to_scan_num;
    for folder in folders_vec {
        let folder_string = folder.to_string(&mut cx)?.value(&mut cx);
        dirs_to_scan_queue.push_back(folder_string);
    }

    // While there are still folders left to scan
    while !dirs_to_scan_queue.is_empty() {
        // Get the next folder to scan
        let folder = dirs_to_scan_queue.pop_front().unwrap();

        // Scan the folder
        visit_directory(
            folder.clone(),
            supported_formats_vec.clone(),
            &mut dirs_to_scan_queue,
            &mut files_to_scan_queue,
        );

        // Call the progress callback
        let this = cx.undefined();
        let args = vec![
            cx.number(0).upcast(),
            cx.number(files_to_scan_queue.len() as f64).upcast(),
            cx.string(folder.clone()).upcast(),
        ];
        on_progress_callback.call(&mut cx, this, args)?;
    }

    // First, create a directory for thumbnails
    create_thumbnails_dir(thumbnails_dir_str.as_str()).unwrap();

    // All directories have been scanned, now scan the files
    total_files_to_scan_num = files_to_scan_queue.len();

    let scanned_local_tracks: Vec<Result<LocalTrack, ScannerError>> = files_to_scan_queue
        .iter()
        .enumerate()
        .map(|(index, file)| {
            let result = visit_file(
                file.clone(),
                extractor_from_path,
                thumbnails_dir_str.as_str(),
            );

            // Send progress back to JS
            handle_progress(
                &mut cx,
                total_files_to_scan_num,
                index,
                file.clone(),
                on_progress_callback.clone(),
            )
            .unwrap();
            return result;
        })
        .collect();

    scanned_local_tracks.iter().for_each(|track| match track {
        Ok(track) => {
            let len = result.len(&mut cx);
            let mut track_js_object = JsObject::new(&mut cx);
            let track_uuid_js_string = cx.string(track.uuid.clone());
            track_js_object
                .set(&mut cx, "uuid", track_uuid_js_string)
                .unwrap();

            set_properties_from_metadata(&mut cx, &mut track_js_object, &track.metadata);

            let track_filename_js_string = cx.string(track.filename.clone());
            track_js_object
                .set(&mut cx, "filename", track_filename_js_string)
                .unwrap();

            let track_path_js_string = cx.string(track.path.clone());
            track_js_object
                .set(&mut cx, "path", track_path_js_string)
                .unwrap();

            let track_local = cx.boolean(true);
            track_js_object.set(&mut cx, "local", track_local).unwrap();

            result.set(&mut cx, len, track_js_object).unwrap();
        }
        Err(error) => {
            handle_error(&mut cx, error, on_error_callback.clone()).unwrap();
        }
    });

    Ok(result)
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
    cx.export_function("scanFolders", scan_folders)?;
    Ok(())
}