Поиск-фильтр по сообщениям скайпа

от автора

Вчера внезапно узнал, что логи скайпа хранятся в .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 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *