2012-02-17

GetFiles, GetDirectories, EnumerateDirectories, EnumerateFiles - UnauthorizedAccessException

Funkcje służąca do wyliczenia plików i katalogów w połączeniu z parametrem SearchOption.AllDirectories (a być może także bez niego) mają jedną wadę potrafią zwrócić wyjątek UnauthorizedAccessException, w przypadku katalogu do którego nie mamy praw. Najgorzej wygląda to właśnie podczas rekurencyjnego przeszukiwania całego drzewa. Jeśli gdzieś w tym drzewie jest plik/katalog do którego nie mamy praw to dostaniemy ten wyjątek.

Wydaje się, że powinno to zostać trochę bardziej elegancko rozwiązane. W praktyce oznacza to, że na podanych wyżej funkcjach nie można polegać. Bo taki katalog/plik może trafić się wszędzie.

Przy czym nie jestem pewien czy bardziej nie chodzi tutaj o katalogi niż o pliki. Tego nie sprawdzałem.

Moja wersja zwracająca wszystkie pliki w w drzewie katalogu i pomijająca te do których nie mamy praw:

public static void GetFiles(DirectoryInfo a_dir, List<string> a_files)
{
    try
    {
        foreach (var file in a_dir.GetFiles())
            a_files.Add(file.FullName);
    }
    catch
    {
        System.Console.WriteLine("dir ex: {0}", a_dir.FullName);
    }

    try
    {
        foreach (var dir in a_dir.GetDirectories())
            GetFiles(dir, a_files);
    }
    catch (UnauthorizedAccessException)
    {
        System.Console.WriteLine("dir2 ex: {0}", a_dir.FullName);
    }
}

2012-02-16

RestrictedFrequencyAction

Klasa jak sama nazwa wskazuje albo i nie służy do wywoływania delegatów z ograniczeniem: nie częściej niż pewien zadany czasy. Przydatna kiedy bardzo wiele wątków wprost bombarduje GUI żądaniami odświeżenia. W przypadku gdy pomiędzy kolejnymi akcjami nie upłynął określony czas aktualna akcja jest zapamiętywana jako kandydat do wywołania po upłynięciu czasu blokady. Należy pamiętać o tym, że z wszystkich akcji zablokowanych zostanie wywołana tylko ostania.

Nawet jeśli takie zjawisko w naszej aplikacji nie występuje, a może się zdarzyć, warto wykorzystać taką klasę by uniknąć problemów z czasem reakcji GUI.

Ja osobiście z tego modelu zrezygnowałem. Dlatego klasę utrwalam na blogu. Timer co 0.5s wystarcza w moim wypadku aż za nadto. Ponieważ wątki wykorzystują połączenia sieciowe real-time nie jest potrzebne.

public class RestrictedFrequencyAction
{
    private TimeSpan m_update_delta;
    private DateTime LastPerform;
    private volatile bool m_scheduled;
    private Object m_lock = new Object();
    private volatile Action m_action;

    public RestrictedFrequencyAction(int a_update_delta_ms)
    {
        m_update_delta = new TimeSpan(0, 0, 0, 0, a_update_delta_ms);
        LastPerform = DateTime.Now - m_update_delta - m_update_delta;
    }

    public void Perform(Action a_action)
    {
        lock (m_lock)
        {
            int t = (int)(m_update_delta - 
                (DateTime.Now - LastPerform)).TotalMilliseconds;

            if (t < 0)
            {
                LastPerform = DateTime.Now;
                a_action();
            }
            else
            {
                m_action = a_action;

                if (!m_scheduled)
                {
                    m_scheduled = true;

                    new Task(() =>
                    {
                        Thread.Sleep(t);
                        lock (m_lock)
                        {
                            LastPerform = DateTime.Now;
                            m_scheduled = false;
                            m_action();
                        }

                    }).Start();
                }
            }
        }
    }
}

RichTextBoxAppender

Jak w tytule, appender dla RichTextBox-a. W NLog dostępny w standardzie.

Powstał na podstawie wielu podobnych implementacji znalezionych w sieci.

Większość z nich używa Invoke() do publikowani danych na kontrolce, co może prowadzić do deadlock-a, w sytuacji gdy wątek GUI i jakiś inny razem starają się coś zalogować. Zastąpienie Invoke() wersją asynchroniczną BeginInvoke rozwiązuje problem.

Czemu czegoś takiego nie ma w standardzie log4net, nie wiem. Wersja dla NLog oferuje znacznie więcej od tej tutaj (kolorowanie elementów linii loga, tworzenie osobnego okna z kontrolką, integracja z kontrolką na formatce z poziomu xml).

public class RichTextBoxAppender : AppenderSkeleton
{
    private RichTextBox m_rich_text_box = null;
    private LevelMapping m_level_mapping = new LevelMapping();
    public int MaxLines = 100000;

    private delegate void UpdateControlDelegate(LoggingEvent a_logging_event);

    public RichTextBoxAppender(RichTextBox a_rich_text_box)
        : base()
    {
        m_rich_text_box = a_rich_text_box;
    }

