Blog (65)
Komentarze (803)
Recenzje (0)

dpsidebar - programowanie w windows cz.2

@tfldpsidebar - programowanie w windows cz.215.09.2012 11:16

Ból w plecach, ramionach, szyi, nadgarstkach, okolic nerek (żeby nie napisać dosadniej), oczy błagające o tarcze okularów, ulubiona płyta nagle znienawidzona. To wszystko oznacza, że tydzień, dwa tygodnie nawet były intensywne w pisanie. To w formie przeprosin za opóźnienie...

Słowem wstępu - pod poprzednim pojawił się komentarz tomimakiego w sprawie bezpieczeństwa gadgetów. Jest oczywiście prawdą, że gadgety mogą być niebezpieczne. Jak pisałem poprzednio - gadget jest uruchomiony z uprawnieniami użytkownika, może się łączyć do netu, ergo, możliwe jest przejęcie kontroli nad komputerem przy użyciu gadgetów. Przy czym trzeba pamiętać, że każdy program ściągnięty z niezaufanego źródła może być niebezpieczny. W tym przypadku sami jesteśmy autorami programu, więc o ile nie cierpimy na rozdwojenie jaźni i ufamy sobie sami możemy własne gadgety odpalać.

Makieta

Odrobinka teorii - aplikacja www wyświetla dane na żądanie użytkownika. Gadget robi dokładnie to samo - wyświetla dane. Dlatego dobrym zwyczajem, ułatwiającym prace programistom jest w pierwszej kolejności stworzenie makiet aplikacji, które potem "uzupełniane" są dynamicznymi danymi przez "backend". W przypadku dpsidebar postąpić należy podobnie. Jak wiadomo z poprzedniego wpisu gadget dzielimy na główną część, którą możemy dowolnie modelować, flyout, który w pewnym stopniu (znacznym) również możemy dowolnie modelować oraz settings, który możemy modelować w stopniu nieznacznym.

Założenia dpsidebar są takie, że na głównej części wyświetlane będą kolejne wpisy, jeden pod drugim, zbliżone układem z wpisami pod blogami dobrychprogramów. Będzie także możliwość dodania kolejnego artykułu oraz przejścia do edycji listy monitorowanych artykułów. Na flyoucie wyświetlana będzie lista monitorowanych wpisów wraz z możliwością usunięcia wpisów oraz pełne (w przeciwieństwie do tych z body- skróconych) wpisy. W settingsach ustawić będzie można czas odświeżania oraz zdecydować o wyświetlaniu lub nie awatarów przy wpisach.

Grafik ze mnie mizerny. Dlatego nie przesadzałem z makietami. Jeśli ktoś czuje się na tym polu dobrze - zachęcam by nie lekceważyć tego etapu.

Gadget w podstawowym trybie
Gadget w podstawowym trybie

Kodowanie

Żeby gadget w ogóle zadziałał należy spakować zipem plik nazwa.html oraz Gadget.xml. O ile pierwsza nazwa jest dowolna (definiuje się ją w Gadget.xml) o tyle ta druga jest obligatoryjna. Kod xml jest śmiesznie prosty:


<?xml version="1.0" encoding="utf-8" ?>
<gadget>
<name>DPSidebar</name>
<namespace>windows.sdk</namespace>
<version>1.0.0.0</version>
<a rel="nofollow"uthor name="tfl">
    <info url="dobreprogramy.pl" />
    <logo src="IMG/tfl.png" />
</author>
<copyright>&#169; tfl.</copyright>
<description>DP SideBar.</description>
<icons>
    <icon height="48" width="48" src="IMG/logo.jpg" />
</icons>
<hosts>
    <host name="sidebar">
      <base type="HTML" apiVersion="1.0.0" src="dpsidebar.html" />
      <permissions>Full</permissions>
      <platform minPlatformVersion="1.0" />
      <defaultImage src="IMG/logo.jpg" />
    </host>
</hosts>
</gadget>

Plik manifestu jest nad wyraz prosty i nie wymaga moim zdaniem go tłumaczyć. Dodam tylko, że linki do grafik i plików są relatywne do pliku Gadget.xml.

Sidebar.exe z ustawieniami z Gadget.xml
Sidebar.exe z ustawieniami z Gadget.xml

Pewien znajomy powiedział mi, że kodowanie zaczyna się od ustalenia struktury folderów. Nasz będzie bardzo prosta:

[list] [item]CFG[/item][item]

  • settings.ini

[/item][item]CSS[/item][item]

  • dpsidebar.css
  • flyout.css

