BLOGas.lt
Sukurk savo BLOGą Kitas atsitiktinis BLOGas

Lietuvos .NET vartotojų grupės susitikimai #11 Vilniuje ir #8 Kaune

Parašė Sergejus | 2010-09-22 22:58

Šį kartą kalbėsime apie šiuo metu populiarią temą - TDD (Test Driven Development). Susitikimo metu aptarsime TDD principus, įrankius bei patirtį taikant TDD realiuose projektuose. Po susitikimo diskutuosime TDD tematika!

Susitikimai vyks Vilniuje - rugsėjo 28 d. (viešbutyje Crowne Plaza, Safyrinė salė B), Kaune - rugsėjo 29 d. (Technopolis, Microsoft Inovacijų Centras).

18:00 - 19:15 - Pristatymas iš TDD 
19:30 - 20:30 - Diskusija TDD tematika
20:30 - Vakarinė dalis

Registracija: Vilniuje, Kaune. Kadangi paskutiniu metu norinčių yra daugiau nei vietų, prašome registruotis tik jeigu tikrai planuojate atvykti.

Rodyk draugams

Pingy - #3 PingItem ir PingItemRepository

Parašė Sergejus | 2010-09-18 17:25

Primenu, Pingy – tai mano bandymas sukurti paslaugą, kuri kas tam tikrą laiką kreiptųsi į vartotojo nurodytą URL, taip neduodant IIS užmigti. Tuo pačiu metu bus renkama statistika apie serverio būseną, atsakos laikas ir pan. Pingy yra kuriamas specialiaiWindows Azure mokymosi tikslais.

Turinys

Pingy - #1 Architektūra
Pingy - #2 Windows Azure Table ir NoSQL mąstymas
Pingy - #3 PingItem ir PingItemRepository
Pingy - #4 PingItem repozitorijos atnaujinimas metodu GetItemsByPeriod
Pingy - #5 Windows Azure Queue ir PingTaskRepository
Pingy - #6 Windows Azure Queue korektiškas žinučių apdorojimas
Pingy - #7 Pinger serviso kūrimas
Pingy - #8 PingReportItem ir PingReportRepository

Šioje dalyje mes aprašysime esybę PingItem bei jos saugojimui skirtus Azure Table Storage objektus. Papildomai, įgyvendinsime pirmai iteracijai reikalingą PingItemRepository funkcionalumą.

PingItem ir susijusios klasės

Pati esybė atrodo labai paprastai:

public class PingItem
{
    public string Client { get; set; }
    public string Url { get; set; }
    public PingPeriod Period { get; set; }
}

Kreipimosi į serverį periodas nurodomas išvardijimo PingPeriod pagalba:

public enum PingPeriod
{
    QuarterHour = 15,
    HalfHour = 30,
    OneHour = 60,
    TwoHours = 120,
    SixHours = 360,
    TwelveHours = 720
}

Kaip matyti, reikšmės yra nurodomos minutėmis (tai bus svarbu vėliau). PingItem yra modelio POCO klasė, todėl mums reikalinga specialiai Windows Azure Table skirta klasė. Pavadinkime šią klasę PingEntity:

public class PingEntity : TableServiceEntity
{
    public string Url { get; set; }
    public int Period { get; set; }
}

Kodėl PingEntity neturi laukelio Client? Atsimenate PartitionKey? Mūsų atveju kaip PartitionKey ir bus naudojamas konkretus klientas. Tai leis visą su klientu susijusią informaciją saugoti kartu vienoje fizinėje saugykloje. Apibrėžkime pagalbine klasę PingItemConverter, kuri leis konvertuoti objektus iš ir į PingItem klasę:

public static class PingItemConverter
{
    public static PingItem ToPingItem(this PingEntity item)
    {
        return new PingItem
        {
            Client = item.PartitionKey,
            Url = item.Url,
            Period = (PingPeriod)item.Period,
        };
    }

    public static PingEntity ToPingEntity(this PingItem item)
    {
        return new PingEntity
        {
            PartitionKey = item.Client,
            RowKey = String.Format("{0}_{1}", item.Client, item.Url.SanitizeUrl()),
            Url = item.Url,
            Period = (int)item.Period,
        };
    }
}