    private void UpdateControl(LoggingEvent a_logging_event)
    {
        LevelTextStyle selectedStyle = 
            m_level_mapping.Lookup(a_logging_event.Level) as LevelTextStyle;
        if (selectedStyle != null)
        {
            m_rich_text_box.SelectionBackColor = selectedStyle.BackColor;
            m_rich_text_box.SelectionColor = selectedStyle.TextColor;
            m_rich_text_box.SelectionFont = 
                new Font(m_rich_text_box.Font, selectedStyle.FontStyle);
        }

        m_rich_text_box.AppendText(RenderLoggingEvent(a_logging_event));

        // Clear if too big.
        if (MaxLines > 0)
        {
            if (m_rich_text_box.Lines.Length > MaxLines)
            {
                int pos = m_rich_text_box.GetFirstCharIndexFromLine(1);
                m_rich_text_box.Select(0, pos);
                m_rich_text_box.SelectedText = String.Empty;
            }
        }

        // Autoscroll.
        m_rich_text_box.Select(m_rich_text_box.TextLength, 0);
        m_rich_text_box.ScrollToCaret();
    }

    protected override void Append(LoggingEvent a_logging_event)
    {
        if (m_rich_text_box.InvokeRequired)
        {
            m_rich_text_box.BeginInvoke(
                new UpdateControlDelegate(UpdateControl),
                new object[] { a_logging_event });
        }
        else
        {
            UpdateControl(a_logging_event);
        }
    }

    public void AddMapping(LevelTextStyle a_mapping)
    {
        m_level_mapping.Add(a_mapping);
    }

    public override void ActivateOptions()
    {
        base.ActivateOptions();

        m_level_mapping.ActivateOptions();
    }

    protected override bool RequiresLayout 
    { 
        get 
        { 
            return true; 
        }
    }
}

public class LevelTextStyle : LevelMappingEntry
{
    public bool Bold;
    public bool Italic;
    public Color TextColor;
    public Color BackColor;
    public FontStyle FontStyle { get; private set; }

    public override void ActivateOptions()
    {
        base.ActivateOptions();

        if (Bold)
            FontStyle |= FontStyle.Bold;
        if (Italic)
            FontStyle |= FontStyle.Italic;
    }
}

2012-02-14

Failed to load toolbox item 'controle name' it will be removed from the toolbox

Taki ostatnio napotkałem błąd. Tym samym nie możemy też bez dostania wyjątku otworzyć otworzyć formy z taką kontrolką jak i samej wizualizacji kontrolki.

Problem pojawił się kiedy do projektu dodałem DLL-ki korzystające z mixed-mode i tym samym zmieniłem platformę z Any CPU na x64. Jak szukałem rozwiązania to tego nie wiedziałem, teraz jestem mądrzejszy po fakcie.

Rozwiązanie polega na przerzuceniu kontrolek do assembly skofigurowanego na Any CPU.

NHibernate - mapowanie

NHibernate możemy poinformować o sposobie w jaki powinien mapować obiekty na struktury bazy danych na kilka sposobów

1.
Mapowanie przez kod, w przypadku gdy nasze mapowanie jest rozdzielone od obiektów:

public class Customer 
{
    public int Id { get; set; }
    public string FirstName { get; set; }
}

public class CustomerMapping : ClassMapping<Customer>
{
    public CustomerMapping()
    {
        Lazy(false);
        Id<int>(x => x.Id, map => map.Generator(Generators.HighLow));
        Property<string>(c => c.FirstName, 
            map => { map.Length(10); map.NotNullable(true); });
    }
}

ModelMapper mapper = new ModelMapper();
mapper.AddMapping<CustomerMapping>();
HbmMapping mapping = mapper.CompileMappingFor(new[] { typeof(Customer) });

Nasz obiekt nie jest w żaden sposób związany z klasami NHibernate.

2.
Mapowanie przez kod, w przypadku gdy nie chcemy tworzyć osobnych obiektów mapujących.

public interface IClassMapping
{
    void Map(ModelMapper a_mapper);
}

public class Customer : IClassMapping
{
    public int Id { get; set; }
    public string FirstName { get; set; }

    public void Map(ModelMapper a_mapper)
    {
        a_mapper.Class<Customer>(m =>
        {
            m.Id(x => x.Id, map => map.Generator(Generators.HighLow));
            m.Property(c => c.FirstName, 
                map => { map.Length(10); map.NotNullable(true); });
            m.Lazy(false);
        });
    }
}

Interfejs jest nam w tym przypadku potrzebny do automatyczneo namierzania klas:

private static void AddMappings()
{
    ModelMapper mapper = new ModelMapper();

    foreach (var asm in AssembliesToMap)
    {
        var types = from type in asm.GetTypes()
                    where !type.IsInterface
                    where type.IsImplementInterface(typeof(IClassMapping))
                    where !type.IsAbstract
                    select type;

        foreach (var type in types)
            (Activator.CreateInstance(type) as IClassMapping).Map(mapper);
    }

    HbmMapping mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
    Configuration.AddDeserializedMapping(mapping, "MangaCrawler"); 
}

3.
Mapowanie z wykorzystaniem atrybutów.

4.
Mapowanie z wykorzystaniem XML.

5.
Wykorzystanie zdarzeń klasy ModelMapper do dekorowania. Dzięki temu możemy jak mi się wydaje możemy obejść się dla większości elementów bez ręcznego mapowania. Np. możemy zdefiniować konwencję nazewnictwa kolumn w bazie, automatycznie podłączyć generatory id... Dokładnie tego nie sprawdzałem, być może jeszcze wiele innych rzeczy można ustandaryzować.