65.9K
CodeProject 正在变化。 阅读更多。
Home

我的媒体搜索:查找和欣赏您 PC 上的媒体文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (6投票s)

2021年8月30日

Apache

3分钟阅读

viewsIcon

7650

downloadIcon

254

正在寻找关于“身处困境”的那首很棒的 Killers 歌曲?只需输入“Killers Rut”即可找到

引言

我的媒体搜索是一个有趣的小Windows应用程序,用于查找和享受您的媒体文件。

您可以告诉它要索引的目录,例如图片、音乐和视频...

...它会索引这些文件夹中的文件(以及您不想索引的文件),以支持快速搜索...

...然后您可以搜索有趣的内容...

获得搜索结果列表后,您可以打开文件、打开包含文件夹,并获取Windows为每个文件维护的所有详细元数据。

哇,有大量的元数据!

我的媒体搜索解释

我的媒体搜索是一个Windows Forms应用程序,使用.NET Framework 4.7.2。我想使用.NET 5.0,但我知道的用于获取文件属性和缩略图的现成代码来自Windows API,即Microsoft.WindowsAPICodePack库。

你应该已经预料到了... 我的媒体搜索由 metastrings 数据库提供支持!我为这个应用程序重新设计了metastrings,并使其作为软件具有通用连贯性。

  • 放弃了对MySQL的支持。只有在性能和可伸缩性不是问题的情况下,使用metastrings才有意义。这使得它可以用于向应用程序添加轻量级数据库支持,而不是必须支持成为笨拙的客户端-服务器数据库解决方案的一部分。
  • 随着MySQL的退出,我可以解除字符串的长度限制,因为SQLite没有这样的限制。这意味着您不必使用“长字符串”API,我曾想删除它,但mscript使用了它,所以它仍然存在。目前。
  • 为了巩固其作为小型、易于使用的数据库的一致角色,我为所有字符串值添加了全文索引。不是长字符串,只是Define / SELECT的东西。有了全文索引,metastrings就可以实现我的媒体搜索了。

代码概述

lib项目具有SearchInfo类,该类实现了几乎所有非UI功能。

cmd项目是SearchInfo的概念验证。您可以使用它来索引任意目录、更新索引和执行搜索。请注意,它仅索引您提供的目录,从而擦除对任何其他目录的索引。只是一个概念验证。

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.IO;

namespace fql
{
    class Program
    {
        [STAThread]
        static async Task<int> Main(string[] args)
        {
            if (args.Length < 1)
            {
                Console.WriteLine("Usage: <directory path>");
                return 0;
            }

            string dirPath = args[0].Trim();
            if (!Directory.Exists(dirPath))
            {
                Console.WriteLine("ERROR: Directory does not exist: {0}", dirPath);
                return 1;
            }

#if !DEBUG
            try
#endif
            {
                while (true)
                {
                    Console.WriteLine();
                    Console.WriteLine("Commands: reset, update, search, quit");

                    Console.WriteLine();
                    Console.Write("> ");

                    string line = Console.ReadLine().Trim();
                    if (string.IsNullOrEmpty(line))
                        continue;

                    Console.WriteLine();

                    if (line == "reset")
                    {
                        SearchInfo.Reset();
                        Console.WriteLine("DB reset");
                    }
                    else if (line == "update")
                    {
                        var updateResult = 
                            await SearchInfo.UpdateAsync
                            (
                                new List<string> { dirPath }, 
                                new List<string>(), 
                                OnDirectoryUpdate
                            );
                        Console.WriteLine("DB updated: files added: 
                                {0} - removed: {1} - modified: {2} - indexed: {3}", 
                                          updateResult.filesAdded, 
                                          updateResult.filesRemoved, 
                                          updateResult.filesModified,
                                          updateResult.indexSize);
                    }
                    else if (line.StartsWith("search "))
                    {
                        var results = await SearchInfo.SearchAsync
                                      (line.Substring("search ".Length).Trim());
                        Console.WriteLine($"Search results: {results.Count}");
                        foreach (var result in results)
                            Console.WriteLine(result);
                    }
                    else if (line == "quit")
                    {
                        Console.WriteLine("Quitting...");
                        break;
                    }
                }
            }
#if !DEBUG
            catch (Exception exp)
            {
                Console.WriteLine("EXCEPTION: {0}", exp);
                return 1;
            }
#endif
            Console.WriteLine("All done.");
            return 0;
        }

        static void OnDirectoryUpdate(UpdateInfo update)
        {
            Console.WriteLine(update.ToString());
        }
    }
}

app项目是顶级的Windows Forms应用程序,那里没有什么太有趣的东西,只是通常的Windows Forms东西。

SearchInfo 类

获取文件的所有Windows元数据

