Разработка

Ragnarök – тёмная тема для Visual Studio

0

Как-то даже странно, что я до сих пор про неё не писал, ибо пользуюсь ею уже много лет. Но вот сегодня натолкнулся на новую многообещающую тему Dark Pastel и подумал, что пора Smile

За основу была взята одноимённая тема изначально опубликованная Томасом Рестрепо для Visual Studio 2005 и 2008. (У него есть и другие темы).

К сожалению, я ещё не встречал популярных тем, включающих в себя раскраску токенов специфичных для Resharper’а, что очень быстро сводит на нет все попытки попробовать что-то отличное от стандартных настроек. (далее…)

RavenDB, dynamic и Excel

0

Интро

Уже давно хотелось пощупать главное нововведение .NET 4 – DLR, да и RavenDB выглядит очень аппетитным, а собранные вместе в одном месте дают возможность реализовать очень интересные штуки. Да и на чём потренироваться у меня есть – мой список дисков, который я веду в Excel. Давно уже хочется отказаться от таблиц и использовать что-то более гибкое.

Итак, далее последует примерный конспект моих мытарств в освоении всего нового, а именно – как работать с приложениями MS Office напрямую, основы использования динамических объектов и работа с RavenDB. На всякий случай, я использовал MS Office 2010 RTM x64, Visual Studio 2010 RTM и RavenDB build 101 (начал я с 100, но там был баг с загрузкой индексов).

Как оседлать Excel

Итак, как же нам получить все интересующие на данные из исходного файлика в формате xlsx? Побродив немного по MSDN и форумам, я заметил два основных подхода: работа как с БД через Jet OLE DB (пример на CodeProject), либо напрямую через компоненты автоматизации (COM-объекты).

Поскольку у меня много мета-информации хранится в комментариях к ячейкам, первый вариант отпадает сразу. Второй вариант тоже не сахар, ибо работа с COM – не самое благодарное занятие.

Компонентная модель

Итак, работаем с COM. Очень поверхностно, работа заключается в создании объекта нужного приложения (в моём случае – Excel), открытия интересующего нас файла и последующего взаимодействия с этим самым файлом. Самое главное – не забыть потом всё это закрыть (и нет, using здесь не помощник).

using Microsoft.Office.Interop.Excel;
<...>
private static void Main()
{
	var excel = new Application();
	try
	{
		Workbook workbook = excel.Workbooks.Open("CDDB.xlsx", ReadOnly: true, Editable: false);
		Worksheet sheet = workbook.Sheets["Игры"];
		foreach (Range row in sheet.Rows)
		{
			var objetToStore = GameConverter(row.Cells);
			<...>
		}
		workbook.Close();
	}
	finally
	{
		excel.Quit();
	}
}

Для сравнения, вот как выглядело открытие файла раньше:

Workbook workbook = excel.Workbooks.Open("CDDB.xlsx", null, true, null, null, null, null, null, null, false, null, null, null, null, null);

Достаём данные

Собственно, обработка данных. В данном примере функция GameConverter принимает одну строку-запись из файла и конвертирует её в некий объект, который будет более наглядно отражать её суть. И тут возникает вопрос, – как это сделать. Лично мне в данном случае не хотелось привязываться к какой-то конкретной объектной модели, поэтому первое, что пришло в голову – использовать анонимные объекты.

private static object GameConverter(dynamic gameRow)
{
	return new
	{
		type = "game",
		title = new
		{
			localized = GetField(gameRow, 1),
			original = GetField(gameRow, 2),
		},
		publisher = GetField(gameRow, 3),
		disks = GetField(gameRow, 4),
		architecture = GetField(gameRow, 5),
		media = GetField(gameRow, 6),
		protection = GetField(gameRow, 7),
		language = GetField(gameRow, 8),
		shippedVersion = GetField(gameRow, 9),
		latestPatch = GetField(gameRow, 10),
		borrowedBy = GetField(gameRow, 11),
		purchaseDate = GetField(gameRow, 12),
		price = GetField(gameRow, 13),
	};
}

private static object GetField(dynamic row, int idx)
{
	return new { value = row[idx].Value2, comment = GetComment(row[idx].Comment) };
}

private static string GetComment(dynamic cell)
{
	return cell == null ? null : cell.Shape.AlternativeText;
}

Хм… выглядит неплохо, но смущает явный копипаст. Ну да ладно, пока что сойдёт. Самое главное – уже сейчас видны преимущества использования динамических объектов, ибо я так и не разобрался, как же просто можно обратиться к полям с данными (что к чему надо кастовать на каком шаге), даже зная, как называются интересующие меня поля.

