// szybki kurs libgadu
// (c) copyright 2001-2003 by wojtek kaniewski <wojtekka@irc.pl>
//			      robert j. wozny <speedy@ziew.org>

// tekst poprawiany ostatnio 2003-03-18

każda sesja jest opisywana przez ,,struct gg_session''. biblioteka może
w ramach jednego procesu/wątku obsługiwać tyle sesji, na ile pozwolą
zasoby. na początku deklarujemy:

	struct gg_session *blah;

następnie będziemy się łączyć. przykład będzie dotyczył socketów
nieblokujących, bo w większości aplikacji ciężko sobie pozwolić na
zawieszanie programu na czas łączenia.

	struct gg_login_params p;

	memset(&p, 0, sizeof(p));
	p.uin = 123456;
	p.password = "hasło";
	p.async = 1;
	p.status = GG_STATUS_INVISIBLE;

	if (!(blah = gg_login(&p)))
		my_error();

jeśli uda się rozpocząć proces łączenia, dostajemy wskaźnik do struktury,
inaczej NULL. wywołanie gg_login() powoduje uruchomienie drugiego procesu
lub wątku w tle, który wywoła gethostbyname() i potokiem zwróci wynik.
później połączy się z serwerem, wyśle, odbierze, połączy się ze wskazanym
adresem IP, zaloguje się itd. jako że wszystko dzieję się w tle, klient
musi sprawdzać cały czas podane deskryptory. pole ,,blah->fd'' zawiera
deskryptor, a ,,blah->check'' jest bitmapą i zawiera GG_CHECK_READ i/lub
GG_CHECK_WRITE jeśli mamy sprawdzić czy przyszły nowe dane i/lub możemy
wysyłać. jeśli coś się wydarzy, wywołujemy ,,gg_watch_fd()'', a libgadu
sobie już sprawdzi, co takiego się zdarzyło:

	while (1) {
		fd_set rd, wr, ex;

		FD_ZERO(&rd);
		FD_ZERO(&wr);
		FD_ZERO(&ex);

		if ((blah->check & GG_CHECK_READ))
			FD_SET(blah->fd, &rd);
		if ((blah->check & GG_CHECK_WRITE))
			FD_SET(blah->fd, &wr);
		FD_SET(blah->fd, &ex);

		if (select(blah->fd + 1, &rd, &wr, &ex, NULL) == -1)
			my_error();

		if (FD_ISSET(blah->fd, &ex))
			my_error();

		if (FD_ISSET(blah->fd, &rd) || FD_ISSET(blah->fd, &wr))
			my_handle_event();
	}

dla uproszczenia, nie ma tutaj obsługi timeoutów i tym podobnych dodatków.
poza tym, jeśli program sprawdza też inne deskryptory (np. stdin dla
klientów konsolowych), dobrze byłoby sprawdzić, czy dana sesja coś robi i
nie sprawdzać ,,blah->fd'' jeśli ,,blah->state == GG_STATE_IDLE''. od czasu
do czasu można dać serwerowi znać, że coś się dzieje, za pomocą...

	gg_ping(blah);

ale to już wymaga implementacji timerów i liczenia czasu od ostatniego
pinga. ,,blah->last_event'' mówi, kiedy dostaliśmy cokolwiek ostatnio od
serwera. wszystkie pola struktury są opisane w pliku libgadu.h.