public static Dictionary<string, string> GetFileMetadata(string filePath)
{
    Dictionary<string, string> metadata = new Dictionary<string, string>();

    Shell32.Shell shell = new Shell32.Shell();
    Shell32.Folder objFolder = shell.NameSpace(Path.GetDirectoryName(filePath));

    List<string> headers = new List<string>();
    for (int i = 0; i < short.MaxValue; ++i)
    {
        string header = objFolder.GetDetailsOf(null, i);
        if (string.IsNullOrEmpty(header))
            break;

        headers.Add(header);
    }
    if (headers.Count == 0)
        return metadata;

    foreach (Shell32.FolderItem2 item in objFolder.Items())
    {
        if (!filePath.Equals(item.Path, StringComparison.OrdinalIgnoreCase))
            continue;

        for (int i = 0; i < headers.Count; ++i)
        {
            string details = objFolder.GetDetailsOf(item, i);
            if (!string.IsNullOrWhiteSpace(details))
                metadata.Add(headers[i], details);
        }
    }

    return metadata;
} // GetFileMetadata()

此代码是使用.NET Framework而不是.NET 5+的要求之一。如果没有此函数和搜索结果的平铺视图,.NET 5+可能会正常工作。

搜索索引算法

  1. 从所选目录中收集所有文件系统文件路径和上次修改日期
  2. 删除排除目录中的所有文件系统文件路径
  3. 收集所有数据库文件路径和上次修改日期
  4. ProcessFiles: 匹配所有文件路径 - 文件系统和数据库 - 确定要添加到数据库或从数据库中删除哪些
  5. 执行数据库操作以更新索引

这是中心ProcessFiles函数的实现

private static void ProcessFiles(IEnumerable<string> filePaths, DirProcessInfo info)
{
    List<string> filesToAdd = new List<string>();
    List<object> filesToRemove = new List<object>(); // object for direct metastrings use

    foreach (string filePath in filePaths)
    {
        bool inDb = info.filesLastModifiedInDb.ContainsKey(filePath);
        bool inFs = info.filesLastModifiedInFs.ContainsKey(filePath);

        if (inDb && !inFs)
        {
            ++info.filesRemoved;
            filesToRemove.Add(filePath);
            continue;
        }

        if (inFs && !inDb)
        {
            ++info.filesAdded;
            filesToAdd.Add(filePath);
            continue;
        }

        if (!inDb && !inFs) // weird!
        {
            ++info.filesRemoved;
            filesToRemove.Add(filePath);
            continue;
        }

        // else in both

        if (info.filesLastModifiedInDb[filePath] < info.filesLastModifiedInFs[filePath])
        {
            ++info.filesModified;
            filesToAdd.Add(filePath);
            continue;
        }
    }
    info.toDelete = filesToRemove;

    info.toAdd = new List<Tuple<string, long, string>>(filesToAdd.Count);
    foreach (string filePath in filesToAdd)
    {
        string searchData =
            filePath.Substring(UserRoot.Length)
                .Replace(Path.DirectorySeparatorChar, ' ')
                .Replace('.', ' ');
        while (searchData.Contains("  "))
            searchData = searchData.Replace("  ", " ");
        searchData = searchData.Trim();

        long lastModified = info.filesLastModifiedInFs[filePath];
        info.toAdd.Add
        (
            new Tuple<string, long, string>(filePath, lastModified, searchData)
        );
    }
}

用于计算全文索引的searchData字符串的代码会将路径组件分开,剥离文件扩展名,消除双空格并修剪结果。

一旦ProcessFiles确定需要做什么,此代码就会与metastrings交互以完成该操作

using (var ctxt = msctxt.GetContext())        // create the metastrings Context
{
    update.Start("Cleaning search index...", dirProcInfo.toDelete.Count);
    updater?.Invoke(update);
    await ctxt.Cmd.DeleteAsync("files", dirProcInfo.toDelete);

    update.Start("Updating search index...", dirProcInfo.toAdd.Count);
    updater?.Invoke(update);
    Define define = new Define("files", null); // reuse this object, pull allocs out of loops
    foreach (var tuple in dirProcInfo.toAdd)
    {
        define.key = tuple.Item1;
        define.metadata["filelastmodified"] = tuple.Item2;
        define.metadata["searchdata"] = tuple.Item3;

        await ctxt.Cmd.DefineAsync(define);

        ++update.current;
        if ((update.current % 100) == 0)
            updater?.Invoke(update);
    }
}

结论

所以构建应用程序并享受使用它的乐趣。 我认为你会发现它对于挖掘你数千张图片和歌曲以找到你想要的东西很有用。

实现这个应用程序因 metastrings 而变得容易。

我希望你现在有信心向你的应用程序添加全文搜索。

尽情享用!

历史

  • 2021 年 8 月 29 日:初始版本
© . All rights reserved.