Ctrl K
ComunidadGuías…

src/SqliteClipboardImporter.cs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Clipman
{
    internal static class SqliteClipboardImporter
    {
        private const int SQLITE_OK = 0;
        private const int SQLITE_ROW = 100;
        private const int SQLITE_DONE = 101;
        public static bool LooksLikeSqliteDatabase(string path)
        {
            if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) return false;
            try
            {
                var header = new byte[16];
                using (var stream = File.OpenRead(path))
                {
                    if (stream.Read(header, 0, header.Length) != header.Length) return false;
                }
                return Encoding.ASCII.GetString(header) == "SQLite format 3\0";
            }
            catch
            {
                return false;
            }
        }

        public static List<ClipEntry> LoadEntries(string path)
        {
            using (var db = new SqliteDatabase(path))
            {
                if (db.HasTable("formats") && db.HasTable("items"))
                {
                    return LoadTylerClipmanEntries(db);
                }

                if (db.HasTable("Main") && db.HasColumn("Main", "mText"))
                {
                    return LoadDittoEntries(db);
                }
            }

            throw new InvalidDataException("This SQLite database is not a supported clipboard manager database.");
        }

        private static List<ClipEntry> LoadTylerClipmanEntries(SqliteDatabase db)
        {
            var entries = new List<ClipEntry>();
            const string sql =
                "select i.time, f.data " +
                "from items i join formats f on f.clip_id = i.id " +
                "where f.format = 'CF_UNICODETEXT' " +
                "order by i.time asc, i.id asc";
            db.Query(sql, reader =>
            {
                var created = UnixSecondsToMilliseconds(reader.Int64(0));
                var text = DecodeUnicodeText(reader.Blob(1));
                if (string.IsNullOrWhiteSpace(text)) return;
                entries.Add(new ClipEntry
                {
                    Text = text,
                    Group = "Imported from old Clipman",
                    SourceMachine = CurrentMachineName(),
                    CreatedUnixMs = created,
                    LastUsedUnixMs = created
                });
            });
            return entries;
        }

        private static List<ClipEntry> LoadDittoEntries(SqliteDatabase db)
        {
            var entries = new List<ClipEntry>();
            var timeColumn = db.HasColumn("Main", "lDate") ? "lDate" :
                db.HasColumn("Main", "clipOrder") ? "clipOrder" :
                db.HasColumn("Main", "CRC") ? "CRC" :
                string.Empty;
            var hasFullTextData = db.HasTable("Data") &&
                db.HasColumn("Main", "lID") &&
                db.HasColumn("Data", "lID") &&
                db.HasColumn("Data", "lParentID") &&
                db.HasColumn("Data", "strClipBoardFormat") &&
                db.HasColumn("Data", "ooData");
            var timeExpression = timeColumn.Length == 0 ? "0" : "m." + QuoteIdentifier(timeColumn);
            var order = timeColumn.Length == 0 ? "m.rowid" : "m." + QuoteIdentifier(timeColumn);
            var sql = "select m.mText, " + timeExpression;
            if (hasFullTextData)
            {
                sql +=
                    ", (select d.ooData from Data d where d.lParentID = m.lID " +
                    "and d.strClipBoardFormat = 'CF_UNICODETEXT' collate nocase order by d.lID desc limit 1)" +
                    ", (select d.ooData from Data d where d.lParentID = m.lID " +
                    "and d.strClipBoardFormat = 'CF_TEXT' collate nocase order by d.lID desc limit 1)";
            }
            sql += " from Main m order by " + order + " asc";
            db.Query(sql, reader =>
            {
                var description = reader.String(0);
                var text = hasFullTextData
                    ? DecodeDittoText(reader.Blob(2), reader.Blob(3), description)
                    : description.TrimEnd('\0');
                if (string.IsNullOrWhiteSpace(text)) return;
                var created = timeColumn.Length == 0 ? TimeUtil.NowUnixMs() : NormalizePotentialUnixTime(reader.Int64(1));
                entries.Add(new ClipEntry
                {
                    Text = text,
                    Group = "Imported from Ditto",
                    SourceMachine = CurrentMachineName(),
                    CreatedUnixMs = created,
                    LastUsedUnixMs = created
                });
            });
            return entries;
        }

        private static string DecodeDittoText(byte[] unicodeData, byte[] ansiData, string description)
        {
            var unicodeText = unicodeData != null && unicodeData.Length % 2 == 0
                ? DecodeUnicodeText(unicodeData)
                : string.Empty;
            if (!string.IsNullOrWhiteSpace(unicodeText)) return unicodeText;

            var ansiText = ansiData == null || ansiData.Length == 0
                ? string.Empty
                : Encoding.Default.GetString(ansiData).TrimEnd('\0');
            if (!string.IsNullOrWhiteSpace(ansiText)) return ansiText;

            return (description ?? string.Empty).TrimEnd('\0');
        }

        private static long NormalizePotentialUnixTime(long value)
        {
            if (value <= 0) return TimeUtil.NowUnixMs();
            if (value > 1000000000000L) return value;
            if (value > 1000000000L) return value * 1000L;
            return TimeUtil.NowUnixMs();
        }

        private static long UnixSecondsToMilliseconds(long seconds)
        {
            return seconds > 0 ? seconds * 1000L : TimeUtil.NowUnixMs();
        }

        private static string DecodeUnicodeText(byte[] data)
        {
            if (data == null || data.Length == 0) return string.Empty;
            return Encoding.Unicode.GetString(data).TrimEnd('\0');
        }

        private static string CurrentMachineName()
        {
            try { return Environment.MachineName; }
            catch { return string.Empty; }
        }

        private sealed class SqliteDatabase : IDisposable
        {
            private IntPtr handle;

            public SqliteDatabase(string path)
            {
                if (sqlite3_open16(path, out handle) != SQLITE_OK)
                {
                    var message = LastError();
                    Close();
                    throw new InvalidDataException("Could not open SQLite database: " + message);
                }
            }

            public bool HasTable(string name)
            {
                var found = false;
                Query("select name from sqlite_master where type='table' and lower(name)=lower(?)", statement =>
                {
                    statement.BindText(1, name);
                }, reader =>
                {
                    found = true;
                });
                return found;
            }

            public bool HasColumn(string table, string column)
            {
                var found = false;
                Query("pragma table_info(" + QuoteIdentifier(table) + ")", reader =>
                {
                    if (string.Equals(reader.String(1), column, StringComparison.OrdinalIgnoreCase))
                    {
                        found = true;
                    }
                });
                return found;
            }

            public void Query(string sql, Action<RowReader> row)
            {
                Query(sql, null, row);
            }

            public void Query(string sql, Action<Statement> bind, Action<RowReader> row)
            {
                using (var statement = Prepare(sql))
                {
                    if (bind != null) bind(statement);
                    while (true)
                    {
                        var result = sqlite3_step(statement.Handle);
                        if (result == SQLITE_ROW)
                        {
                            row(new RowReader(statement.Handle));
                            continue;
                        }
                        if (result == SQLITE_DONE) break;
                        throw new InvalidDataException("SQLite query failed: " + LastError());
                    }
                }
            }

            private Statement Prepare(string sql)
            {
                IntPtr statement;
                var result = sqlite3_prepare16_v2(handle, sql, -1, out statement, IntPtr.Zero);
                if (result != SQLITE_OK)
                {
                    throw new InvalidDataException("SQLite prepare failed: " + LastError());
                }
                return new Statement(statement);
            }

            private string LastError()
            {
                if (handle == IntPtr.Zero) return "unknown error";
                var ptr = sqlite3_errmsg(handle);
                return ptr == IntPtr.Zero ? "unknown error" : Marshal.PtrToStringAnsi(ptr);
            }

            public void Dispose()
            {
                Close();
            }

            private void Close()
            {
                if (handle == IntPtr.Zero) return;
                sqlite3_close(handle);
                handle = IntPtr.Zero;
            }
        }

        private sealed class Statement : IDisposable
        {
            public IntPtr Handle { get; private set; }

            public Statement(IntPtr handle)
            {
                Handle = handle;
            }

            public void BindText(int index, string value)
            {
                var result = sqlite3_bind_text16(Handle, index, value, -1, new IntPtr(-1));
                if (result != SQLITE_OK)
                {
                    throw new InvalidDataException("SQLite parameter bind failed.");
                }
            }

            public void Dispose()
            {
                if (Handle == IntPtr.Zero) return;
                sqlite3_finalize(Handle);
                Handle = IntPtr.Zero;
            }
        }

        private sealed class RowReader
        {
            private readonly IntPtr statement;

            public RowReader(IntPtr statement)
            {
                this.statement = statement;
            }

            public string String(int column)
            {
                var ptr = sqlite3_column_text16(statement, column);
                if (ptr == IntPtr.Zero) return string.Empty;
                var bytes = sqlite3_column_bytes16(statement, column);
                if (bytes <= 0) return string.Empty;
                var data = new byte[bytes];
                Marshal.Copy(ptr, data, 0, bytes);
                return Encoding.Unicode.GetString(data).TrimEnd('\0');
            }

            public long Int64(int column)
            {
                return sqlite3_column_int64(statement, column);
            }

            public byte[] Blob(int column)
            {
                var bytes = sqlite3_column_bytes(statement, column);
                if (bytes <= 0) return new byte[0];
                var ptr = sqlite3_column_blob(statement, column);
                if (ptr == IntPtr.Zero) return new byte[0];
                var data = new byte[bytes];
                Marshal.Copy(ptr, data, 0, bytes);
                return data;
            }
        }

        private static string QuoteIdentifier(string value)
        {
            return "\"" + (value ?? string.Empty).Replace("\"", "\"\"") + "\"";
        }

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
        private static extern int sqlite3_open16(string filename, out IntPtr db);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern int sqlite3_close(IntPtr db);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr sqlite3_errmsg(IntPtr db);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, EntryPoint = "sqlite3_prepare16_v2")]
        private static extern int sqlite3_prepare16_v2(IntPtr db, string sql, int nByte, out IntPtr statement, IntPtr tail);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern int sqlite3_step(IntPtr statement);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern int sqlite3_finalize(IntPtr statement);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
        private static extern int sqlite3_bind_text16(IntPtr statement, int index, string value, int bytes, IntPtr destructor);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr sqlite3_column_text16(IntPtr statement, int column);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern int sqlite3_column_bytes16(IntPtr statement, int column);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern long sqlite3_column_int64(IntPtr statement, int column);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr sqlite3_column_blob(IntPtr statement, int column);

        [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
        private static extern int sqlite3_column_bytes(IntPtr statement, int column);
    }
}