[/item][item]IMG[/item][item]

  • background.png
  • background_n.png
  • logo.jpg
  • minus-16.png
  • page-pencil-16.png
  • paper-gavel-16.png
  • plus-16.png
  • tfl.png
  • warning.png

[/item][item]JS[/item][item]

  • cfg.js
  • dpsidebar.js
  • jquery-1.8.1.min.js
  • json.js

[/item][item]dpsidebar.html[/item][item]flyout.html[/item][item]settings.html[/item][item]Gadget.xml[/item][/list]

Jasne?

Ok, czas zając się realizacją wymagań. Po pierwsze - w jakiś sposób trzeba by zdobyć treść zadeklarowanej przez użytkownika strony by następnie ją sparsować w poszukiwaniu ostatniego komentarza. Można to zrobić na przykład przy pomocy metody $.get() od JQuery (o szczegóły zapraszam do wpisu Slepcia ). Ja jednak postanowiłem użyć ActiveX. Głównie dlatego, że się da. Dodatkowo zrezygnowałem z asynchroniczności. Nie będę ukrywał, że szkoda mi było czasu na użeranie się z callbackowaniem. W przypadku metod JQuery sprawa będzie o wiele prostsza dzięki wykorzystaniu parametru success.

W konsekwencji w obiekcie dpsidebar (jeden z dwóch głównych obiektów całego dpsidebar) tworzymy sobie metodę getPage(url):


	getPage: function(url)
	{
		var xmlhttp = false;
		try
		{
			xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e)
		{
		xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
		}
		xmlhttp.open("GET", url, false);
		xmlhttp.onreadystatechange = function()
		{
			 if (xmlhttp.readyState==4)
			 {
				if (xmlhttp.status==200)
				{
					dpsidebar.page = xmlhttp.responseText;
				}
				else
				{
					dpsidebar.page = false;
				}
			}
		};
		xmlhttp.send(null);
	},

Zadaniem tej metody jest dodanie treści do parametru page. I mamy już całą interesującą nas stronę. Teraz należy z niej wybrać interesujący nas element. Wykorzystamy w tym przypadku wyrażenia regularne. Chcemy mieć możliwość monitorowania wpisów pod artykułami na głównej oraz pod blogami. Powstać więc muszą dwa różne regexpy:


switch(this.getType(url))
		{
			case 'blog':
				var pattern = /div class="(even|odd) item" id="komentarz_(\d{1,10}?)">(<a href="http:\/\/www.dobreprogramy.pl\/(\d{1,6},.{1,15},Uzytkownik.html|.{1,15})">)?<img style="margin:1px 12px 12px 0" src="http:\/\/avatars.dpcdn.pl\/Avatar\.ashx.{1}file=(\d*_?\d*)?\.?(jpg|png|gif)?&type=UGCFriendsList" alt="avatar" width="46" height="46" class="border small float-left" \/>(<\/a>)?<div class="text-h65 font-heading display-inl_blk nick">(<a  class="color-inverse" href="http:\/\/www.dobreprogramy.pl\/(\d{1,6},.{1,15},Uzytkownik.html|.{1,15})" title=".{1,150}">|<span title=".{1,150}">)(.{1,50}?)(<\/a>|<\/span>)<\/div> \| <span class="color-thirdary font-heading text-h7">(.{15,16}?)<\/span><div class="text-h75 tresc">(.+?)<\/div>/gi;
			break;
			case 'art':
				var pattern = /<div class="item" id="komentarz_(\d{1,10}?)"><div class="commentContent"><div class="avatarSpace"><img alt="Avatar" class="avatar" align="left" src='http:\/\/avatars.dpcdn.pl\/Avatar\.ashx.{1}file=(\d*_?\d*)?\.?(jpg|png|gif)?' style="width:50px;height:50px;" \/><img src=".{1,150}" alt=".{1,150}" class="onlineStatus" \/><\/div><span class="userInformation" title=".{1,150}"><span class="nick" style="font-weight:bold;">(<a href="http:\/\/www.dobreprogramy.pl\/(.+?)">)?(.{1,50}?)(<\/a>)?<\/span><span class="CommentDate">\| (.{15,16}?)<\/span><\/span><span class="numer"><a href="#komentarz_\d{1,10}" class="CommentDate">#\d{1,4}<\/a><\/span><p class="commentText" style="margin-top: 2px; margin-bottom: 0px;">(.+?)<\/p>\<\!\-\- <div class="clear"><\/div> \-\-\><\/div><\/div>/gi;
			break;
			default:
				return false;
			break;
		}