Atkreipkite dėmesį į metodą SanitizeUrl. Kadangi tiek PartitionKey, tiek RowKey turi ribojimus dėl leistinų simbolių, šis metodas užtikrina korektišką URL konvertavimą:

internal static class Helper
{
    public static string SanitizeUrl(this string s)
    {
        var str = s.ToLower();
        str = Regex.Replace(str, @"(http|https)://", String.Empty);
        str = Regex.Replace(str, @"[^a-z0-9_.]", "-");

        return str;
    }
}

PingItem repozitorija

Pats laikas pereiti prie klasės PingItemRepository, kuri išsaugos mūsų PingItem objektą Windows Azure Table saugykloje. Mums reikalingas interfeisas atrodo taip:

public interface IPingItemRepository
{
    IEnumerable<PingItem> GetClientItems(string client);
    bool Exists(PingItem item);
    void AddItem(PingItem item);
    void RemoveItem(PingItem item);
    void RemoveClientItems(string client);
}

Realizacija yra pateikiama žemiau, bet prieš tai norėtųsi pabrėžti, kad su Windows Azure Table duomenų kontekstą geriausiai kurti su kiekviena užklausa, o nesaugoti lokaliai.

public class PingItemRepository : IPingItemRepository
{
    private readonly CloudStorageAccount account;
    private readonly string pingEntityTable = typeof(PingEntity).Name;

    public PingItemRepository(CloudStorageAccount account)
    {
        this.account = account;

        var client = account.CreateCloudTableClient();
        client.CreateTableIfNotExist(pingEntityTable);
    }

    public IEnumerable<PingItem> GetClientItems(string client)
    {
        return CreateContext().CreateQuery<PingEntity>(pingEntityTable)
                              .Where(e => e.PartitionKey == client)
                              .AsTableServiceQuery()
                              .Execute()
                              .Select(e => e.ToPingItem());
    }

    public bool Exists(PingItem item)
    {
        var entity = item.ToPingEntity();
        return CreateContext().CreateQuery<PingEntity>(pingEntityTable)
                              .Where(e => e.PartitionKey == entity.PartitionKey)
                              .Where(e => e.RowKey == entity.RowKey)
                              .FirstOrDefault() != null;
    }

    public void AddItem(PingItem item)
    {
        var context = CreateContext();

        var pingEntity = item.ToPingEntity();
        context.AddObject(pingEntityTable, pingEntity);
        context.SaveChanges();
    }

    public void RemoveItem(PingItem item)
    {
        var context = CreateContext();

        var pingEntity = item.ToPingEntity();
        context.AttachTo(pingEntityTable, pingEntity, "*");
        context.DeleteObject(pingEntity);
        context.SaveChanges();
    }

    public void RemoveClientItems(string client)
    {
        var context = CreateContext();

        var pingEntities = context.CreateQuery<PingEntity>(pingEntityTable)
                                  .Where(e => e.PartitionKey == client)
                                  .AsTableServiceQuery()
                                  .Execute()
                                  .ToList();

        foreach (var entity in pingEntities)
        {
            context.DeleteObject(entity);
        }
        context.SaveChanges();
    }

    protected TableServiceContext CreateContext()
    {
        return new TableServiceContext(account.TableEndpoint.ToString(), account.Credentials);
    }
}

Jeigu atidžiai išnagrinėjote PingItemRepository kodą, tai tikriausiai pastebėjote metodą AsTableServiceQuery(). Jo dėka paprasta WCF Data Services užklausa yra konvertuojama į CloudTableQuery užklausą, kuri yra specialiai pritaikyta darbui su Windows Azure Tables (automatiškai nuskaito visus įrašus, o ne po 1000 kaip yra pagal nutylėjimą, įvykus ryšio klaidai, pakartoja operaciją ir t.t.). Taip pat jums gali kilti klausimas, kodėl metode Exists() aš panaudojau FirstOrDefault() vietoje, pavyzdžiui, Any(). Atsakymas paprastas – Any() nėra palaikomas su Windows Azure Tables.

Kitoje dalyje…

Taigi šioje dalyje mes apibrėžėme mūsų PingItem modelį bei susijusią Windows Azure Tables klasę ir įgyvendinome repozitoriją. Kitoje dalyje mes susidursime su pirmais NoSQL sunkumais: norint kitaip surūšiuoti lentelę, mums teks kurti visiškai naują lentelę su tais pačiais duomenimis.