После прогона с отладчиком, видно, что объект формируется как надо:

Наш анонимный объект в отладчике

Работа с RavenDB

Сначала, почему RavenDB? Потому что это объектное хранилище, да к тому же написанное на .NET для .NET. Идеальный вариант. Работать с ним проще простого: запускаем сервер, инициализируем клиент, подключаемся, работаем.

using Raven.Client;
using Raven.Client.Document;
using Raven.Database.Data;
<...>
private static void Main()
{
<...>
		using (IDocumentStore store = new DocumentStore {Url = "http://localhost:8080/", Credentials = CredentialCache.DefaultNetworkCredentials}.Initialize())
		using (IDocumentSession session = store.OpenSession())
		{
			Worksheet sheet = workbook.Sheets["Игры"];
			foreach (Range row in sheet.Rows)
			{
				var o = GameConverter(row.Cells);
				session.Store(o);
			}
			session.SaveChanges();
		}
<...>
}

Вроде бы всё просто: делаем объекты, пихаем их в базу. Но что это? Падение при попытке сохранить первый же объект?

2_exception

Хм… раз мы не управляем генерацией идентификаторов объектов вручную, RavenDB пытается генерировать их сам на основе имени класса сохраняемого объекта и у нашего анонимного типа оно несколько длинновато. Выхода два: делать дополнительную работу по генерации идентификаторов объектов, либо генерировать объект по-другому. Поскольку лень – двигатель прогресса, мы пойдём вторым путём.

Как очистить базу

Самым неожиданным моментом было обнаружить, что вот так просто нельзя дропнуть всю БД и начать с нового листа. Возможно, есть способ и попроще, но самый простой выход, до которого дошёл я, – это создать индекс, возвращающий все документы, а потом вызвать метод массового удаления, используя этот самый индекс.

private static void InitDb(IDocumentStore store)
{
	try
	{
		store.DatabaseCommands.GetIndex("AllDocuments");
	}
	catch (InvalidOperationException)
	{
		store.DatabaseCommands.PutIndex("AllDocuments", new IndexDefinition {Map = "from doc in docs select new {doc}"});
	}
	store.DatabaseCommands.DeleteByIndex("AllDocuments", new IndexQuery {PageSize = int.MaxValue}, false);
}

Конечно, чтобы удалить все документы, придётся сначала дождаться полной индексации и, возможно, более корректно обрабатывать paging, но для прототипа сойдёт и так.

Как создавать объекты. Попытка №2

Да, анонимные объекты хороши не всегда. Следующий логичный шаг – создание объектной модели. Ничего особенно умного, просто разбить наш анонимный класс на подклассы и дать им имена.

private class Field
{
	public dynamic value;
	public string comment;
}
private class Title
{
	public Field localized;
	public Field original;
}

private class Game
{
	public string type;
	public Title title;
	public Field publisher;
	public Field disks;
	public Field architecture;
	public Field media;
	public Field protection;
	public Field language;
	public Field shippedVersion;
	public Field latestPatch;
	public Field borrowedBy;
	public Field purchaseDate;
	public Field price;
}

Вот, теперь всё работает.

{
	"type":"game",
	"title":{
		"localized":{
			"value":"Fate/stay night",
			"comment":null
		},
		"original":{
			"value":"フェイト/ステイナイト",
			"comment":"FEITO / SUTEI NAITO"
		}
	},
	"publisher":{
		"value":"Type-Moon",
		"comment":null
	},
	"disks":{
		"value":1,
		"comment":null
	},
	"architecture":{
		"value":"PC",
		"comment":null
	},
	"media":{
		"value":"DVD",
		"comment":null
	},
	"protection":{
		"value":"CD",
		"comment":null
	},
	"language":{
		"value":"я",
		"comment":"есть патч англификации"
	},
	"shippedVersion":{
		"value":null,
		"comment":null
	},
	"latestPatch":{
		"value":null,
		"comment":null
	},
	"borrowedBy":{
		"value":null,
		"comment":null
	},
	"purchaseDate":{
		"value":39764,
		"comment":null
	},
	"price":{
		"value":2559.05,
		"comment":null
	}
}

Только что-то как-то слишком много лишнего. Сплошные null’ы. Что-то мне подсказывает, что этого можно избежать, а ключ лежит в использовании DLR и конструировании объектов по мере надобности.

Как создавать объекты в runtime

Итак, ExpandoObject. Новый класс, призванный в паре с dynamic дать разработчиком большую гибкость по работе с объектами во время выполнения программы. Вещь, доступная в таких языках, как Python, Ruby, JavaScript, PowerShell и прочих. Нас же интересует добавление новых полей по мере надобности.