wracając do obsługi deskryptorów -- jeśli klient zauważy, że coś się
zmieniło na podanym sockecie, powinien wywołać ,,gg_watch_fd()'',
która wszystkim się zajmie. zwraca ona wskaźnik do zaalokowanej
struktury opisującej zdarzenie. po obejrzeniu należy zwolnić ją za
pomocą ,,gg_event_free()''. w powyższym przykładzie jest wywoływana
funkcja ,,my_handle_event()'', która może wyglądać tak:

	struct gg_event *e;
	
	if (!(e = gg_watch_fd(blah)))
		my_error();

	switch (e->type) {
		case GG_EVENT_NONE:
		case GG_EVENT_PONG:
			/* olewamy */
			break;

		case GG_EVENT_CONN_SUCCESS:
			printf("połączono!\n");
			/* tutaj wysyłamy userlistę za pomocą gg_notify() */
			break;

		case GG_EVENT_CONN_FAILED:
			printf("nie udało się\n");
			/* powód w e->event.failure, stałe GG_FAILURE_... */
			break;

		case GG_EVENT_MSG:
			printf("masz wiadomość!\n");
			printf("od: %d\n", e->event.msg.sender);
			printf("treść: %s\n", e->event.msg.message);
			/* e->event.msg.class mówi czy rozmowa czy wiad. */
			/* jeśli e->event.msg.sender równy 0, to mamy */
			/* wiadomość systemową o numerze w msg.class */
			break;

		case GG_EVENT_NOTIFY:
			printf("oto ludzie, którzy się pojawili: ");
			/* tutaj sprawdzanie tablicy e->event.notify */
			break;

		case GG_EVENT_STATUS:
			printf("ktoś %d zmienił stan\n", e->event.status.uin);
			/* nowy stan w e->event.status.status */
			break;

		case GG_EVENT_ACK:
			printf("wiadomość dotarła do %d.\n",
				e->event.ack.recipient);
			/* e->event.ack.status mówi czy dotarła do klienta */
			/* czy leży na serwerze, stałe GG_ACK_... */
			/* e->event.ack.seq to numerek wiadomości */
			break;

		case GG_EVENT_PUBDIR50_REPLY:
			printf("znalazło kogoś\n");
			/* opisane niżej */
			break;
	}

	gg_event_free(e);

przy okazji wiadomo, co oznaczają zdarzenia. część z nich można ignorować,
jeśli robi się okrojonego klienta, np. wysyłającego jedną wiadomość z linii
komend.

po zalogowaniu należy wysłać serwerowi listę użytkowników, których mamy
w liście kontaktów. ,,gg_notify()'' przyjmuje za argument tablicę zmiennych
typu ,,uin_t''. w odpowiedzi dostaniemy GG_EVENT_NOTIFY i tablicę struktur
,,struct gg_notify_reply'', jeśli ktoś jest. po szczegóły odsyłam do
libgadu.c, libgadu.h i źródeł konsolowego klienta. jeśli dodajemy lub
usuwamy kogoś w trakcie działania, należy skorzystać z ,,gg_add_notify()''
lub ,,gg_remove_notify()''. jeśli chcemy korzystać z listy osób blokowanych
lub takich, przed którymi się ukrywamy, należy korzystać z funkcji
,,gg_notify_ex()'', ,,gg_add_notify_ex()'' i ,,gg_remove_notify_ex()'',
które biorą dodatkowy argument mówiący, jak traktować użytkownika.
odpowiadają za to stałe GG_USER_NORMAL, GG_USER_BLOCKED i GG_USER_OFFLINE.

żeby zmienić stan na zajęty lub dostępny, używamy ,,gg_change_status()'',
,,gg_change_status_descr()'' lub ,,gg_change_status_descr_time()''.

wysyłanie wiadomości za pomocą ,,gg_send_message()''. parametr ,,class''
mówi, czy ma się pojawić w osobnym okienku (GG_CLASS_MSG) czy w okienku
rozmowy (GG_CLASS_CHAT). funkcja zwraca numer sekwencyjny wiadomości,
którego możemy użyć do potwierdzenia. wiadomość, która ma być sformatowana
w odpowiedni sposób (pogrubienie, kursywa, kolory, itp.) wysyłamy za pomocą
,,gg_send_message_richtext()''. wiadomości konferencyjne wysyłamy funkcjami
,,gg_send_message_confer()'' lub ,,gg_send_message_confer_richtext()''.

jeśli chcemy się wylogować, wywołujemy ,,gg_logoff()'' i potem zwalniamy
pamięć związaną z sesją funkcją ,,gg_free_session()''.

