Netværkstransparens

Indledning

I internetalderen er det yderst vigtigt at desktopprogrammer kan få adgang til ressourcer via internettet: De skal kunne hente filer fra en web-server, skrive filer til en FTP-server eller læse e-mail fra en e-mail-server. Ofte kaldes muligheden for at få adgang til filer uafhængig af deres sted for netværkstransparens.

Tidligere implementeredes forskellige måder at nå dette mål. Det gamle NFS-filsystem er et forsøg på at implementere netværkstransparens på POSIX-grænsefladesniveau. Mens dette fungerer rigtigt godt i lokale, tætkoblede netværk, skalerer det ikke for ressourcer med utilforladelig og muligvis langsom adgang. Her er asynkronisme vigtig. Mens du venter på at browseren skal hente en side, skal brugergrænsefladen ikke blokeres. Desuden skal sidefremvisningen ikke begynde når hele siden er tilgængelig, men den skal opdateres regelmæssigt mens data ankommer.

I KDE-bibliotekerne implementeres netværkstransparens med KIO-programmeringsgrænsefladen. Det centrale begreb i arkitekturen er et I/O-job. Et job kan kopiere filer, fjerne filer og lignende ting. Så snart et job er startet, arbejder det i baggrunden og blokerer ikke programmet. Al kommunikation fra jobbet tilbage til programmet, såsom at leverere data eller fremgangsinformation, gøres integreret i Qt's begivenhedsløkke.

Baggrundsoperationer opnås ved at starte I/O-slaver til at udføre visse opgaver. I/O-slaver startes som separate processer, og kommunikation sker via Unix domæneudtag. På denne måde behøves intet flertrådssystem, og ustabile slaver kan ikke få programmet som bruger dem til at bryde sammen.

Filsteder udtrykkes med URL'er som er omfattende brugt. Men i KDE, udvider URL'er ikke kun området med tilgængelige filer udenfor det lokale filsystem. De går også i modsat retning, f.eks. kan man søge i tar-arkiver. Dette opnås ved at indlejre URL'er i hinanden. En fil i et tar-arkiv på en HTTP-server ville kunne have URL'en:

http://www-com.physik.hu-berlin.de/~bernd/article.tgz#tar:/paper.tex

Brug af KIO

I de fleste tilfælde oprettes job ved at kalde funktioner i KIO-navnerummet. Disse funktioner har en eller to URL'er som argument, og muligvis også andre nødvendige parametre. Når jobbet er afsluttet, sender det signalet result(KIO::Job*). Efter signalet er sendets, fjerner jobbet sig selv. Derfor ser et typisk brugertilfælde sådan her ud:

void FooClass::makeDirectory()
{
    SimpleJob *job = KIO::mkdir(KURL("file:/home/bernd/kiodir"));
    connect( job, SIGNAL(result(KIO::Job*)), 
             this, SLOT(mkdirResult(KIO::Job*)) );
}

void FooClass::mkdirResult(KIO::Job *job)
{
    if (job->error())
        job->showErrorDialog();
    else
        cout << "mkdir gik godt" << endl;
}

Afhængig af jobtypen, kan du også forbinde til andre signaler.

Her er en oversigt over de mulige funktioner:

KIO::mkdir(const KURL &url, int permission)

Opretter en mappe, valgfrit med visse rettigheder.

KIO::rmdir(const KURL &url)

Fjerner en mappe.

KIO::chmod(const KURL &url, int permissions)

Ændrer rettigheder for en fil.

KIO::rename(const KURL &src, const KURL &dest, bool overwrite)

Omdøber en fil.

KIO::symlink(const QString &target, const KURL &dest, bool overwrite, bool showProgressInfo)

Opretter et symbolsk link.

KIO::stat(const KURL &url, bool showProgressInfo)

Finder nogen information om filen, såsom størrelse, ændringstid og rettigheder. Informationen kan hentes fra KIO::StatJob::statResult() efter jobbet er afsluttet.

KIO::get(const KURL &url, bool reload, bool showProgressInfo)