Rodyk draugams

Internet Explorer 9 Beta jau preinama!

Parašė Sergejus | 2010-09-15 19:25

Pirmą kartą viešai pristatytas MIX10 renginyje, apie Internet Explorer 9 buvo daug rašoma oficialiame IE tinklaraštyje. Daugelis jūsų tikriausiai girdėjo apie tokias IE9 naujoves kaip:

  • naujasis JavaScript varikliukas Chakra, kuris moka prekompiliuoti kodą
  • dalinis HTML5 palaikymas
  • CSS3 palaikymas
  • SVG palaikymas
  • aparatinis spartinimas
  • daug kitų dalykų

Šiam momentui mes turėjome keturias Developer Preview versijas ir apie kiekvieną buvo detaliai rašome minėtame IE tinklaraštyje. Šiandien pasirodė ilgai laukta Internet Explorer 9 Beta versija. Kas gi naujo lyginant su Internet Explorer 9 Developer Preview 4?

Atnaujinta grafinė sąsaja

ie9-1

Galimybė pertempti kortelę į naują langą

ie9-3

IE papildukų pasikrovimo laikas

ie9-2

Dažniausiai lankomi puslapiai

ie9-4

Web puslapių integracija su Windows 7 jump list

ie9-5

Ko pasigedau?

  • Bookmarks valdymas vis dar IE7 laikų, jokios sinchronizacijos su debesimi
  • “Paste and Go” funkcijos nebuvimas
  • Dažniausiai lankomų puslapių sąrašas atsilieka nuo Chrome ar Opera savo funkcionalumu
  • Vis dar nėra normalios paieškos puslapyje

Verdiktas

Microsoft labai šauniai padirbėjo ties standartų palaikymu, JavaScript sparta, aparatiniu spartinimu, bet grafinę sąsają reikia dar tobulinti (nepamirškime, tai tik Beta versija). Norėtųsi išskirti, mano manymu, labai novatorišką idėją dėl Jump List palaikymo, tikrai patogu! Pagaliau ateina laikas, kai naršyklių kova vyks ne standartų palaikyme, bet UX. Bet kuriuo atveju, labai rekomenduoju išbandyti Internet Explorer 9 Beta ir jums.

Rodyk draugams

Mano pristatymas iš Azure Lenkijos klausytojams

Parašė Sergejus | 2010-09-14 19:15

Populiariausio Lenkijoje .NET programuotojų portalo CodeGuru.pl prašymu, šį ketvirtadienį (rugsėjo 16 dieną) 20:30 Lietuvos laiku darysiu pristatymą iš Windows Azure platformos. Pristatymas vyks Live Meeting pagalba, todėl dalyvauti galite ir jus! Daugiau informacijos bei prisijungimas prie Live Meeting – čia.

Rodyk draugams

Su programuotojų diena!

Parašė Sergejus | 2010-09-13 10:14

Sveikiname vieni kitus komentaruose.

Rodyk draugams

Agile Turas 2010 Vilniuje

Parašė Sergejus | 2010-09-08 20:55

Sprendžiant iš didelio susidomėjimo Agile per paskutinį Lietuvos .NET vartotojų grupės susitikimą, nusprendžiau pranešti, kad pirmą kartą Lietuvoje organizuojamas Agile Turas 2010. Renginys vyks spalio 14 dieną, Vilniuje, “Best Western Vilnius” viešbutyje. Daugiau informacijos oficialiame puslapyje - AgileTuras2010Vilnius.agile.lt.

Rodyk draugams

Pingy - #2 Windows Azure Table ir NoSQL mąstymas

Parašė Sergejus | 2010-09-07 21:33

Primenu, Pingy – tai mano bandymas sukurti paslaugą, kuri kas tam tikrą laiką kreiptųsi į vartotojo nurodytą URL, taip neduodant IIS užmigti. Tuo pačiu metu bus renkama statistika apie serverio būseną, atsakos laikas ir pan. Pingy yra kuriamas specialiai Windows Azure mokymosi tikslais.

Turinys

Pingy - #1 Architektūra
Pingy - #2 Windows Azure Table ir NoSQL mąstymas
Pingy - #3 PingItem ir PingItemRepository
Pingy - #4 PingItem repozitorijos atnaujinimas metodu GetItemsByPeriod
Pingy - #5 Windows Azure Queue ir PingTaskRepository
Pingy - #6 Windows Azure Queue korektiškas žinučių apdorojimas
Pingy - #7 Pinger serviso kūrimas
Pingy - #8 PingReportItem ir PingReportRepository