jeśli chcemy przypomnieć swoje hasło, wywołujemy funkcję
,,gg_remind_password2()'', a wynikową struktuję ,,gg_http'' traktujemy
podobnie do ,,gg_session'':
 - sprawdzamy ->fd i ->check,
 - wywołujemy ,,gg_remind_passwd_watch_fd()'', gdy coś się dzieje. funkcja
   ta zwraca -1 w przypadku błędu. jeśli zwraca 0, wywołujemy ją, póki
   ->state nie będzie równe GG_STATE_DONE lub GG_STATE_ERROR.
 - po zakończeniu, wywołujemy ,,gg_remind_passwd_free()''.

jeśli chcemy zmienić hasło, wywołujemy funkcję ,,gg_change_passwd3()''
i traktujemy podobnie wynikowe ,,gg_http''.

do zarządzania listą kontaktów na serwerze służą funkcje
,,gg_userlist_get()'', ,,gg_userlsit_put()'' i ,,gg_userlist_remove()''.

***

OBSŁUGA KATALOGU PUBLICZNEGO GG 5.0 (na podstawie listu na ekg-devel)

skoro już działa, opiszę aktualne API. głównym założeniem była
maksymalna niezależność od zmian w protokole, zmian nazw pól, dodawania
nowych itd. zastosowane podejście może być trochę dziwne na pierwszy rzut
oka, ale podpatrzyłem to w poważniejszych projektach (np. libdbi).

jeśli chcemy szukać:

        gg_pubdir50_t req = gg_pubdir50_new(GG_PUBDIR50_SEARCH_REQUEST);

        if (!req)
                out_of_memory();

        /* szukamy po numerku... */

        gg_pubdir50_add(req, GG_PUBDIR50_UIN, "123456");

        /* lub... */

        gg_pubdir50_add(req, GG_PUBDIR50_FIRSTNAME, "Ania");
        gg_pubdir50_add(req, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_FEMALE);

        /* lub... */

        gg_pubdir50_add(req, GG_PUBDIR50_BIRTHYEAR, "1979 1985");
        gg_pubdir50_add(req, GG_PUBDIR50_START, "0");
        gg_pubdir50_add(req, GG_PUBDIR50_ACTIVE, GG_PUBDIR50_ACTIVE_TRUE);

        /* i w końcu... */

        gg_pubdir50(sesja, req);

        /* i zwalniamy pamięć, albo sobie gdzieś zachowujemy. whatever */

        gg_pubdir50_free(req);

jak witać, gg_pubdir50_new() tworzy obiekt opisujący operację katalogu,
gg_pubdir50_add() dodaje kolejne parametry. rodzaj parametru jest w
rzeczywiści stałą tekstową, np. GG_PUBDIR50_UIN to "FmNumber". należy
pamiętać, że wszystkie argumenty są tekstami. nie trzeba się bawić w ich
alokowanie czy coś takiego. biblioteka sobie sama zapamięta. teksty muszą
być oczywiście w CP1250. na końcu wywołujemy gg_pubdir50() i tyle. funkcja
ta zwraca numer sekwencyjny wyszukiwania, który możemy sobie zachować dla
późniejszych referencji.

