一场因为新需求引发的填坑——IE书签排序信息的读取

引用

Github Chromium 项目

起因

需求是这样的:需要将IE中的全部书签同步到Edge,并且保留IE中的特定顺序。(PS:不知道为什么,现在还有人有同步IE书签的需求🤔)

因为原始的项目代码是个缝合怪, 程序的本地是基于WPF,一部分书签合并的逻辑在WPF这,另一部分读取IE书签及顺序的代码在一个C++写的Dll中, 还有个一部分调用了另外的C++库来处理一些借口。

接手到这个代码之后,发现真是一层套一层,一环连一环,真像一个中国结 🤣,所以 就动手把所有逻辑用C# 重写了。

分析

Chromium版的Microsoft Edge的书签很好找到

开发版的书签文件路径为:

C:\Users\{用户名}\AppData\Local\Microsoft\Edge Dev\User Data\Default\Bookmarks

稳定版的书签文件路径为:

C:\Users\{用户名}\AppData\Local\Microsoft\Edge\User Data\Default\Bookmarks

Edge的书签文件很好处理,就是一个Json文件,反序列化后处理就完事了。书签的顺序和Children内的对象排序是一样的。

需要注意的是里面的date_added,是一个时间戳,转化为C#的DateTime的方式为:

var dateTime= new DateTime(1601, 1, 1).AddTicks(value * 10); //value即为要转换的值

IE的话,书签在

C:\Users\{用户名}\Favorites

如果不考虑顺序的话,直接递归遍历文件夹内所有Url文件,然后提取出地址就好,问题在这里并没有保存书签在IE内的顺序。

那么顺序在哪里呢?经过一顿的Google,在Chromium 中找到了一个文件
Chromium传送门

由上可知,IE收藏夹的排序信息保存在注册表的以下路径:HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MenuOrder

引用以上连接的一段话:

// If the order of the items in a folder is customized by user, the order is

// recorded in the REG_BINARY value named “Order” of the corresponding key.

// The content of the “Order” value is a raw binary dump of an array of the

// following data structure

// struct {

// uint32_t size; // Note that ITEMIDLIST is variably-sized.

// uint32_t sort_index; // 0 means this is the first item, 1 the second,

// …

// ITEMIDLIST item_id;

// };

// where each item_id should correspond to a favorites link file (*.url) in the current folder.

(ps:不知道这是怎么分析的,希望哪位知道的能指导一二😉)

代码

参考上面的代码就很好写咯,下面直接给出C#的核心代码:
这其中用到的Model为:

    public class IEBookMarkModel
    {
        public string FullName { get; set; }
        public int SortIndex { get; set; }
        public BookMarkType BookMarkType { get; set; }
        public List<IEBookMarkModel> SubBookMark { get; set; }
    }

    public enum BookMarkType
    {
        Unknown,
        URL,
        Folder
    }
        [DllImport("shell32.dll", SetLastError = true, ExactSpelling = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SHGetPathFromIDListW(IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder pszPath);

        public static (bool, List<IEBookMarkModel>,string message) GetBookMarkSort(string registryPath,string path)
        {
            byte[] value = GetOrderFromRegistry(registryPath);
            if (value == null || value.Length == 0) return (false, null,"can not access registry value");
            int valueSize = value.Length;
            var pVaule = Marshal.AllocHGlobal(valueSize);
            Marshal.Copy(value, 0, pVaule, value.Length);

            if (!BinaryRead(ref pVaule, valueSize, ItemCountOffset, out int itemCount)) return (false, null,"can not get item count");

            List<IEBookMarkModel> result = new List<IEBookMarkModel>();
            int baseOffset = ItemListBeginOffset;
            for (int i = 0; i < itemCount; i++)
            {
                if (!BinaryRead(ref pVaule, valueSize,baseOffset+ItemSizeOffset, out int itemSize)
                    || baseOffset + itemSize > valueSize
                    || baseOffset + itemSize <= baseOffset) return (false, null,"can not get item size");
                if (!BinaryRead(ref pVaule, valueSize, baseOffset+ ItemSortIndexOffset, out int sortIndex)) return (false, null,"can not get sort index");

                if (!BinaryRead(ref pVaule, valueSize, baseOffset+ItemIDListOffset, out short idListSize)) return (false, null,"can not get IDList size");

                var pIdList = ReadIDList(ref pVaule, valueSize, baseOffset + ItemIDListOffset, idListSize);
                StringBuilder fullFileName = new StringBuilder(666); //这里其实用不到申请这么多的内存,只是为了666 😁
                //这里使用 SHGetPathFromIDListW 根据IDList获取相关文件名
                if (pIdList == IntPtr.Zero || !SHGetPathFromIDListW(pIdList, fullFileName)) return (false, null, "can not get IDList or SHGetPathFromIDListW failed");

                var bookMarkPath = ($"{path}\\{GetName(fullFileName.ToString())}").ToLower();
                OrderList.Add(bookMarkPath, sortIndex);
                result.Add(new IEBookMarkModel() { FullName = bookMarkPath, SortIndex = sortIndex,BookMarkType=Utilities.GetBookMarkType(bookMarkPath)});
                baseOffset += itemSize;
            }
            Marshal.FreeHGlobal(pVaule);
            return (true,result,"fuck success");

        }
        static IntPtr ReadIDList(ref IntPtr source, int sourceSize, int offset, int idListSize)
        {
            int head = 0;
            while(true)
            {
                short cb = 0;
                if (head > idListSize || !BinaryRead(ref source, sourceSize, offset + head, out cb)) return IntPtr.Zero;
                if (cb == 0) break;
                head += cb;
            }
            return source + offset;
        }
        //这里处理的不怎么好,其实可以直接unsafe来处理
        static bool BinaryRead<T>(ref IntPtr source,int sourceSize,int offset,out T value)
        {
            var type = typeof(T);
            if((type==typeof(ushort)|| type == typeof(short)) && offset+sizeof(ushort)<sourceSize)
            {
                value =(T)(object)Marshal.ReadInt16(source, offset);
                return true;
            }else if((type == typeof(uint) || type == typeof(int)) && offset + sizeof(uint) < sourceSize)
            {
                value = (T)(object)Marshal.ReadInt32(source, offset);
                return true;
            }else if ((type == typeof(ulong) || type == typeof(long)) && offset + sizeof(ulong) < sourceSize)
            {
                value = (T)(object)Marshal.ReadInt64(source, offset);
                return true;
            }
            else
            {
                value = (T)(object)0;
                return false;
            }
        }

        static byte[] GetOrderFromRegistry(string path)
        {
            using(var key=Registry.CurrentUser.OpenSubKey(path))
            {
                return (byte[])key?.GetValue(OrderValueName);
            }
        }

        static string GetName(string path)
        {
            return  Path.GetFileName(path);
        }

结尾

获取完主文件夹的信息之后,剩下的只需要在 HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MenuOrder
中递归搜索收藏文件夹内的排序信息。

如果相关的路径下没有Order的键值,那么就说明该子收藏夹无自定义的排序信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值