Dėl informacijos gausos, nusprendžiau šiai temai skirti atskirą straipsnį. Prieš tęsiant, labai rekomenduoju perskaityti mano įvadą į Windows Azure Table.

Windows Azure Table trumpai

Trumpai priminsiu, Windows Azure Table yra paskirstyta (angl. distributed) NoSQL (neturi fiksuotos lentelių struktūros bei ryšių) tipo duomenų bazė. Tai reiškia, kad dalis jūsų duomenų bus saugoma vienoje saugykloje, o kita dalis – kitoje. Akivaizdu, kad duomenų gavimas iš skirtingų fizinių saugyklų reikalauja ne tik duomenų ištraukimo, bet ir perdavimo tinklu (kas nėra labai greita operacija). Dėl šios priežasties paskirstytose duomenų bazėse stengiamasi logiškai susijusią informaciją saugoti kartu, vienoje fizinėje saugykloje. Būtent šiam tikslui Windows Azure Table saugykloje ir egzistuoja PartitionKey sąvoka. Visos esybės, kurioms nurodytas tas pats PartitionKey, bus ne tik saugomos kartu, bet ir galės dalyvauti transakcijoje (paskirstytose duomenų bazėse transakcijos tarp skirtingų particijų nėra palaikomos). RowKey, kaip ir seka iš pavadinimo, yra įrašo raktas, kuris turi būti unikalus savo particijoje. Kitaip tariant, tikrąjį įrašo ID sudaro pora { PartitionKey; RowKey }.

Windows Azure Table klasių aprašymas

Norint saugoti duomenis Windows Azure Table saugykloje, reikia apibrėžti klasę, kuri paveldėtų iš TableStorageEntity arba turėtų savybes PartitionKey, RowKey ir Timestamp. PartitionKey ir RowKey paskirtį mes jau aptarėme, o savybės Timestamp paskirtis yra aiški – nusako paskutinio atnaujinimo laiką. TableStorageEntity klasės aprašas nurodytas žemiau:

public abstract class TableServiceEntity
{
    protected TableServiceEntity(string partitionKey, string rowKey);
    protected TableServiceEntity();
    public DateTime Timestamp { get; set; }
    public virtual string PartitionKey { get; set; }
    public virtual string RowKey { get; set; }
}

Mažiau žinoma, bet svarbu žinoti

Yra keletas svarbių aspektų, kurios būtina žinoti apie TableServiceEntity:

  • norint gauti konkretų įrašą, reikia nurodyti tiek PartitionKey, tiek RowKey; kitaip gausite visus nurodytos particijos duomenis arba visų particijų įrašus su nurodytu RowKey;
  • nepaisant fakto, kad Windows Azure Table leidžia nurodyti Where sąlyga bet kokiam esybės stulpeliui, niekada to nedarykite, nes indeksuoti yra tik PartitionKey ir RowKey stulpeliai;
  • įrašai yra automatiškai rūšiuojami pirma pagal PartitionKey, po to pagal RowKey ir to pakeisti negalima;
  • norėdami turėti duomenis išrūšiuotus pagal skirtingus stulpelius, teks pradubliuoti duomenis kiekvienam rūšiavimo variantui;
  • PartitionKey reikia parinkti tokį, kad prie jo galima būtų pririšti kuo daugiau susijusios informacijos; tuo pačiu metu PartitionKey neturi būti nei per daug konkretus, nei per daug platus.

Kitoje dalyje…

Tiek trumpai bendros informacijos apie Windows Azure Table saugyklą, kitoje dalyje pereisime prie PingItem esybės aprašymo bei susijusios repozitorijos kūrimo.

Rodyk draugams

Pingy - #1 Architektūra

Parašė Sergejus | 2010-09-05 22:22

Primenu, Pingy – tai mano bandymas sukurti paslaugą, kuri kas tam tikrą laiką kreiptųsi į vartotojo nurodytą URL, taip neduodant IIS užmigti. Tuo pačiu metu bus renkama statistika apie serverio būseną, atsakos laikas ir pan. Pingy yra kuriamas specialiai Windows Azure mokymosi tikslais.