żeby otrzymać wynik, należy obsłużyć zdarzenia GG_EVENT_PUBDIR50_SEARCH_REPLY,
GG_EVENT_PUBDIR50_WRITE i GG_EVENT_PUBDIR50_READ. dla przykładu, obsługa
wyników wyszukiwania wygląda następująco:

        gg_search50_t res = zdarzenie->event.search50;
        int count = gg_search50_count(res);

        if (count < 1) {
                wiadomość("Nie znaleziono");
                return;
        }

        for (int i = 0; i < count; i++) {
                const char *uin, *first, *nick, *born, *city, *status;

                uin = gg_pubdir50_get(res, i, GG_PUBDIR50_UIN);
                first = gg_pubdir50_get(res, i, GG_PUBDIR50_FIRSTNAME);
                nick = gg_pubdir50_get(res, i, GG_PUBDIR50_NICK);
                born = gg_pubdir50_get(res, i, GG_PUBDIR50_BIRTHYEAR);
                city = gg_pubdir50_get(res, i, GG_PUBDIR50_CITY);
                status = gg_pubdir50_get(res, i, GG_PUBDIR50_STATUS);

                printf("Numer: %s\nImię: %s\nPseudonim: %s\n"
                        "Urodzony: %s\nMiejscowość: %s\n", uin,
                        first, nick, born, city);

                switch ((status) ? atoi(status) : -1) {
                        case GG_STATUS_AVAIL:
                                printf("Dostępny\n");
                                break;
                        case GG_STATUS_BUSY:
                                printf("Zajęty\n");
                                break;
                        default:
                                printf("Niedostępny\n");
                }

                printf("\n");
        }

        gg_event_free(zdarzenie);

jeśli chcemy wiedzieć, od jakiego numeru zacząć wyszukiwanie, żeby dostać
dalszą część, używamy gg_pubdir50_next(). jeśli chcemy numer sekwencyjny,
używamy gg_pubdir50_seq().

w żadnym wypadku nie można się odwoływać do pól gg_pubdir50_t, ponieważ
mogą się zmieniać między wersjami biblioteki. dzięki odwoływaniu się przez
funkcje, mamy pewność, że bez względu na zmiany API/ABI mamy to samo.
dodatkowo, jeśli dojdą jakieś opcje wyszukiwania, nie trzeba w bibliotece
niczego zmieniać -- żadnych struktur, itd. po prostu odwołujemy się do
kolejnego pola przez gg_pubdir50_add() i gg_pubdir50_get().

***

BEZPOŚREDNIE POŁĄCZENIA

gadu-gadu, w przeciwieństwie do irc, umożliwia połączenia w obie strony,
bez względu na to, który klient nadaje, a który odbiera. do tego, jeśli
obie strony wychodzą z tego samego adresu IP, serwer informuje ich o ich
adresach wewnętrznych z tego samego LANu. mamy kilka możliwych sytuacji:

a) mam publiczny lub niepubliczny adres IP i chcę wysłać plik do kogoś
   z publicznym adresem -- łączę się z jego klientem, przedstawiam się,
   mówię czego chcę i jeśli to zaakceptuje, zaczynam wysyłać plik. bardzo
   to przypomina zwykłe połączenia dcc klientów irc.

b) mam publiczny adres IP i wysyłam plik do kogoś za maskaradą -- wysyłam
   do niego odpowiedni pakiet ctcp (client-to-client protocol). jest to
   pakiet klasy GG_CLASS_CTCP (0x10) o treści składającej się z jednego
   znaku o kodzie 0x02. druga strona, odebrawszy taki pakiet łączy się
   z nami, mówi, że proszono ją o połączenie i czeka na dalsze instrukcje.
   wtedy wysyłamy informację, że owszem, chcemy wysłać plik, mówimy jaki
   i jeśli druga strona to zaakceptuje, nadajemy.

c) mam niepubliczny adres IP, tak samo jak i druga strona -- tutaj
   nawiązanie połączenia jest możliwe tylko i wyłącznie, gdy oba klienty
   znajdują się w tej samej sieci (tj. oba łączą się z serwerem GG z tego
   samego adresu zewnętrznego) i wygląda to wtedy identycznie jak w punkcie
   a).

to, czy możemy się z kimś połączyć widać po porcie, jaki dostajemy w
pakietach gg_notify_reply. jeśli jest mniejszy niż 10, połączenie nie
jest możliwe, a wtedy wysyłamy pakiet ctcp za pomocą funkcji
gg_dcc_request().

każde połączenie związanie z dcc opisywane jest przez strukturę gg_dcc.
najważniejsze jest GG_SESSION_DCC_SOCKET, które odpowiada za przychodzące
połączenia. tworzymy je przez:

	struct gg_dcc *socket = gg_dcc_socket_create(uin, port);

	if (!socket)
		błąd("nie mogę otworzyć socketu");

	dodaj_do_listy_przeglądanych_deskryptorów(socket);

