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

0

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

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

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

Обёртки над текстовыми командами в PowerShell

2

Хотел поделиться опытом создания простой объектно-ориентированной обёртки над обыкновенной консольной утилитой для облегчения работы с ней. Для примера была выбрана pnputil для работы с хранилищем дистрибутивов драйверов в Windows. (далее…)

PowerShell и систематизация файлов

0

Дурацкое название, но что-то лучше ничего не придумалось.

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

Для начала, хочется заметить, что в общем случае имя файла генерируется сервером в виде my_picture_s_name_by_awesome_me.jpg, т.е. название + by + имя пользователя, приведённое к виду, пригодному для сохранения в любой файловой системе.

Итак, хранятся все картинки в одной папке – новые в корне, старые – рассортированные по авторам. От скрипта требуется умение автоматически распихивать файлы заданного вида по папкам. Решается тривиально (скрипт был написан очень давно, когда v1.0 только-только вышла и я ещё почти ничего о нём не знал):

$files = Get-ChildItem -Filter "*_by_*.???" | where {$_.GetType() -eq [System.IO.FileInfo]}
$myRegEx = New-Object System.Text.RegularExpressions.Regex('.+_by_(.+)\..{3}', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
foreach ($f in $files) {
	$dir = Join-Path $f.Directory $myRegEx.Split($f.Name)[1]
	$destination = Join-Path $dir $f.Name
	if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
	Move-Item -Path $f.FullName -Destination $destination -Force
}

Второй скрипт пришлось придумать только что, поскольку буквально на прошлой неделе к некоторым файлам зачем-то стали приписывать некий набор символов. Пока что я не знаю, зачем он нужен и какой у него формат, эмпирически же известно, что это семисимвольный довесок, начинающийся на d2x или d2y. Пока что будем его обрезать:

ls | where {$_.FullName -Match '.+\-[a-z0-9]{7}\.[a-z]{3,4}'} |
ren -NewName {$_.FullName -Replace '(.+)(\-[a-z0-9]{7})(\.[a-z]{3,4})','$1$3'}

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#, который был гораздо неприятней).

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

Firefox 4

0

Последние полнедели я живу с ночной сборкой того, что к концу года выйдет под именем Firefox 4. Поскольку буквально на днях должен состояться релиз первой публичной беты, я решился перейти на ночные сборки вместо стабильной версии. Пара багов и неработающих функций то тут, то там меня не особенно пугает, а общее впечатление уже сейчас очень положительное.

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

Итак, первое, что бросается в глаза – скорость. Скорость загрузки, отрисовки страниц (в т.ч. при прокручивании), сложных веб-приложений, использующих JavaScript. Несмотря на то, что код только-только дорос до более-менее стабильного состояния, уже сейчас заметна разница в старте приложения по сравнению с текущей стабильной веткой. Конечно, до Chrome ещё далеко, но по крайней мере, уже не возникает раздражения при постоянном пользовании программы (и это с учётом того, что у меня стоит множество расширений для Firefox).

Второе – то, что я ждал больше всего – новый интерфейс.

Его сильно переработали и теперь он очень хорошо вписывается в окружение Windows 7. Aero Galss, Aero Peek (хотя всё ещё используется один процесс :( ), закладки сверку, аппаратное ускорение отрисовки примитивов, использование DirectWrite для типографики… Это просто сказка какая-то :)

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

Ещё не все нововведения, анонсированные для 4й версии реализованы, что несколько расстраивает. Так, главная кнопка приложения попала в транк лишь пару недель назад (а переехала на положенное место и того позже). Кнопки Обновить/Отменить всё ещё отдельные, как в прототипах для 3.7 (а не интегрированы в адресную строку). Закладки для веб-приложений тоже ещё не реализованы (а я уже так привык к ним, когда пользовался прототипом на предыдущих версиях). Менеджер аккаунтов тоже ещё не готов (сейчас он ничем не отличается от предыдущих версий). Новый менеджер расширений готов лишь на половину (совсем нет страницы поиска, процесс установки/обновления никак не показывается). Ну и т.д. и т.п.

Но лично для меня – стабильная работа (пока ещё ни одного вылета), DirectWrite и новый интерфейс вполне достаточны, чтобы пребывать в восторге и пользоваться сырым продуктом на постоянной основе.

Вверх