Пошук-фільтр за повідомленнями скайпу

Вчора раптово дізнався, що логи скайпу зберігаються в .sqlite. Чудово, подумав я, буде заняття на вихідний.
Сьогодні подивився хабру, знайшов тему, присвячену опису самої бази — тема, а також по відновленню цієї самої бази — тема і згадка програмки SkypeLogViewer. Чудово, подумав я, пора писати черговий обдовбаний велосипед.
Ідея проста: вибірка і фільтрація чатів через lua — для тих, хто бажає трошки попрактикуватися у використанні lua, sql-запитах і lua-аналога linq, а також тим, кого не влаштовує стандартний пошук скайпа. Сам додаток написано на C#(WPF).
Що вийшло — дивіться під катом.


Отже, почнемо з простого — підключення бібліотек, необхідних для роботи з lua і sqlite.

Вибір припав на NLua і System.Data.Sqlite відповідно. Для установки використовуємо NuGet.

install-package nlua
install-package system.data.sqlite

Для зручності і на всяк пожежний робимо невеликий клас-wrapper для lua

public class LuaLogic
{
public Lua lua = new Lua();

//Використання цієї функції дозволяє зарегестрировать public - метод класу C# для використання з lua
public void reg(object target, string funcname)
{
try
{
lua.RegisterFunction(funcname, target, target.GetType().GetMethod(funcname));
}
catch (Exception ex)
{

}
}

//Виклик lua-функції з шарпа
public object[] call(string lua_func, params object[] args)
{
try
{
var func = lua[lua_func] as LuaFunction;
return func.Call(args);
}
catch (Exception ex)
{
return null;
}
}
}


І так, я в курсі, що багато хто вважають, що Exception зобов'язаний виводитися — ось тільки потреби в цьому в даному конкретному випадку не бачу.

Розмітку для gui викладати не буду, бо ось опис використовуваних у коді елементів GUI:

output — RichTextBox, для виведення різного роду інформації, наприклад, вульгарних жартів або ascii-арту
runlua — Button, для виконання lua-коду. Взагалі-то можна підвісити на редагування файлу за допомогою FileSystemWatcher'а, але це вже на любителя
accounts — ComboBox, який буде виводитися список користувачів скайпу, коли-небудь логинившихся на комп'ютері

Тепер перейдемо, власне, до коду. Почнемо з допоміжних функцій.


static LuaLogic logic = new LuaLogic();
public string current_path = "";
private List<Dictionary<string, object>> data;


//Виконання запиту до бази. Повертає дані в більш-менш зручному для роботи форматі.
private List<Dictionary<string, object>> _query(string comm)
{
var result = new List<Dictionary<string, object>>();
using (var db = new SQLiteConnection(@"data source=" + current_path))
{
db.Open();
using (var command = new SQLiteCommand(comm, db))
{
command.CommandTimeout = 999;
using (var reader = використовувати command.executereader())
{
while (reader.Read())
{
result.Add(Enumerable.Range(0, reader.FieldCount)
.ToDictionary(
reader.GetName,
reader.GetValue));
}
}
}
db.Close();
}
return result;
}


//Функція для упаковки результату вибірки в зрозумілий lua формат. На жаль, як працювати з List<Dictionary<string, object>> через lua так і не допер. Перетворює колекцію у lua-таблицю, состоящуюю з lua-таблиць.
public LuaTable genTable(List<Dictionary<string, object>> d)
{
logic.lua.NewTable("datatable");
var table = logic.lua.GetTable("datatable");
for(int i=0; i<d.Count; i++)
{
logic.lua.NewTable("f");
table[i] = logic.lua.GetTable("f");
foreach (var entry in d[i])
{
((LuaTable) table[i])[entry.Key] = entry.Value;
}
}
return table;
}


/*** Функції, що викликаються з lua ***/

//Власне, запит до БД. Виклик переніс на бік lua для більшої зручності.
public void scanDB(string request=null)
{
if(data!=null)
data.Clear();

data = new List<Dictionary<string, object>>();
data = _query(request??"select from_dispname,body_xml,timestamp messages from order by timestamp desc");
genTable(data);
}

//Виведення даних у RichTextBox
public void _print(object obj)
{
Dispatcher.Invoke(() =>output.AppendText(obj + "\n"));
}

//Ще 1 варіант виводу даних у RichTextBox. Просто так.
public void _printblock(string text)
{
Dispatcher.Invoke(() => 
output.Document.Blocks.Add(new Paragraph(new Run(text))
{
Margin = new Thickness(0)
}));
}

//Очищення RichTextBox
public void _clear()
{
Dispatcher.Invoke(() => output.Document.Blocks.Clear());
}