port może wynosić 0, a wtedy libgadu samo weźmie pierwszy lepszy
z brzegu. w razie powodzenia zwraca zaalokowaną strukturę gg_dcc,
której najbardziej interesującym polem jest gg_dcc->port zawierające
numer przyznanego portu. jeśli funkcja zwróci NULL, patrzymy na errno.
EINVAL to niewłaściwie parametry, ENOMEM brak pamięci, a reszta
możliwych błędów to te związane z socketami, typu EADDRINUSE gdy nie
może wolnego portu znaleźć.

teraz wypadałoby ustawić zmienną ,,gg_dcc_port'' i połączyć się z
serwerem GG, żeby ogłosić swoje namiary. ogłaszany adres IP będzie
brany z połączenia z serwerem.

	gg_dcc_port = socket->port;
	połącz_się_z_serwerem();

w każdym razie, gdy pojawi się coś na deskryptorze, wywołujemy:

	struct gg_event *event = gg_dcc_watch_fd(socket);

	if (!event) {
		usuń_z_listy_przeglądanych_deskryptorów(socket);
		gg_dcc_free(socket);
		błąd("poważny błąd"):
	}

błąd jest zwracany tylko w naprawdę krytycznych sytuacjach, gdy
brakuje pamięci, lub nie powiodła się operacja na socketach, która
nie miała się nie powieść (i przy okazji dalsza zabawa jest
kompletnie bezcelowa).

jeśli błędu nie będzie, dostajemy informacje o zdarzeniu. w przypadku
GG_SESSION_DCC_SOCKET mogą to być:

1) GG_EVENT_NONE -- nic ciekawego się nie wydarzyło.

2) GG_EVENT_DCC_ERROR -- wystąpił błąd, którego kod znajduje się w
   event->event.dcc_error. w przypadku tego typu sesji możliwy jest
   tylko GG_ERROR_DCC_HANDSHAKE, który mówi, że nie udało się nawiązać
   połączenia z klientem.

3) GG_EVENT_DCC_NEW -- nowe połączenie od klienta. w polu
   event->event.dcc_new jest struktura gg_dcc typu GG_SESSION_DCC,
   którą dodajemy do listy przeglądanych deskryptorów.

w każdym z tych wypadków należy po sprawdzeniu zdarzenia wywołać funkcję:

	gg_event_free(socket->event);

by zwolnić pamięć po zdarzeniu.


gdy nadejdzie połączenie i dopiszemy je do listy przeglądanych deskryptorów,
musimy zwracać uwagę na następujące zdarzenia:

1) GG_EVENT_NONE -- nic się nie zdarzyło.

2) GG_EVENT_DCC_CLIENT_ACCEPT -- klient się przedstawił i czeka na
   autoryzację połączenia. sprawdzamy gg_dcc->uin czy jest naszym numerem
   i czy gg_dcc->peer_uin jest na naszej liście kontaktów i czy chcemy z
   nim nawiązywać połączenie. jeśli nie, to po prostu usuwamy połączenie:

   	if (!akceptujemy_połączenie(klient->uin, klient->peer_uin)) {
		usuń_z_listy_przeglądanych_deskryptorów(client);
		gg_dcc_free(klient);
	}

3) GG_EVENT_DCC_CALLBACK -- poprosiliśmy klienta, żeby się z nami połączył
   za pomocą gg_dcc_request() i on teraz pyta się, czego chcemy. zaraz po
   tym zdarzeniu należy wywołać funkcję:

	gg_dcc_set_type(klient, rodzaj_połączenia);

   gdzie rodzaj to GG_SESSION_DCC_SEND albo GG_SESSION_DCC_VOICE. jeśli
   wysyłamy plik, można od razu wywołać gg_dcc_fill_file_info(), ale nie
   jest to wymagane. kiedy przyjdzie pora, libgadu sama nas o to poprosi.