Turinys

Pingy - #1 Architektūra
Pingy - #2 Windows Azure Table ir NoSQL mąstymas
Pingy - #3 PingItem ir PingItemRepository
Pingy - #4 PingItem repozitorijos atnaujinimas metodu GetItemsByPeriod
Pingy - #5 Windows Azure Queue ir PingTaskRepository
Pingy - #6 Windows Azure Queue korektiškas žinučių apdorojimas
Pingy - #7 Pinger serviso kūrimas
Pingy - #8 PingReportItem ir PingReportRepository

Kūrimo iteracijos

Kadangi tai yra mano asmeninis laisvalaikio projektas, kuriam laiko nebus skiriama ypatingai daug, nusprendžiau Pingy kurti iteracijomis, tam kad kuo greičiau gauti bent dalį veikiančio funkcionalumo. Pirmoje iteracijoje bus įgyvendintos 3 bazinės funkcijos:

  • Kliento URL įvedimas ir redagavimas
  • Planuotojas (angl. scheduler), atsakingas už kreipimosi į serverį užduočių suformavimą ir įdėjimą į eilę
  • Kreipimosi į serverį servisai (angl. pingers), atsakingi už kreipimąsi į serverius ir gautos statistinės informacijos išsaugojimą

Kaip matote, šioje iteracijos mes nieko nedarysime susijusio su klientų autentifikacija bei statistinių duomenų atvaizdavimu. Šioje dalyje aš aprašysiu Pingy architektūrą, atitinkančią pirmos iteracijos tikslus.

Architektūra

Aiškumo dėlei, pirma pateiksiu sekų diagramą, iš kurios matysis informacijos srautai, kokie Windows Azure servisai bus reikalingi bei kaip bus užtikrinamas Pingy plečiamas (angl. scalability):

Pingy

Iš pateiktos diagramos matyti, kad Pingy sudarys tokios dalys:

  • Web Page – Windows Azure Web rolė, skirta kliento URL įvedimui bei redagavimui
  • Table Storage – ypatingai plečiama Windows Azure NoSQL tipo saugykla
  • Ping Scheduler – Windows Azure rolė, atsakingą už URL surinkimą pagal skirtingus laiko periodus (pvz., kas 15 min., kas 30 min., kas 60 min., kas 120 min. ir kas 720 min.)
  • Message Queue – Windows Azure eilių mechanizmas, užtikrinantis patikimą žinučių pateikimą
  • Pingers – Windows Azure rolė, atsakingą už kreipimąsi į serverius pagal nurodytus URL bei gautos statistikos išsaugojimą
  • Server – kliento nurodytas serveris (tiksliau jo URL), į kurį kreipimąsi kas nurodytą laiką

Šioje diagramoje nėra parodytos tokios trivialios operacijos kaip:

  • Visų kliento URL gavimas
  • Kliento URL redagavimas ir validavimas
  • Kitos pagalbinės operacijos

Pingy plečiamumas

Kaip bus užtikrinamas Pingy plečiamumas? Kliento URL ir kreipimosi į serverį dažnumas bus saugomas plečiamoje Windows Azure Table saugykloje. Ten pat bus saugoma ir kreipimosi statistika. Kreipimosi į serverį užduotis (angl. ping task) bus talpinama į Windows Azure eilę, kuri ne tik užtikrina žinutės gavimą ir apdorojimą, bet ir gali būti plečiama kuriant po atskirą eilę kiekvienam periodui. Pradiniame etape mes naudosime vieną bendrą eilę. Kreipimosi į serverį servisų (angl. Pingers) skaičius (tapatu virtualių mašinų skaičiui) nėra ribojimas – priklausomai nuo poreikio mes galime turėti nuo vieno iki kelių. Pradiniame variante startuosime su dviem. Ping Scheduler užduotis - nurodytam intervalui gauti visas nuorodas ir patalpinti atitinkamas užduotis į eilę. Pirminiame variante turėsime vieną servisą (t.y. vieną virtualią mašiną su šiuo servisu), kuriame kiekvienam periodui bus sukurta po atskirą giją. Esant plečiamumo poreikiui, nesunku bus patobulinti kodą, kad galima būtų paleisti kiekvienam periodui po nuosavą virtualią mašiną.