А тепер — логіка програми!

public MainWindow()
{
InitializeComponent();

//Отримуємо шлях до налаштувань скайпа. Якщо у вас лежить в іншому місці - ну що ж, допілку лобзиком ніхто не відміняв
var searchpath = Environment.ExpandEnvironmentVariables("%AppData%\\Skype");
var dirs = Directory.GetDirectories(searchpath);
//Трохи ризикований спосіб отримання папок зі списком користувачів, але розумніші і універсальніше не придумав
var userlist = dirs.Where(dir => File.Exists(dir + "\\main.db")).Select(x=>x.Replace(searchpath+"\\", "")).ToList();

accounts.ItemsSource = userlist;
accounts.SelectedItem = accounts.Items[0];

//змінюємо шлях до файлу з логами щодо зміни обраного значення в ComboBox'е
accounts.SelectionChanged += (sender, args) => current_path = Environment.ExpandEnvironmentVariables("%AppData%\\Skype") + "\\" + accounts.SelectedItem + "\\main.db";

if(userlist.Count>0)
current_path = Environment.ExpandEnvironmentVariables("%AppData%\\Skype") + "\\" + userlist[0] + "\\main.db";
else
{
_print("Навіщо тобі читалка логів скайпу, якщо у тебе навіть скайпу нема?");
}

//Реєструємо шарповские функції для виклику з Lua
logic.reg(this, "_print");
logic.reg(this, "_clear");
logic.reg(this, "_printblock");
logic.reg(this, "scanDB");



runlua.Click += (sender, args) =>
{
try
{
new Thread(() =>
{
//Підключаємо 2 скрипта - для роботи з linq-подібними where і select і, власне, основний скрипт. Lua-linq я чесно спер і переробив під свої завдання <a href="http://codea.io/обговорення/discussion/618/linq-for-lua-functional-collection-class/p1">звідси</a>.
logic.lua.DoFile(@"scripts\pseudolinq.lua");
logic.lua.DoFile(@"scripts\script.lua");

//Виклик функції, в якій зберігається lua-логіка. До речі, необов'язково, достатньо додати виклик потрібної функції в одному з підвантажуваних lua-скриптів
logic.call("search_pattern");
}).Start();
}
catch (Exception ex)
{
_printblock(ex.Message);
}
};

//Стара добра заглушка, на випадок, якщо немає необхідності збереження даних і лінь возитися з потоками
Closing += (sender, args) => Process.GetCurrentProcess().Kill();
}


Ну і на закуску — lua-код.

pseudolinq.lua
LinqArray = {}

function LinqArray:new( arr )
Ret = {}
Ret.arr = arr;
setmetatable( Ret , self )
self.__index = self;
return Ret;
end

--[[function LinqArray:init(items)
if items then self:addRange(items) end
end]]--

function LinqArray:add(item)
table.insert(self.arr, item)
end

function LinqArray:addRange(items)
for k,v in ipairs(items) do
self.arr:add(v)
end
end

function LinqArray:where(func)
local results = {};
for k, v in ipairs(self.arr) do
if func(v) then
table.insert(results, v);
end
end

return LinqArray:new(results)
end

function LinqArray:select(func)
local results = {}
for k, v in ipairs(self.arr) do
_print(func(v));
table.insert(results, func(v));
end

return LinqArray:new(results)
end


script.lua

function search_pattern() 
--підчищаємо висновок від попереднього запиту
_clear();
local f = LinqArray:new(datatable);

--сама мякотка - linq-подібний фільтр
local filtered = f:where(function(x) return string.len(x["from_dispname"])>1; end):select(function(x) return x["from_dispname"]; end);

--роздруківка отриманих повідомлень в потрібному форматі. Просто приклад. Використання саме в такому вигляді необов'язково, нежелаемо і взагалі, нерекомендуемо. Якщо що - я попередив.

local i=0;
for i=1,#filtered.arr,1 do
local arg = filtered.arr[i]; 
_printblock(arg["from_dispname"]..": ");
_print(arg["body_xml"]);
end
end

--Власне, запит до бази. Результат зберігається в пам'яті для прискорення роботи. В даному випадку, запит виконується тільки 1 раз, що б виконувати при кожному перезапуску lua, досить прибрати перевірку
if(datatable==nil) then
scanDB("select * from messages limit 100");
end


Зупинюся трохи детальніше на фільтрі.
Взагалі, робити його в linq-подібному форматі необов'язково, та й сам фільтр можна було робити запитом — але це ж хаб «ненормальне програмування», потрібно додати що-небудь неочевидне.
Зупинка завершена.

В принципі, це все.
Спасибі за увагу!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.