From 9852de294e80a9cdad29bd254042f35abbf15213 Mon Sep 17 00:00:00 2001 From: Justin Maximillian Kimlim Date: Thu, 16 Dec 2021 10:46:13 +0700 Subject: [PATCH] feat: add support for `lnk` files --- lib/files.json | 5 +++ src-tauri/Cargo.lock | 20 ++++++++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/files_api.rs | 48 +++++++++++++++++++++++++---- src/Api/directory.ts | 2 ++ src/Components/Open/displayFiles.ts | 21 ++++++++++--- src/Components/Open/open.ts | 42 ++++++++++++++----------- src/Icon/extension/lnk.svg | 1 + src/Typings/fileMetaData.d.ts | 5 +++ 9 files changed, 117 insertions(+), 28 deletions(-) create mode 100644 src/Icon/extension/lnk.svg diff --git a/lib/files.json b/lib/files.json index 7f9d591a..bd50a5de 100644 --- a/lib/files.json +++ b/lib/files.json @@ -268,5 +268,10 @@ "type": "Yarn Package Manager", "fileNames": [".yarnrc", "yarn.lock", ".yarnclean", ".yarn-integrity", "yarn-error.log", ".yarnrc.yml", ".yarnrc.yaml"], "thumbnail": "extension/yarn.svg" + }, + { + "extensions": ["lnk"], + "type": "Windows Shortcut", + "thumbnail": "extension/lnk.svg" } ] diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 78526547..6eed409b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -48,6 +48,7 @@ dependencies = [ "normpath", "notify", "open", + "parselnk", "path-absolutize", "serde", "serde_json", @@ -2155,6 +2156,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parselnk" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d098204d9ef47f80312460c1555a152972ea9f0a67487a77b2ab7e03fd510d4f" +dependencies = [ + "bitflags", + "byteorder", + "chrono", + "thiserror", + "widestring", +] + [[package]] name = "path-absolutize" version = "3.0.11" @@ -3838,6 +3852,12 @@ dependencies = [ "cc", ] +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + [[package]] name = "wildmatch" version = "2.1.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index de2c8fab..47261842 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -28,6 +28,7 @@ lazy_static = "1.4.0" font-loader = "0.11.0" glob = "0.3.0" tauri-plugin-vibrancy = { git = "https://github.com/amrbashir/tauri-plugin-vibrancy" } +parselnk = "0.1.0" [dev-dependencies] tokio = {version="1.14", features = ["full"] } diff --git a/src-tauri/src/files_api.rs b/src-tauri/src/files_api.rs index 7169789f..b6290d1c 100644 --- a/src-tauri/src/files_api.rs +++ b/src-tauri/src/files_api.rs @@ -11,6 +11,8 @@ use glob::{glob_with, MatchOptions}; #[cfg(not(target_os = "macos"))] use normpath::PathExt; use notify::{raw_watcher, RawEvent, RecursiveMode, Watcher}; +use parselnk::Lnk; +use std::convert::TryFrom; use std::sync::mpsc::channel; #[cfg(target_os = "windows")] use tauri::api::path::local_data_dir; @@ -20,7 +22,13 @@ use std::os::windows::prelude::*; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; -#[derive(serde::Serialize, Clone)] +#[derive(serde::Serialize, Clone, Debug)] +pub struct LnkData { + file_path: String, + icon: String, +} + +#[derive(serde::Serialize, Clone, Debug)] pub struct FileMetaData { file_path: String, basename: String, @@ -61,6 +69,7 @@ pub struct FolderInformation { number_of_files: u16, files: Vec, skipped_files: Vec, + lnk_files: Vec, } #[derive(serde::Serialize)] @@ -242,26 +251,53 @@ pub async fn read_directory(dir: &Path) -> Result { let mut number_of_files: u16 = 0; let mut files = Vec::new(); let mut skipped_files = Vec::new(); + let mut lnk_files: Vec = Vec::new(); for path in paths { number_of_files += 1; - let file_name = path.unwrap().path().display().to_string(); - let file_info = get_file_properties(file_name.clone()).await; + let file_path = path.unwrap().path().display().to_string(); + let file_info = get_file_properties(file_path.clone()).await; if file_info.is_err() { - skipped_files.push(file_name); + skipped_files.push(file_path); continue; } else { let file_info = file_info.unwrap(); if hide_system_files && file_info.is_system { - skipped_files.push(file_name); + skipped_files.push(file_path); continue; } - files.push(file_info) + if file_info.file_type == "Windows Shortcut" { + let path = std::path::Path::new(&file_info.file_path); + let lnk = Lnk::try_from(path).unwrap(); + let lnk_icon = lnk.string_data.icon_location; + let lnk_icon = match lnk_icon { + Some(icon) => { + let icon = icon.into_os_string().into_string().unwrap(); + let icon_type = file_lib::get_type(icon.clone(), false).await; + if icon_type == "Image" { + icon + } else if icon_type == "Executable" { + extract_icon(icon).await.unwrap_or("".to_string()) + } else { + "".to_string() + } + } + None => "".to_string(), + }; + lnk_files.push(LnkData { + file_path: file_path, + icon: lnk_icon, + }); + files.push(file_info) + } else { + files.push(file_info) + } }; } Ok(FolderInformation { number_of_files, files, skipped_files, + lnk_files, }) } diff --git a/src/Api/directory.ts b/src/Api/directory.ts index a05a12a3..7eb3f092 100644 --- a/src/Api/directory.ts +++ b/src/Api/directory.ts @@ -4,12 +4,14 @@ import { invoke } from '@tauri-apps/api'; import type FileMetaData from '../Typings/fileMetaData'; import { getCurrent } from '@tauri-apps/api/window'; import { UnlistenFn } from '@tauri-apps/api/event'; +import { LnkData } from '../Typings/fileMetaData'; let listener: UnlistenFn; let searchListener: UnlistenFn; interface DirectoryData { files: FileMetaData[]; number_of_files: number; skipped_files: string[]; + lnk_files: LnkData[]; } /** * Invoke Rust command to read information of a directory diff --git a/src/Components/Open/displayFiles.ts b/src/Components/Open/displayFiles.ts index df628870..abbaaf86 100644 --- a/src/Components/Open/displayFiles.ts +++ b/src/Components/Open/displayFiles.ts @@ -6,6 +6,7 @@ import { Select } from '../Files/File Operation/select'; import normalizeSlash from '../Functions/path/normalizeSlash'; import joinPath from '../Functions/path/joinPath'; import getDefaultSort from './defaultSort'; +import { LnkData } from '../../Typings/fileMetaData'; /** * Display files into Xplorer main section * @param {fileData[]} files - array of files of a directory @@ -13,6 +14,7 @@ import getDefaultSort from './defaultSort'; * @param {HTMLElement} onElement - element to append files element * @param {{reveal: boolean, revealDir: boolean}} options - options * @param {boolean} isSearch - if true, files are searched + * @param {LnkData[]} lnk_files - array of lnk files * * @returns {Promise} */ @@ -21,7 +23,8 @@ const displayFiles = async ( dir: string, onElement?: HTMLElement, options?: { reveal: boolean; revealDir: string }, - isSearch?: boolean + isSearch?: boolean, + lnk_files?: LnkData[] ): Promise => { const FilesElement = onElement ?? document.createElement('div'); const preference = await Storage.get('preference'); @@ -76,8 +79,6 @@ const displayFiles = async ( const imageAsThumbnail = (appearance?.imageAsThumbnail ?? 'smalldir') === 'smalldir' ? files.length < 100 : appearance?.imageAsThumbnail === 'yes'; for (const file of files) { - const fileType = file.file_type; - const preview = await fileThumbnail(file.file_path, file.is_dir ? 'folder' : 'file', true, imageAsThumbnail); const fileGrid = document.createElement('div'); fileGrid.className = 'file-grid grid-hover-effect file'; let displayName: string; @@ -114,6 +115,18 @@ const displayFiles = async ( fileGrid.dataset.isdir = String(file.is_dir); if (file.time_deleted) fileGrid.dataset.trashDeletionDate = String(file.time_deleted); if (file.is_trash) fileGrid.dataset.realPath = escape(joinPath(file.original_parent, file.basename)); + let preview: string; + if (file.file_type === 'Windows Shortcut') { + fileGrid.dataset.isLnk = 'true'; + for (const lnk of lnk_files) { + if (file.file_path === lnk.file_path) { + preview = await fileThumbnail(lnk.icon, 'file', true, true); + break; + } + } + } else { + preview = await fileThumbnail(file.file_path, file.is_dir ? 'folder' : 'file', true, imageAsThumbnail); + } fileGrid.dataset.path = escape(normalizeSlash(file.file_path)); fileGrid.dataset.isSystem = String(file.is_system); @@ -136,7 +149,7 @@ const displayFiles = async ( )}` : `` } - ${fileType} + ${file.file_type} `; FilesElement.appendChild(fileGrid); } diff --git a/src/Components/Open/open.ts b/src/Components/Open/open.ts index dc64afee..9b331b6a 100644 --- a/src/Components/Open/open.ts +++ b/src/Components/Open/open.ts @@ -108,25 +108,31 @@ const OpenDir = async (dir: string, reveal?: boolean, forceOpen = false): Promis stopLoading(); return; } - directoryInfo.getFiles().then(async (files) => { - UpdateInfo('number-of-files', `${files.number_of_files - files.skipped_files.length} files`); - if (!files.files.length) { - MAIN_ELEMENT.classList.add('empty-dir-notification'); - MAIN_ELEMENT.innerText = 'This folder is empty.'; - stopLoading(); - } else { - await displayFiles(files.files, dir, MAIN_ELEMENT, { + const files = await directoryInfo.getFiles(); + UpdateInfo('number-of-files', `${files.number_of_files - files.skipped_files.length} files`); + if (!files.files.length) { + MAIN_ELEMENT.classList.add('empty-dir-notification'); + MAIN_ELEMENT.innerText = 'This folder is empty.'; + stopLoading(); + } else { + await displayFiles( + files.files, + dir, + MAIN_ELEMENT, + { reveal, revealDir: normalizeSlash(dir), - }); - stopLoading(); - updateTheme('grid'); - LOAD_IMAGE(); - changeWindowTitle(getBasename(getDirname(dir))); - console.timeEnd(dir); - if (!isReload) directoryInfo.listen(() => reload()); - } - }); + }, + null, + files.lnk_files + ); + stopLoading(); + updateTheme('grid'); + LOAD_IMAGE(); + changeWindowTitle(getBasename(getDirname(dir))); + console.timeEnd(dir); + if (!isReload) directoryInfo.listen(() => reload()); + } } else { directoryInfo = new DirectoryAPI(dir); if (!(await directoryInfo.exists())) { @@ -141,7 +147,7 @@ const OpenDir = async (dir: string, reveal?: boolean, forceOpen = false): Promis MAIN_ELEMENT.innerText = 'This folder is empty.'; stopLoading(); } else { - await displayFiles(files.files, dir, MAIN_ELEMENT); + await displayFiles(files.files, dir, MAIN_ELEMENT, null, null, files.lnk_files); stopLoading(); updateTheme('grid'); LOAD_IMAGE(); diff --git a/src/Icon/extension/lnk.svg b/src/Icon/extension/lnk.svg new file mode 100644 index 00000000..09e49fa6 --- /dev/null +++ b/src/Icon/extension/lnk.svg @@ -0,0 +1 @@ +file_type_lnk \ No newline at end of file diff --git a/src/Typings/fileMetaData.d.ts b/src/Typings/fileMetaData.d.ts index 157aab02..5bf96457 100644 --- a/src/Typings/fileMetaData.d.ts +++ b/src/Typings/fileMetaData.d.ts @@ -20,4 +20,9 @@ interface FileMetaData { last_accessed?: SystemTime; created?: SystemTime; } + +export interface LnkData { + file_path: string; + icon: string; +} export default FileMetaData;