I tutaj kończy się proste... Box z komentarzem zmienia się w zależności czy autor był zalogowany czy nie. Po prostu trzeba mi uwierzyć na słowo, że ten reg daje odpowiednie (wystarczające) dane. Oczywiście każdy może go sobie dowolnie pozmieniać. Uważny czytelnik na pewno zauważył, że switch jest po metodzie getType(). Metoda ta również w oparciu o wyrażenia regularne definiuje czy wpis jest blogiem, czy nie. Wygląda ona następująco:


	getType: function(url) 
	{
		if(this.page == "")
		{
			this.getPage(url);
		}
		var regexType = new RegExp('<title>(.+)');
		var title = regexType.exec(this.page);
		var regexArt = new RegExp('- dobreprogramy');
		var regexBlog = new RegExp('- blogi u.?ytkownik.?w portalu dobreprogramy');
		this.title = title[0].replace("<title>","");
		if(!regexArt.exec(title) && regexBlog.exec(title))
		{
			this.type = "blog";
			return "blog";
		}
		else if(regexArt.exec(title) && !regexBlog.exec(title))
		{
			this.type = "art";
			return "art";
		}
		else
		{
			this.type = "unknown";
			return "unknown";
		}
	},

To już na szczęście jest proste. Mamy więc odpowiednie dane, by zbudować obiekt komentarza, którego następnie umieścimy w naszym gadgecie korzystając z metody $.append();.

Konfiguracja

Cóż jednak z tego, że wiemy, jak wyświetlić komentarz, skoro nasz gadget nie wie jaką stronę ma parsować? Nauczymy go więc tego tworząc konfigurację. Nasza konfiguracja ze względu na swoją małą objętość zdecydowałem się umieścić w pliku txt w formie jsona.


url:{"urls":[{"url":"http://www.dobreprogramy.pl/Juz-jutro-pierwsze-zadanie-specjalne-w-konkursie-z-dioda,Aktualnosc,36174.html","last_comment":"1036375","title":"Już jutro pierwsze zadanie specjalne w konkursie z diodą! - dobreprogramy\r"}]}
refresh:60
show_avatars:true

Do obsługi konfiguracji posłuży nam obiekt cfg zdefiniowany w pliku cfg.js. Posiadać on będzie metody loadFile(), która (jak nazwa wskazuje) załaduje nam plik. Tym razem posłużyć się czystym javascriptem nie mogłem (javascript by design nie ma dostępu do plików). Odpalimy więc kolejny raz ActiveX.


	loadFile: function () {
		try {
			var fs = new ActiveXObject("Scripting.FileSystemObject");
			if(!fs.FileExists(this.getFullPath())) this.createFile();
			try {
				var ini = "";
				var ts = fs.OpenTextFile(this.getFullPath(), 1); 
				
				try {
					ini = ts.ReadAll();
				}
				finally {
					ts.Close();
				}
				var regexUrl = new RegExp('url:(.+?)\n');
				var urls = regexUrl.exec(ini);
				this.urls = $.parseJSON(urls[1]).urls;
				var regexRefresh = new RegExp('refresh:(.+?)\n');
				var refresh = regexRefresh.exec(ini);
				this.refresh = refresh[1];
				var regexShowAvatars = new RegExp('show_avatars:(.+?)$');
				var show_avatars = regexShowAvatars.exec(ini);
				this.show_avatars = show_avatars[1];
				
			}
			finally {
				fs = null;
			}
		}
		catch (e) {
			for(i in e)
			{

			}
		}
	}

Metoda ta tworzy od razy parametry obiektu cfg, których używać będziemy w naszym gadegecie. Przydała się także metoda $.parseJSON, która pozwala na utworzenie obiektów na podstawie tekstu zawierającego json. Metoda w pierwszej kolejności sprawdza, czy plik istnieje, jeśli nie - tworzy go (metoda createFile()). Potem już tylko wyrażenia regularne. Istnieje oczywiście metoda odwrotna - saveFile()


saveFile: function () {
		try {
			var fs = new ActiveXObject("Scripting.FileSystemObject");
			var newFile = fs.CreateTextFile(this.getFullPath(), true);

			try {
				newFile.Write(this.getIniString());
			}
			finally {
				newFile.Close();
			}
		}
		catch (e) {
			
		}
	},

Ta z kolei metoda wykorzystuje inna, getIniString, która z obiektów, które w trakcie działania gadgetu mogły ulec zmianie (title i last_comment) zwraca string, który umieszczany jest w pliku Settings.ini

Podsumowanie