private static dynamic GameConverter(dynamic gameRow)
{
	dynamic game = new ExpandoObject();
	game.type = "game";
	game.title = new ExpandoObject();
	dynamic field = GetField(gameRow, 1);
	if (field != null) game.title.localized = field;
	field = GetField(gameRow, 2);
	if (field != null) game.title.original = field;
	field = GetField(gameRow, 3);
	if (field != null) game.publisher = field;
	field = GetField(gameRow, 4);
	if (field != null) game.disks = field;

	<...>

	return game;
}

private static dynamic GetField(dynamic row, int idx)
{
	dynamic value = row[idx].Value2;
	if (value == null || value.ToString().Trim() == "") return null;

	dynamic field = new ExpandoObject();
	field.value = value;
	dynamic comment = GetComment(row[idx].Comment);
	if (comment != null) field.comment = comment;
	return field;
}

private static string GetComment(dynamic cell)
{
	return cell == null ? null : cell.Shape.AlternativeText;
}

Запускаем. И… это именно то, что надо :)

{
	"type":"game",
	"title":{
		"localized":{
			"value":"Fate/stay night"
		},
		"original":{
			"value":"フェイト/ステイナイト",
			"comment":"Подпись: FEITO / SUTEI NAITO"
		}
	},
	"publisher":{
		"value":"Type-Moon"
	},
	"disks":{
		"value":1
	},
	"architecture":{
		"value":"PC"
	},
	"media":{
		"value":"DVD"
	},
	"protection":{
		"value":"CD"
	},
	"language":{
		"value":"я",
		"comment":"Подпись: есть патч англификации для 2 из 3 путей"
	},
	"purchaseDate":{
		"value":39764
	},
	"price":{
		"value":2559.05
	},
	"Id":"expandoobjects/15"
}

Только вот смотрю я на весь этот копипаст и кажется, что можно это дело улучшить. Глянем в MSDN, ага, так и есть – ExpandoObject реализует интерфейс IDictionary. Отлично!

private static dynamic GameConverter(dynamic gameRow)
{
	dynamic game = new ExpandoObject();
	game.type = "game";
	game.title = new ExpandoObject();
	AppendFieldIfNotNull(game.title, "localized", GetField(gameRow, 1));
	AppendFieldIfNotNull(game.title, "original", GetField(gameRow, 2));
	var fields = new[]
	             	{
	             		"publisher",
	             		"disks",
	             		"architecture",
	             		"media",
	             		"protection",
	             		"language",
	             		"shippedVersion",
	             		"latestPatch",
	             		"borrowedBy",
	             		"purchaseDate",
	             		"price"
	             	};
	for (int i = 3; i < 14; i++)
		AppendFieldIfNotNull(game, fields[i - 3], GetField(gameRow, i));
	return game;
}

private static void AppendFieldIfNotNull(ExpandoObject obj, string field, dynamic value)
{
	if (value == null) return;
	if (value.ToString().Trim() == "") return;

	// ReSharper disable RedundantCast
	(obj as IDictionary<string, object>) = value;
	// ReSharper restore RedundantCast
}