Taigi kaip tikriausiai pastebėjote, apie plečiamumą galvojau pirmoje vietoje. Jeigu turite minčių kaip dar labiau jį pagerinti – rašykite komentaruose!

Pingy esybės

Pirmoje iteracijoje mes dirbsime su tokiomis esybėmis:

  • Client – pagrindinė esybė, kuri figūruoja visoje sistemoje
  • PingItem – esybė, atitinkanti įrašą apie kliento URL ir kreipimosi į serverį dažnumą
  • PingTask – esybė, atitinkanti poreikį kreiptis į nurodytą kliento URL
  • PingReportItem – esybė, atitinkanti įrašą apie kreipimosi į serverį rezultatą

Kitoje dalyje…

Kitoje dalyje mes apibrėšime PingItem esybę, sukursime jai skirtą repozitoriją bei trumpai paaiškinsiu NoSQL mąstymo pagrindus, reikalingus efektyviam darbui su Table Storage. Kaip visada, laukiu jūsų pastabų / klausimų / pageidavimų komentaruose!

Rodyk draugams

Pingy – mokomės Windows Azure kuriant realią aplikaciją

Parašė Sergejus | 2010-09-02 21:23

Taip, ir vėl aš pradedu straipsnių ciklą, skirtą Windows Azure programavimui. Daugiau nei prieš pusmetį parašiau 7 dalių įvadą į Windows Azure:

Įvadas į Windows Azure – 1 dalis (Pagrindinės sąvokos)
Įvadas į Windows Azure – 2 dalis (Windows Azure Hello World ASP.NET puslapio kūrimas)
Įvadas į Windows Azure – 3 dalis (lokalios objektinės Windows Azure saugyklos konfigūravimas)
Įvadas į Windows Azure – 4 dalis (lokalios BLOB saugyklos panaudojimas)
Įvadas į Windows Azure – 5 dalis (lokalios BLOB saugyklos perkėlimas į Windows Azure)
Įvadas į Windows Azure – 6 dalis (esybių lentelių saugyklos panaudojimas)
Įvadas į Windows Azure – 7 dalis (XDrive panaudojimas)

Kas atsimena, po 7 dalies iš manęs staiga atėmė testinį Windows Azure prisijungimą. Kurį laiką bandžiau jį atgauti ir tik prieš kelis mėnesius per MVP kanalą Windows Azure grupė suteikė man naują prisijungimą. Vėl atsiradus galimybei, nusprendžiau pratęsti pažintį su Windows Azure, bet šį kartą kuriant realią aplikaciją.

Tie, kas užsiima Web programavimu .NET platformoje, tikriausiai yra susidūrę su viena IIS problema – po tam tikro neveikimo laiko jis tiesiog „užmiega“, dėl ko pirma HTTP užklausa užtrunka pakankamai ilgai. Yra keletas galimų būdų šiai problemai apeiti: AppPool Recycling; Windows servisas, kuris kas nustatytą laiką kreipiasi į nurodytą Web aplikaciją ir t.t. Aš nusprendžiau sukurti paslaugą (servisą), kuri leistų vartotojui nurodyti į kokį URL kokiu dažnumu reikia kreiptis. Tuo pačiu būtų kaupiama ir serverio veikimo statistika, pavyzdžiui, atsakos laikas, atsakymo HTTP kodas ir pan.

Tikrai žinau, kad panašios paslaugos egzistuoja (ir žymiai geresnės), bet mano tikslas yra kitas – geriau išmokti Windows Azure. Aplikaciją pavadinau Pingy (nuo angl. ping), kitame straipsnyje aš trumpai aprašysiu jos architektūrą, pateiksiu sekų diagramą bei paaiškinsiu kaip bus siekiama ypatingai geros greitaveikos bei plečiamumo (angl. scalability), kurį įgalina Windows Azure platformą.

Rodyk draugams

Windows 7 Ultimate nemokamai #5 - laimėtojas paaiškėjo

Parašė Sergejus | 2010-09-01 18:43

Štai ir atėjo laikas atiduoti paskutinį Windows 7 Ultimate diską, kurį laimėjo…

winner

… skaitytojas vardu benjaminas. Nuoširdžiai sveikinu ir prašau susisiekti su manimi. Nors kompaktų daugiau ir nebeliko, bet tai nereiškia, kad nebus naujų konkursų. Laukite tęsinio!

Rodyk draugams