Blog (65)
Komentarze (803)
Recenzje (0)
@tfldpsidebar - programowanie w windows cz.2

dpsidebar - programowanie w windows cz.2

15.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ą...

Wybrane dla Ciebie
Komentarze (3)