Overfører data fra en URL.

KIO::put(const KURL &url, int permissions, bool overwrite, bool resume, bool showProgressInfo)

Overfører data til en URL.

KIO::http_post(const KURL &url, const QByteArray &data, bool showProgressInfo)

Sender data. Især for HTTP.

KIO::mimetype(const KURL &url, bool showProgressInfo)

Forsøger at finde URL'ens Mime-type. Typen kan hentes fra KIO::MimetypeJob::mimetype() efter jobbet er afsluttet.

KIO::file_copy(const KURL &src, const KURL &dest, int permissions, bool overwrite, bool resume, bool showProgressInfo)

Kopierer en enkelt fil.

KIO::file_move(const KURL &src, const KURL &dest, int permissions, bool overwrite, bool resume, bool showProgressInfo)

Omdøber eller flytter en enkelt fil.

KIO::file_delete(const KURL &url, bool showProgressInfo)

Sletter en enkelt fil

KIO::listDir(const KURL &url, bool showProgressInfo)

Laver en liste med indholdet i en mappe. Hver gang nogle nye indgange bliver kendte, sendes signalet KIO::ListJob::entries().

KIO::listRecursive(const KURL &url, bool showProgressInfo)

Ligner funktionen listDir(), men denne er rekursiv.

KIO::copy(const KURL &src, const KURL &dest, bool showProgressInfo)

Kopierer en fil eller mappe. Mapper kopieres rekursivt.

KIO::move(const KURL &src, const KURL &dest, bool showProgressInfo)

Flytter eller omdøber en fil eller mappe.

KIO::del(const KURL &src, bool shred, bool showProgressInfo)

Sletter en fil eller mappe.

Mappeindgange

Begge jobbene KIO::stat() og KIO::listDir() returnerer deres resultater med typerne UDSEntry og UDSEntryList. Den sidste er defineret som QValueList<UDSEntry>. Forkortelsen UDS betyder "Universal directory service" (Generel mappetjeneste). Principperne bagved dette er at mappeindgangen kun indeholder information som en I/O-slave kan sørge for, ikke mere. For eksempel sørger HTTP-slaven ikke for nogen information om adgangsrettigheder eller ejere af filer. I stedet er en UDSEntry en liste med UDSAtoms. Hvert objekt sørger for en vis information. Den består af en type som opbevares i m_uds, og enten en heltalsværdi i m_long, eller en strengværdi i m_str, afhængig af typen.

Følgende typer er for øjeblikket definerede:

  • UDS_SIZE (heltal) - Filens størrelse.

  • UDS_USER (streng) - Brugeren som ejer filen.

  • UDS_GROUP (streng): Gruppen som ejer filen.

  • UDS_NAME (streng): Filnavnet.

  • UDS_ACCESS (heltal) - Filens rettigheder, som f.eks. opbevares af C-biblioteksfunktionen stat() i feltet st_mode.

  • UDS_FILE_TYPE (heltal): Filtypen, som f.eks. opbevares af stat() i feltet st_mode. Derfor kan du bruge almindelige makroer fra C-biblioteket, som S_ISDIR, for at kontrollere værdien. Bemærk at data som sørges for af I/O-slaver svarer til stat(), ikke lstat(), dvs. i tilfældet med symbolske link, så er filtyperne typen på filen som linket peger på, ikke selve linket.

  • UDS_LINK_DEST (streng): I tilfældet med et symbolsk link, navnet på filen som udpeges.

  • UDS_MODIFICATION_TIME (heltal) - Tiden (med typen time_t) da filen sidst ændredes, som f.eks. opbevares af stat() i feltet st_mtime.

  • UDS_ACCESS_TIME (heltal) - Tiden da filen sidst blev brugt, som f.eks. opbevares af stat() i feltet st_atime.

  • UDS_CREATION_TIME (heltal) - Tiden da filen oprettedes, som f.eks. opbevares af stat() i feltet st_ctime.

  • UDS_URL (streng) - Sørger for en fils URL, hvis den ikke blot er sammensætningen af mappens URL og filnavnet.

  • UDS_MIME_TYPE (streng): Filens Mime-type

  • UDS_GUESSED_MIME_TYPE (streng): Mime-type for filen som gættet af slaven. Forskellen til den foregående type er at den som sørges for her ikke skal betragtes som tilforladelig (eftersom at afgøre den på en tilforladelig måde ville være for dyrt). Klassen KRun kontrollerer for eksempel udtrykkelig Mime-typen, hvis den ikke har tilforladelig information.