4) GG_EVENT_DCC_NEED_FILE_ACK -- klient chce wysłać nam plik. w strukturze
   gg_dcc->file_info znajdują się wszystkie informacje na temat pliku, jak
   jego nazwa, rozmiar, atrybuty, data i czas utworzenia itp. jeśli nie
   chcemy pliku, zamykamy połączenie w podobny sposób jak przy braku
   autoryzacji. libgadu jeszcze nie potrafi odpowiadać negatywnie na prośby
   połączeń dcc. jeśli chcemy plik, otwieramy plik do zapisu i numer jego
   deskryptora zapisujemy do gg_dcc->file_fd. dalej libgadu zajmie się
   transferem.

5) GG_EVENT_DCC_NEED_FILE_INFO -- wcześniej poprosiliśmy drugą stronę by
   się z nami połączyła, bo jest za maskaradą, a my chcemy wysłać plik.
   w tym wypadku możemy albo sami wypełnić strukturę gg_dcc->file_info,
   którą biblioteka wyśle drugiej stronie, albo skorzystać z funkcji
   gg_dcc_fill_file_info().

   	if (gg_dcc_fill_file_info(klient, nazwa_pliku)) {
		błąd("nie mogę otworzyć pliku");
		usuń_z_listy_przeglądanych_deskryptorów(klient);
		gg_dcc_free(klient);
	}

6) GG_EVENT_DCC_DONE -- zakończono transfer, można już nie patrzeć na
   deskryptor i zwolnić pamięć po połączeniu.

7) GG_EVENT_DCC_ERROR -- błąd. możliwy kod błędu to GG_ERROR_DCC_HANDSHAKE
   gdy nie powiodło się ustanowienie połączenia z klientem, GG_ERROR_DCC_NET
   kiedy nie udało się wysłać lub odczytać czegoś z socketa, GG_ERROR_DCC_FILE
   gdy nie można było odczytać albo zapisać do pliku, GG_ERROR_DCC_EOF gdy
   plik lub połączenie zbyt wcześnie się skończy, GG_ERROR_DCC_REFUSED gdy
   użytkownik po drugiej stronie odmówił połączenia.

tutaj również należy pamiętać o wywoływaniu gg_event_free().


jeśli chcemy sami wysłać plik, sprawdzamy najpierw, czy druga strona
może przyjąć połączenie, patrząc na jej port. jeśli powyżej 10, możemy
śmiało wywołać funkcję:

	struct gg_dcc *klient = gg_dcc_send_file(adres_ip, port, nasz_uin, jego_uin);

	if (!klient)
		błąd("nie można ustanowić połączenia");

zaraz potem możemy wywołać funkcję gg_dcc_fill_file_info() by uzupełnić
informację o pliku...

	gg_dcc_fill_file_info(klient, nazwa_pliku);

...ale jeśli tego nie zrobimy teraz, biblioteka poprosi nas o to
w odpowiedniej za pomocą zdarzenia GG_EVENT_DCC_NEED_FILE_INFO.

jeśli port jest podejrzanie niski, znaczy że połączenie jest niemożliwe
i wtedy wywołujemy funkcję:

	gg_dcc_request(sesja_gg, jego_uin);

gdzie sesja_gg to nasza sesja GG (jakoś musimy wysłać wiadomość),
a jego_uin to numer drugiej strony. spowoduje ona, że druga strona
spróbuje się z nami połączyć, jeśli ma taką możliwość.


gdy otrzymamy wiadomość klasy GG_CLASS_CTCP o treści 0x02 znaczy,
że ktoś chce nam coś przesłać i mamy się z nim połączyć. wywołujemy
wtedy:

	struct gg_dcc *klient = gg_dcc_get_file(adres_ip, port, nasz_uin, jego_uin);

	if (!klient)
		błąd("nie można ustanowić połączenia");

dalej tak samo, jak przy zwykłym odbieraniu pliku.

$Id: api.txt,v 1.19 2003/04/15 13:12:55 szalik Exp $