Po części drugiej mamy więc określony sposób na pobieranie treści strony oraz selekcjonowanie z niej interesujących nas elementów. Wiemy też skąd gadget będzie brał listę adresów do monitorowania oraz mniej więcej jak będzie je zapisywał. Powiedzmy więc że około 3 pierwszych punktów zostało zrealizowanych :) W części kolejnej coś już może wyświetlimy oraz pobawimy się flyoutem oraz settingsami.

PS. dotrwanie do końca tego wpisu powinno być premiowane w konkursie z diodą...

Szanowna Użytkowniczko! Szanowny Użytkowniku!
×
Aby dalej móc dostarczać coraz lepsze materiały redakcyjne i udostępniać coraz lepsze usługi, potrzebujemy zgody na dopasowanie treści marketingowych do Twojego zachowania. Twoje dane są u nas bezpieczne, a zgodę możesz wycofać w każdej chwili na podstronie polityka prywatności.

Kliknij "PRZECHODZĘ DO SERWISU" lub na symbol "X" w górnym rogu tej planszy, jeżeli zgadzasz się na przetwarzanie przez Wirtualną Polskę i naszych Zaufanych Partnerów Twoich danych osobowych, zbieranych w ramach korzystania przez Ciebie z usług, portali i serwisów internetowych Wirtualnej Polski (w tym danych zapisywanych w plikach cookies) w celach marketingowych realizowanych na zlecenie naszych Zaufanych Partnerów. Jeśli nie zgadzasz się na przetwarzanie Twoich danych osobowych skorzystaj z ustawień w polityce prywatności. Zgoda jest dobrowolna i możesz ją w dowolnym momencie wycofać zmieniając ustawienia w polityce prywatności (w której znajdziesz odpowiedzi na wszystkie pytania związane z przetwarzaniem Twoich danych osobowych).

Od 25 maja 2018 roku obowiązuje Rozporządzenie Parlamentu Europejskiego i Rady (UE) 2016/679 (określane jako "RODO"). W związku z tym chcielibyśmy poinformować o przetwarzaniu Twoich danych oraz zasadach, na jakich odbywa się to po dniu 25 maja 2018 roku.

Kto będzie administratorem Twoich danych?

Administratorami Twoich danych będzie Wirtualna Polska Media Spółka Akcyjna z siedzibą w Warszawie, oraz pozostałe spółki z grupy Wirtualna Polska, jak również nasi Zaufani Partnerzy, z którymi stale współpracujemy. Szczegółowe informacje dotyczące administratorów znajdują się w polityce prywatności.

O jakich danych mówimy?

Chodzi o dane osobowe, które są zbierane w ramach korzystania przez Ciebie z naszych usług, portali i serwisów internetowych udostępnianych przez Wirtualną Polskę, w tym zapisywanych w plikach cookies, które są instalowane na naszych stronach przez Wirtualną Polskę oraz naszych Zaufanych Partnerów.

Dlaczego chcemy przetwarzać Twoje dane?

Przetwarzamy je dostarczać coraz lepsze materiały redakcyjne, dopasować ich tematykę do Twoich zainteresowań, tworzyć portale i serwisy internetowe, z których będziesz korzystać z przyjemnością, zapewniać większe bezpieczeństwo usług, udoskonalać nasze usługi i maksymalnie dopasować je do Twoich zainteresowań, pokazywać reklamy dopasowane do Twoich potrzeb. Szczegółowe informacje dotyczące celów przetwarzania Twoich danych znajdują się w polityce prywatności.

Komu możemy przekazać dane?

Twoje dane możemy przekazywać podmiotom przetwarzającym je na nasze zlecenie oraz podmiotom uprawnionym do uzyskania danych na podstawie obowiązującego prawa – oczywiście tylko, gdy wystąpią z żądaniem w oparciu o stosowną podstawę prawną.

Jakie masz prawa w stosunku do Twoich danych?

Masz prawo żądania dostępu, sprostowania, usunięcia lub ograniczenia przetwarzania danych. Możesz wycofać zgodę na przetwarzanie, zgłosić sprzeciw oraz skorzystać z innych praw wymienionych szczegółowo w polityce prywatności.

Jakie są podstawy prawne przetwarzania Twoich danych?

Podstawą prawną przetwarzania Twoich danych w celu świadczenia usług jest niezbędność do wykonania umów o ich świadczenie (tymi umowami są zazwyczaj regulaminy). Podstawą prawną przetwarzania danych w celu pomiarów statystycznych i marketingu własnego administratorów jest tzw. uzasadniony interes administratora. Przetwarzanie Twoich danych w celach marketingowych realizowanych przez Wirtualną Polskę na zlecenie Zaufanych Partnerów i bezpośrednio przez Zaufanych Partnerów będzie odbywać się na podstawie Twojej dobrowolnej zgody.