Selv om måden at opbevare information om filer i en UDSEntry er fleksibel og praktisk ud fra en I/O-slaves synvinkel, er det noget rod at bruge for den som skriver programmet. For eksempel for at finde ud af Mime-typen for filen, skal du løbe gennem hele indholdet og kontrollere om m_uds er UDS_MIME_TYPE. Heldigvis, er der en programmeringsgrænseflade som er meget nemmere at bruge: klassen KFileItem.

Synkron brug

Ofte er KIO's asynkrone programmeringsgrænseflade for kompleks at bruge, og derfor er implementering af fuldstændig asynkronisme ikke en prioritet. I et program som for eksempel kun kan håndtere en dokumentfil af gangen, er der alligevel ikke meget som kan gøres mens programmet henter en fil. I disse enkle tilfælde, er der en meget nemmere programmeringsgrænseflade, i form af et antal statiske funktioner i KIO::NetAccess. For eksempel for at kopiere en fil, bruges:

KURL source, target;
source = ...;
target = ...
KIO::NetAccess::copy(source, target);

Funktionen returnerer efter hele kopieringsprocessen er afsluttet. Alligevel så sørger denne metode for en fremgangsdialog, og den sikrer at programmet behandler omtegningsbegivenheder.

En særlig interessant kombination af funktioner er download() sammen med removeTempFile(). Den første henter en fil fra en given URL, og gemmer den i en midlertidig fil med et entydigt navn. Navnet opbevares som det andet argument. Hvis URL'en er lokal, hentes filen ikke, men i stedet sættes det andet argument til det lokale filnavn. Funktionen removeTempFile() sletter filen som angives af argumentet, hvis filen blev oprettet af den foregående nedtegning. Hvis dette ikke er tilfældet, gør den ingenting. På den måde får man et meget enkelt kodefragment til at indlæse filer, uafhængig af deres sted:

