Сегодня посмотрел хабру, нашел тему, посвященную описанию самой базы — тема, а также по восстановлению этой самой базы — тема и упоминание программки 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 from messages 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/talk/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-подобном формате необязательно, да и сам фильтр можно было делать запросом — но это же хаб «ненормальное программирование», нужно же добавить что-нибудь неочевидное.
Остановка завершена.
В принципе, это все.
Спасибо за внимание!
ссылка на оригинал статьи http://habrahabr.ru/post/238395/
Добавить комментарий