Здесь, кстати, нашёлся небольшой баг в решарпере (кстати, буквально за день до этого я нашёл другой баг – в компиляторе c#, который был гораздо неприятней).

Дальнейшая работа уже не так интересна – написание однотипных конвертеров для каждой страницы, перенос дополнительной мета-информации (цвет записи), правильное конвертирование данных (даты) и перенос сортировок в виде индексов, но в этом уже нет ничего нового.

Чудо, что зовётся AdBlock

0

Думается, что все более-менее “продвинутые” пользователи знают, что одним из первых расширений для Firefox стоит ставить AdBlock Plus. Все знают, что парой щелчков мыши (добавлением подписки) можно избавить себя от подавляющего большинства рекламы в сети. Причём, в любом её проявлении.

Естественно, нельзя сделать абсолютно универсальный инструмент, поэтому иногда придётся дотачивать его под себя. Обычно, это сводится к добавлению простеньких правил для блокирования URL определённого вида, но иногда этого недостаточно.

И вот здесь большинство пользователей сдаётся. Мало кто догадывается, знает и тем более использует расширенные возможности по блокированию и скрытию контента. Особенно мне хочется остановиться на скрытии элементов с помощью CSS-селекторов.

Зачем это надо? Например, иногда бывает проще скрыть элемент, в котором показывается реклама, чем блокировать сотню ссылок, откуда она подтягивается. Или чтобы подправить вид сайта после отсекания некоторых элементов дизайна xD

Для примера, возьмём всеми [не]любимый bash.org.ru. Вот как он выглядит в девственном виде, как его задумали создатели:

Вот так сайт видят пользователи IE

А вот так после включения AdBlock со стандартными правилами:

А так видят сайт большинство пользователей Firefox

Не очень эстетично, правда? А если мы нажмём [Ctrl]+[Shift]+[E] и добавим новое правило скрытия элементов вида bash.org.ru##div.q>div:first-child:not(.vote), то в результате увидим вот это:

А вот так видят этот сайт... педанты

Вот ещё несколько полезных примеров:

icanhascheezburger.com##div#toprightad
icanhascheezburger.com##div#rightskyad
icanhascheezburger.com##div#leftcolumnad

mail.ru##div.rb_body
mail.ru##div.mf_smsTop

iphones.ru##div.rc10
iphones.ru##div#sidebar>noindex:last-of-type
iphones.ru##div#sidebar>noindex:nth-of-type(2)

rutracker.org##div[id^="bn-"]
rutracker.org##div.tCenter

urod.ru##a[href^="http://loadpartners.com"]
r.radikal.ru##embed#id_flbaner
exler.ru##td#Counter
exler.ru##div[id^="Banner"]

membrana.ru##body>table>tbody>tr>td>table+table
umap.ru##td.column-right>div:nth-of-type(3)

##div.item-body>div>a[href*="d.techcrunch.com/ck.php"]>img

Особенно радует, что использовать можно практически все возможности, предоставляемые CSS3. Подробности – в документации по AdBlock.

Mono.SIMD на практике

2

Давно хотел посмотреть, как на деле обстоят дела с потоковой обработкой данных в .NET. И вот сегодня представился случай.

Код: недописанный класс, реализующий MD5. Использовался только один оператор для первого раунда (то, что в референсной реализации называется FF()).

using System;
using CryptoLib.Hashing;
using Mono.Simd;

namespace Test
{
    internal class Program
    {
        private static void Main()
        {
            var md5 = new Md5();
            const uint a = 1;
            const uint b = 2;
            const uint c = 3;
            const uint d = 4;
            var s = new Vector4ui(a, b, c, d);
            var x = new uint[16];
            uint r1 = md5.Op1(x, a, b, c, d, 0, 12, 0);
            Vector4ui r2 = md5.Op1(x, s, 0, 12, 0);

            const uint cycles = 100000000;
            DateTime st = DateTime.Now;
            for (uint i = 0; i < cycles; i++)
                r1 = md5.Op1(x, r1, b, c, d, 0, 12, 0);
            DateTime e1 = DateTime.Now;
            for (uint i = 0; i < cycles; i++)
                r2 = md5.Op1(x, r2, 0, 12, 0);
            DateTime e2 = DateTime.Now;

            int bits = IntPtr.Size*8;
            int ms1 = (e1 - st).Milliseconds;
            int ms2 = (e2 - e1).Milliseconds;

            Console.WriteLine("Running {0}-bit code.", bits);
            Console.WriteLine("Normal: {0}ms", ms1);
            Console.WriteLine("SIMD: {0}ms", ms2);
        }
    }
}

Реализация Md5.Op1() вовсе не замысловата:

public uint Op1(uint[] x,
                uint a, uint b, uint c, uint d,
                byte k, byte s, byte i)
{
    return b + (a + F(b, c, d) + x[k] + T[i]).Rol(s);
}

public Vector4ui Op1(uint[] x, Vector4ui state, byte k, byte s, byte i)
{
    state.X = state.Y + (state.X + F(state.Y, state.Z, state.W) + x[k] + T[i]).Rol(s);
    return state;
}

Среда выполнения:

  • 64-битная Windows 7 RC (build 7100);
  • Core2 Quad Q6600 (поддержка инструкций до SSSE3);
  • .NET Framework CLR v2.0.50727;
  • Mono 2.4 (под Windows есть только 32-битный CLR).

Результаты по трём запускам релизного кода:

Normal (ms)

SIMD (ms)

Runtime

Bitness

331,33

11,33

Mono+

32

320,33

131,33

Mono-

32

857,33

768,67

MS .NET

32

853,67

774,00

MS .NET

64

  • Mono+ – с включенной оптимизацией (-O=simd);
  • Mono- – с выключенной оптимизацией (-O=-simd).

Гаджет перевода для Blogger’а

3

Озаботился добавлением переводчика Google в виде гаджета для блоггера… Не нашёл ничего путного, Google тоже не предоставляет такого сервиса (хм…). Полчаса копания по хэлпам и вот теперь — проба.


Та-да!

Вверх