KURL url;
url = ...;
QString tempFile;
if (KIO::NetAccess::download(url, tempFile) {
    // indlæse filen med navnet tempFile
    KIO::NetAccess::removeTempFile(tempFile);
}

Metadata

Som det ses ovenfor, er grænsefladen for I/O-job ganske abstrakt og håndterer ikke nogen udbytning af information mellem programmer og I/O-slaver som er protokolspecifik. Det er ikke altid passendet. Man kan for eksempel give visse parametre til HTTP-slaven for at styre dens cacheopførsel eller sende en mængde cookies sammen med forespørgsler. Til dette behov er et begreb med metadata indført. Når et job oprettes, kan man indstille det ved at tilføje metadata til det. Hvert metadataobjekt består af et par med nøgle og værdi. For eksempel for at forhindre HTTP-slaven fra at hente en URL fra cachen, kan du bruge:

void FooClass::reloadPage()
{
    KURL url("http://www.kdevelop.org/index.html");
    KIO::TransferJob *job = KIO::get(url, true, false);
    job->addMetaData("cache", "reload");
    ...
}

Samme teknik bruges i den anden retning, dvs. til kommunikation fra slaven til programmet. Metoden Job::queryMetaData() spørger efter værdien af en vis nøgle som levereres af slaven. For HTTP-slaven, er et sådant eksempel nøglen "modified" (ændret), som indeholder datoen da URL'en sidst blev ændret (i form af en streng). Et eksempel på hvordan det kan bruges er følgende:

void FooClass::printModifiedDate()
{
    KURL url("http://developer.kde.org/documentation/kde2arch/index.html");
    KIO::TransferJob *job = KIO::get(url, true, false);
    connect( job, SIGNAL(result(KIO::Job*)),
             this, SLOT(transferResult(KIO::Job*)) );
}

void FooClass::transferResult(KIO::Job *job)
{
    QString mimetype;
    if (job->error())
        job->showErrorDialog();
    else {
        KIO::TransferJob *transferJob = (KIO::TransferJob*) job;
        QString modified = transferJob->queryMetaData("modified");
        cout << "Seneste ændring: " << modified << endl;
}

Skemalægning

Når KIO-programmeringsgrænsefladen bruges, behøver du oftest ikke håndtere detaljerne med at starte I/O-slaver og kommunikere med dem. Det normale brugstilfælde er at starte et job med nogle parametre, og håndtere signalerne som jobbet sender.

Bagved scenen er scenariet meget mere kompliceret. Når du opretter et job, lægges det i en kø. Når programmet går tilbage til begivenhedsløkken, tildeles KIO slaveprocesser for jobbene i køen. For det første job som startes, er dette trivielt: en I/O-slave for en passende protokol startes. Efter jobbet (såsom en nedtagning fra en HTTP-server) er afsluttet, tages det dog ikke væk med det samme. I stedet tilføjes det til en gruppe med ledige slaver og fjernes efter en vis tid uden aktivitet (for øjeblikket tre minutter). Hvis en ny forespørgsel for samme værtsmaskine og protokol ankommer, genbruges slaven. Den åbenbare fordel er at ved en serie job med samme værtsmaskine, sparer man omkostningen ved at oprette nye processer, og muligvis også at gå gennem adgangskontrol.

Naturligvis er genbrug kun mulig når den eksisterende slave allerede har afsluttet sit tidligere job. Hvis en ny forespørgsel ankommer mens en eksisterende slaveproces stadigvæk kører, skal en ny proces startes og bruges. Med brugen i eksemplerne ovenfor af programmeringsgrænsefladen, er der ingen begrænsning på at oprette nye slaveprocesser: hvis man starter en serie nedtagninger af 20 forskellige filer i række, laver KIO 20 slaveprocesser. Dette system til at tildele slaver til job kaldes direkte. Det er ikke altid det mest passende system, eftersom det kan behøve meget hukommelse og give høj belastning både på klient- og servermaskine.

Så der er en anden måde. Man kan skemalægge job. Hvis man gør det, laves kun et begrænset antal (for øjeblikket tre) slaveprocesser for en protokol. Hvis du opretter flere job endnu det, så tilføjes de i en kø og behandles når en slaveprocess bliver ledig. Det gøres på følgende måde:

KURL url("http://developer.kde.org/documentation/kde2arch/index.html");
KIO::TransferJob *job = KIO::get(url, true, false);
KIO::Scheduler::scheduleJob(job);

En tredje mulighed er forbindelsesorienteret. For eksempel for IMAP-slaven, giver det ikke mening at starte flere processer for samme server. Kun en IMAP-forbindelse af gangen skal opretholdes. I dette tilfælde skal programmet udtrykkelig håndtere slavebegrebet. Det skal tildele en slave for en vis forbindelse og derefter tildele alle job som skal gå til samme forbindelse til samme slave. Dette kan igen nemt opnås ved at bruge KIO::Scheduler:

KURL baseUrl("imap://bernd@albert.physik.hu-berlin.de");
KIO::Slave *slave = KIO::Scheduler::getConnectedSlave(baseUrl);

KIO::TransferJob *job1 = KIO::get(KURL(baseUrl, "/INBOX;UID=79374"));
KIO::Scheduler::assignJobToSlave(slave, job1);

KIO::TransferJob *job2 = KIO::get(KURL(baseUrl, "/INBOX;UID=86793"));
KIO::Scheduler::assignJobToSlave(slave, job2);

...

KIO::Scheduler::disconnectSlave(slave);

Du kan kun afbryde slaven efter alle job som blev tildelt den er garanteret at være afsluttet.

Definition af en I/O-slave

I det følgende beskriver vi hvordan du kan tilføje en ny I/O-slave til systemet. På lignende måde som tjenester, annonceres I/O-slaver for systemet ved at installere en lille konfigurationsfil. Følgende fragment af Makefile.am installerer FTP-protokollen:

protocoldir = $(kde_servicesdir)
protocol_DATA = ftp.protocol
EXTRA_DIST = $(mime_DATA)

Indholdet i filen ftp.protocol er følgende:

[Protocol]
exec=kio_ftp
protocol=ftp
input=none
output=filesystem
listing=Name,Type,Size,Date,Access,Owner,Group,Link,
reading=true
writing=true
makedir=true
deleting=true
Icon=ftp

Indgangen "protocol" angiver hvilken protokol som slaven har ansvar for. "exec" er (i modsætning til hvad man naivt kan forvente sig) navnet på biblioteket som implementerer slaven. Når det er meningen at slaven skal starte, startes programmet "kdeinit", som derefter indlæser biblioteket i sit adresserum. I praksis kan du betragte slaven som kører som en separat proces, også selvom den er implementeret som et bibliotek. Fordelen ved denne mekanisme er at det sparer meget hukommelse, og reducerer tiden som behøves til linkning under kørsel.

Linjerne "input" og "output" bruges ikke for øjeblikket.

De tilbageværende linjer i filen .protocol angiver hvilke muligheder slaven har. Generelt er de funktioner som slaven skal implementere meget enklere end de funktioner som KIO-programmeringsgrænsefladen sørger for programmet. Grunden til dette er at komplekse job skemalægges som en følge af deljob. For eksempel for at få en liste over en mappe rekursivt, startes et job for topniveaumappen. For hver undermappe som rapporteres tilbage, startes nye underjob. Skemalægning i KIO sikrer at ikke for mange job er aktive samtidigt. På lignende måde, for at kopiere en fil med en protokol som ikke understøtter kopiering direkte (såsom FTP-protokollen), kan KIO læse kildefilen og derefter skrive data til målfilen. For at dette skal virke, skal .protocol annoncere handlingerne som slaven understøtter.

Eftersom slaver indlæses som delte biblioteker, men udgør fuldstændige programmer, ser deres kodeskelet noget anderledes ud sammenlignet med normale delte biblioteksindstiksprogrammer. Funktionen som kaldes for at starte slaven kaldes kdemain(). Denne funktion gør en del initieringer, og går derefter ind i en begivenhedsløkke og venter på forespørgsler fra programmet som bruger den. Dette ser ud som følger:

extern "C" { int kdemain(int argc, char **argv); }

int kdemain(int argc, char **argv)
{
    KLocale::setMainCatalogue("kdelibs");
    KInstance instance("kio_ftp");
    (void) KGlobal::locale();

    if (argc != 4) {
        fprintf(stderr, "Usage: kio_ftp protocol "
                        "domain-socket1 domain-socket2\n");
        exit(-1);
    }

    FtpSlave slave(argv[2], argv[3]);
    slave.dispatchLoop();
    return 0;
}

Implementering af en I/O-slave

Slaver implementeres som delklasser til KIO::SlaveBase (FtpSlave i eksemplet ovenfor). På den måde svarer handlingerne i .protocol til visse virtuelle funktioner i KIO::SlaveBase som implementeringen af slaven skal omimplementere. Her er en liste med mulige handlinger og tilsvarende virtuelle funktioner:

læse: Læser data fra en URL

void get(const KURL &url)

skrive: Skriver data til en URL og opretter filen hvis den ikke findes endnu.

void put(const KURL &url, int permissions, bool overwrite, bool resume)

flytte: Omdøber filen.

void rename(const KURL &src, const KURL &dest, bool overwrite)

slette: Fjerner en fil eller mappe.

void del(const KURL &url, bool isFile)

liste: Giver en liste med indholdet i en mappe.

void listDir(const KURL &url)

oprette mappe: Opretter en mappe.

void mkdir(const KURL &url, int permissions)

Desuden er der funktioner som kan omimplementeres, og ikke er på listen i filen .protocol. For disse handlinger, afgør KIO automatisk om de understøttes eller ej (dvs. standardimplementationen returnerer en fejl).

Leverer information om en fil, ligner C-funktionen stat().

void stat(const KURL &url)

Ændrer adgangsrettigheder for en fil.

void chmod(const KURL &url, int permissions)

Afgør Mime-type for en fil.

void mimetype(const KURL &url)

Kopierer en fil.

copy(const KURL &url, const KURL &dest, int permissions, bool overwrite)

Opretter et symbolsk link.

void symlink(const QString &target, const KURL &dest, bool overwrite)

Alle disse implementationer skal slutte af med et af to kald: Hvis handlingen lykkedes, skal de kalde finished(). Hvis en fejl opstod, skal de kalde error() med en fejlkode som første argument og en streng som andet. Mulige fejlkoder er på listen som nummereringstypen KIO::Error. Det andet argument er oftest URL'en det drejer sig om. Den bruges f.eks. i KIO::Job::showErrorDialog() for at parametrisere fejlmeddelelsen som er læsbart af brugeren.

For slaver som svarer til netværksprotokoller, kan det være interessant at omimplementere metoden SlaveBase::setHost(). Den kaldes for at fortælle slaveprocessen om værtsmaskine og port, og brugernavn og kodeord der skal bruges for at logge på. Generelt kan metadata som angives af programmet hentes med SlaveBase::metaData(). Du kan kontrollere om metadata med en vis nøgle findes med SlaveBase::hasMetaData().

Kommunikation tilbage til programmet

Diverse handlinger som implementeres i en slave, behøver en måde at sende data tilbage til programmet som bruger slaveprocessen.

  • get() sender datablokke. Det gøres med data(), som bruger argumentet QByteArray. Du behøver naturligvis ikke sende al data på en gang. Hvis du sender en stor fil, så kald data() med mindre datablokke, så programmet kan behandle dem. Kald finished() når overførslen er klar.

  • listDir() rapporterer information om indgangene i en mappe. Kald listEntries() med en KIO::UDSEntryList som argument, for dette formål. På tilsvarende måde som data(), kan du kalde den flere gange. Når du er klar, så kald listEntry() med det andet argument sat til true. Du kan også kalde totalSize() for at rapportere det totale antal mappeindgange, hvis det er kendt.

  • stat() rapporterer information om en fil, såsom størrelse, Mime-type, etc. Sådan information pakkes i en KIO::UDSEntry, som beskrives nedenfor. Brug statEntry() for at sende et sådant objekt til programmet.

  • mimetype() kalder mimeType() med et strengargument.

  • get() og copy() vil måske sørge for fremgangsinformation. Dette gøres med metoderne totalSize(), processedSize() og speed(). Den totale størrelse og den behandlede størrelse rapporteres som byte, og hastigheden som byte pr sekund.

  • Du kan sende vilkårlige nøgle/værdipar af metadata med setMetaData().

Kommunikation med brugeren

Ind imellem skal en slave kommunikere med brugeren. Eksempler kan være informative meddelelser, dialoger til godkendelseskontrol og bekræftelsesdialoger når en fil er ved at blive overskrevet.

  • infoMessage(): Dette er for informativ tilbagemelding, såsom meddelelsen "Henter data fra <værtsmaskine>" fra HTTP-slaven, som ofte vises i programmets statuslinje. På programsiden, svarer metoden til signalet KIO::Job::infoMessage().

  • warning(): Viser en advarsel i et meddelelsesfelt med KMessageBox::information(). Hvis et meddelelsesfelt stadigvæk vises fra et tidligere kald af warning() fra samme underproces, sker der ingenting.

  • messageBox(): Denne er endnu udførligere end den tidligere metode. Den tillader at et meddelelsesfelt med tekst og titel og nogle knapper vises. Se nummereringstypen SlaveBase::MessageBoxType som reference.

  • openPassDlg(): Viser en dialog til at indtaste brugernavn og kodeord.