amiga-news DEUTSCHE VERSION
.
Links| Forums| Comments| Report news
.
Chat| Polls| Newsticker| Archive
.

amiga-news.de Forum > Programmierung > Progamm<->Internet<->Programm [ - Search - New posts - Register - Login - ]

1 2 -3- 4 [ - Post reply - ]

2006-08-16, 22:33 h

Ralf27
Posts: 2779
User
Zitat:
Original von Holger:
Es ist allenfalls seltsam, dass Du mit dieser Vorgehensweise tatsächlich eine Datei korrekt übertragen haben willst.


Die vorgehensweise war mit lesen von funktionierendem Quellcode vom Thomas (ok, C-Code kann ich nicht testen, aber ich denk mir mal das der Code vom Thomas funktioniert), dem übersetzen und schaun was passiert.

Die übertragungen (es waren wirklich einige) von .lha-Dateien und auch text und dann die Kontrolle (läuft das Programm, kann man denn Text lesen, kann man die lha-Datei entpacken). Und das mit vielen unterschiedlichen Dateien, etc.

Nenn es Trail&Error, bzw. das testen, als ich nicht weiter gekommen bin. Damit hast du schon recht.
Und "leider" ist hier eine Eigenheit von MB reingekommen, die in C vermutlich(fast schon sicherlich) so nicht vorhanden ist (Strings), die hier eine andere Lösungsvariante eröffnet hat, die wohl nicht dem offiziellen weg entspricht.


@thomas (falls du hier noch mitliest):
Wenn ich das jetzt richtig verstanden habe, kann man mit read() doch auf 0 testen = eof. Somit könnte man doch in deinem Beispielcode mit read() das ende herrausfinden. Oder gab es noch einen Grund, wieso du es so gemacht hast, wie du es gemacht hast?
--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-16, 23:44 h

Ralf27
Posts: 2779
User
Eigentlich wollte ich das folgende nicht mehr kommentieren, aber der vollständigkeitshalber:

Zitat:
Original von Holger:
Lies und begreife es auch: String in Basic funktionieren anders als Strings in C.
In C wird ein String durch ein 0 byte beendet, da würde Deine Vorgehensweise schon von vornherein nicht funktionieren.

Mir kann es doch eigentlich vollkommend egal sein wie C seine Strings verarbeitet. Wenn ich denn C-Code lesen und mir klar ist was es macht, dann kann ich es ja auch mit Basic machen. Außerdem hab ich da oben an keinem Punkt denn String auf ASCII 0 am Ende getestet.
Zitat:
Nein! Read() kommt erst zurück, wenn mindestens ein byte gelesen wurde. Man programmiert nicht, in dem man Annahmen macht, sondern mit der Anwendung dessen, was man in der Dokumentation liest.
Das ist mir schon klar und da geb ich dir auch recht das man mit Annahmen nicht programmieren sollte. Deswegen hier auch die Diskusion. Deswegen auch ist es ja auch eine Annahme die ich erst durch stöbern in Doku oder sonst wo entweder bestätigt oder wiederlegt werden muß.

Aber ok, ich hatte ein allgemeines Problem das ich lösen wollte (wie lad ich ne Datei aus dem Internet runter). Wie geht man da am besten vor? Dokus suchen, Programme suchen, es versuchen nachzubauen und testen. Wenn etwas nicht geht halt suchen, etc. und so weiter.

Ich werd nachher auch noch meinem MB-Compiler noch en klaps geben. :D

Ich danke dir übrigens auch für die Hilfe bei dieser etwas eigenwilligen Story. I-)
--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-17, 13:45 h

Ralf27
Posts: 2779
User
Ich hab hier mal denn Code fürs lesen von Dateien:

code:
c$=SPACE$(256)

tcp&=xOpen&(SADD("TCP:home.pages.at/80"+CHR$(0)),MODE_NEWFILE&)
IF tcp& THEN 
 b$="GET http://home.pages.at/a1260/EigenePage/Dateien/Sudoku.lha HTTP/1.0"+CHR$(10)+CHR$(10)
 junk=xWrite(tcp&,SADD(b$),LEN(b$))
 a$="":a=0
 WHILE a=0
  a$=a$+LEFT$(c$,xRead(tcp&,SADD(c$),256))
  a=INSTR(a$,CHR$(13)+CHR$(10)+CHR$(13)+CHR$(10))
 WEND
 Header$=LEFT$(a$,a)
 Daten$=MID$(a$,a+4)
 a=INSTR(Header$," ")
 IF a THEN
  IF MID$(Header$,a,5)=" 200 " THEN
   
   a=INSTR(Header$,"Last-Modified: ")
   IF a THEN
    a=a+15:a$=MID$(Header$,a,INSTR(a,Header$,CHR$(13))-a)
    PRINT"Zeitstempel: "a$
   END IF
   
   a=INSTR(Header$,"Content-Length: ")
   IF a THEN
    a=a+16:a$=MID$(Header$,a,INSTR(a,Header$,CHR$(13))-a)
    PRINT"Länge: "a$" Bytes"
   END IF

   file&=xOpen&(SADD("ram:Sudoku2.lha"+CHR$(0)),MODE_NEWFILE&)
   IF file& THEN
    a$=Daten$:size=0
    DO
     size=size+LEN(a$)
     IF LEN(a$)<>xWrite(file&,SADD(a$),LEN(a$))THEN
      PRINT"Konnte Datei nicht komplett schreiben!"
      a$=""
     ELSE
      a$=LEFT$(c$,xRead(tcp&,SADD(c$),256))
     END IF
    LOOP UNTIL a$=""
    junk=xClose(file&)
    PRINT"Gelesen:"size" Bytes"
   ELSE
    PRINT"Konnte Ausgabefile nicht anlegen"
   END IF
  ELSE
   PRINT"Konnte Datei nicht finden"
  END IF
 ELSE
  PRINT"Fehlerhafter Request"
 END IF
 junk=xClose(tcp&)
ELSE
 PRINT"Konnte Server nicht finden"
END IF


So, man könnte noch abfragen ob das Schliesen der Datei etc. klappt, oder gar ob das schreiben nach tcp funktioniert, aber mehr dürfte da kaum noch falsch laufen. die eine Schleife mit dem suchen nach dem ende des Headers könnte man nach etwas überarbeiten, aber sonst?

Eigentlich dürfte das da oben jetzt(Hinweis: Ist mehr symbolisch, da im richtigen einsatz z.b. kein PRINT benutzt wird) korrekt sein. Oder findet ihr da nochwas, was absolut verkorkst ist?

So, was macht man nicht alles in der Mittagspause. :D
--
http://www.alternativercomputerclub.de.vu

[ Dieser Beitrag wurde von Ralf27 am 17.08.2006 um 13:55 Uhr geändert. ]

[ - Answer - Quote - Direct link - ]

2006-08-17, 17:32 h

Holger
Posts: 8116
User
Zitat:
Original von Ralf27:
Mir kann es doch eigentlich vollkommend egal sein wie C seine Strings verarbeitet. Wenn ich denn C-Code lesen und mir klar ist was es macht, dann kann ich es ja auch mit Basic machen.

Es ging aber um das, was Du in Basic mit String Operationen gemacht hast, was Thomas definitiv nicht in seinem C-Code gemacht hat. Ich schrieb
Zitat:
Nein, diese Art von Funktionen gibt es dort nicht, weil diese String-Funktionen einer Basic-eigenen Speicherverwaltung unterliegen.
worauf Du schriebst:
Zitat:
Nun, im Code vom Thomas sind diese Befehle drin. Die liegen in den folgenden Includes:
Und darauf kann man einfach nichts anderes, als Dir mit dem Holzhammer einzuprügeln, dass C-Stringfunktion nichts, aber auch gar nichts mit Basic-Stringfunktionen gemeinsam haben. Und wenn Du Basic-Strings als Lesepuffer missbrauchst mag das mangels Basic-Speicherbefehle vielleicht noch angehen (obwohl noch ein AllocMem und FreeMem hier kaum noch in's Gewicht fallen würden).
Wenn Du aber anfängst, solche Strings an andere Variablen zuzuweisen und über Basic miteinander zu vergleichen, begibst Du Dich auf's Glatteis.

Für die Bearbeitung des Headers, der ja wirklich aus Text besteht, mag Dein jetziger code ok sein. Die gelesenen Binärdaten sollte man aber nicht mit Stringfunktionen bearbeiten. Dein code wird nicht nur sauberer, sondern auch effizienter, wenn Du auf diese Manipulationen, die möglicherweise Speicher hin- und herkopieren, verzichtest.

Du liest in Deine String-Variable, erzeugst einen neuen String mit LEFT$ und dem Rückgabewert von Read(), um den neuen String zu schreiben und dessen Länge als Paramter an Write() zu übergeben. Genausogut kannst Du durchgängig den gleichen Puffer (String) benutzen und einfach den Rückgabewert von Read(), also Anzahl gültiger bytes an Write() übergeben. Damit sparst Du Dir die String-Operationen LEFT$ und LEN.

Was Dir immer noch fehlt, ist der Test, ob Read() -1 zurückgibt. Das hieße Lesefehler und bei Netzwerkverbindungen kann es nunmal zu Problemen kommen.

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-17, 17:58 h

Holger
Posts: 8116
User
Übrigens, wenn es Dir darum geht, eine Update-Funktion zu implementieren, solltest Du den Http-Header um "If-Modified-Since" erweitern. Auf diese Weise sparst Du Dir die Dateiübertragung, wenn kein Update vorliegt. Der Header könnte z.B. so aussehen:
code:
GET http://home.pages.at/a1260/EigenePage/Dateien/Sudoku.lha HTTP/1.0
If-Modified-Since: Thu, 17 Aug 2006 17:43:34 CEST

Wenn der Server dann mit Code 304 antwortet, weißt Du, dass es keine neue Version gibt.

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-17, 20:43 h

Ralf27
Posts: 2779
User
Zitat:
Original von Holger:
Übrigens, wenn es Dir darum geht, eine Update-Funktion zu implementieren, solltest Du den Http-Header um "If-Modified-Since" erweitern. Auf diese Weise sparst Du Dir die Dateiübertragung, wenn kein Update vorliegt. Der Header könnte z.B. so aussehen:
code:
GET http://home.pages.at/a1260/EigenePage/Dateien/Sudoku.lha HTTP/1.0
If-Modified-Since: Thu, 17 Aug 2006 17:43:34 CEST

Wenn der Server dann mit Code 304 antwortet, weißt Du, dass es keine neue Version gibt.

Ok, ist auch en Argument. Ich lese halt zur Zeit nur den Header ein (vielleicht 256 Bytes oder gar 512Bytes) und schau nach wie Groß die Datei ist, bzw. von wann.

Wenn ich jetzt die oben genannte Funktion nehme, dann sollte ich vermutlich nur aufs Datum achten und nicht auf die Uhrzeit. Ok, liest sich recht gut.
Aber, bevor ich das ganze Teste mal eine Frage:
Ist der Header dann genau so lang um die 301 zu übertragen?
--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-17, 20:49 h

Ralf27
Posts: 2779
User
Zitat:
Original von Holger:
Und darauf kann man einfach nichts anderes, als Dir mit dem Holzhammer einzuprügeln, dass C-Stringfunktion nichts, aber auch gar nichts mit Basic-Stringfunktionen gemeinsam haben. Und wenn Du Basic-Strings als Lesepuffer missbrauchst mag das mangels Basic-Speicherbefehle vielleicht noch angehen (obwohl noch ein AllocMem und FreeMem hier kaum noch in's Gewicht fallen würden).
Wenn Du aber anfängst, solche Strings an andere Variablen zuzuweisen und über Basic miteinander zu vergleichen, begibst Du Dich auf's Glatteis.

Hm, sorry, ich verseh es jetzt wirklich nicht. Das mit AllocMem ist mir klar und ich hab mich recht weit oben auch schon dafür entschuldigt das ich quasi ne Abkürzung genommen habe und nicht über AllocMem() gegangen bin.
Zitat:
Für die Bearbeitung des Headers, der ja wirklich aus Text besteht, mag Dein jetziger code ok sein. Die gelesenen Binärdaten sollte man aber nicht mit Stringfunktionen bearbeiten.
Mal kurz ne Frage zwischen rein. Wo issen da der unterschied zwischen Binär und Text in einem String? Es dürfte doch eigentlich keinen geben. Jedenfalls ist mir da jetzt keiner ersichtlich.
Ein String ist ja eigentlich nur ein Speicherbereich, auf dem die Variable hinweist.
Zitat:
Dein code wird nicht nur sauberer, sondern auch effizienter, wenn Du auf diese Manipulationen, die möglicherweise Speicher hin- und herkopieren, verzichtest.

Du liest in Deine String-Variable, erzeugst einen neuen String mit LEFT$ und dem Rückgabewert von Read(), um den neuen String zu schreiben und dessen Länge als Paramter an Write() zu übergeben. Genausogut kannst Du durchgängig den gleichen Puffer (String) benutzen und einfach den Rückgabewert von Read(), also Anzahl gültiger bytes an Write() übergeben. Damit sparst Du Dir die String-Operationen LEFT$ und LEN.

Da hast du vollkommend recht, da hatte ich wohl wirklich Tomaten auf den Augen. Da kann ich mir einiges sparen. So kann es wohl leider auch kommen wenn man vor lauter Bytes den Code nicht mehr sieht. Danke.
Zitat:
Was Dir immer noch fehlt, ist der Test, ob Read() -1 zurückgibt. Das hieße Lesefehler und bei Netzwerkverbindungen kann es nunmal zu Problemen kommen.
Hm, wie soll ich dann bei -1 vorgehn? Bzw. was passiert da genau? Kommt dann im nächsten Block die richtigen Daten rüber? Oder bedeutet das Totalverlust? Bzw. wie muß ich da reagieren?
--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-18, 12:40 h

Holger
Posts: 8116
User
Zitat:
Original von Ralf27:
Mal kurz ne Frage zwischen rein. Wo issen da der unterschied zwischen Binär und Text in einem String? Es dürfte doch eigentlich keinen geben. Jedenfalls ist mir da jetzt keiner ersichtlich.
Ein String ist ja eigentlich nur ein Speicherbereich, auf dem die Variable hinweist.

Du benutzt einen ganz bestimmten Basic-Dialekt und schreibst code, der nur mit genau diesem bestimmten Basic-Dialekt funktioniert. Ob Strings z.B. mit einem 0-byte abgeschlossen werden oder die Länge in einem zusätzlichen Speicher abgelegt wird, ob die einzelnen Zeichen ascii oder z.B. unicode Zeichen sind, ob überhaupt alle Zeichen erlaubt sind oder bestimmte Zeichen beim Anwenden von String-Operationen gefiltert werden oder z.B. Zeilenumbrüche konvertiert werden, hängt immer von der ganz konkreten Sprache und -implementation ab.

Weißt Du z.B., ob LEFT$ eine Kopie der Zeichendaten anlegt oder einfach nur auf den gleichen Speicherbereich mit anderer Längenangabe verweist? Und ob z.B. der String-Vergleich a$=b$ nicht sofort positiv zurückgibt, wenn die Adresse und Länge übereinstimmen, bzw. immer negativ zurückgibt, wenn die Adressen nicht übereinstimmen, weil die interne Optimierung immer gleiche Strings auf die gleiche Adresse legt?

Der Unterschied ist auch der, dass man auch nach einem Jahr beim ersten Blick auf den code erkennt, dass es hier nicht um Textmanipulation, sondern um das Kopieren von (Binär-)Daten geht.
Zitat:
Hm, wie soll ich dann bei -1 vorgehn? Bzw. was passiert da genau? Kommt dann im nächsten Block die richtigen Daten rüber? Oder bedeutet das Totalverlust? Bzw. wie muß ich da reagieren?
Danach kommen normalerweise keine Daten mehr. Die übliche Vorgehensweise ist Datei schliessen und eine Fehlermeldung ausgeben, wie Du auch in meinem Beispielcode sehen kannst. PrintFault macht das für Dich. Das Textargument sollte eine Beschreibung der Aktion sein, die man durchführen wollte, die Art des Fehler wird von der Funktion (ab OS2.1 auch übersetzt) angehängt.

Du kannst auch alle Daten, die Du gelesen hast, bevor Read() -1 zurückgegeben hat, behalten und zu einem späteren Zeitpunkt versuchen, mit einer neuen Verbindung den Rest zu lesen, http unterstützt auch das Fortsetzen, leider unterstützt das nicht jeder server. Wenn man Pech hat, muss man also alles von vorne lesen.

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-18, 12:45 h

Holger
Posts: 8116
User
Zitat:
Original von Ralf27:
Aber, bevor ich das ganze Teste mal eine Frage:
Ist der Header dann genau so lang um die 301 zu übertragen?

Was meinst Du mit "genau so lang"? Der Header ist so lang, wie der Header halt sein muss. Wenn Code 301 zurückgegeben wird, wird nach dem Header keine Datei übertragen, das ist alles.

Deine Vorgehensweise, die Datei ohne Bedingung anzufordern und nur den Header auszuwerten, bedeutet, dass die Datei trotzdem übertragen wird, so weit die Pufferspeicher es erlauben. Das kann nicht nur Resourcen, sondern u.U. auch richtiges Geld kosten...

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-19, 00:02 h

Ralf27
Posts: 2779
User
Zitat:
Original von Holger:
Übrigens, wenn es Dir darum geht, eine Update-Funktion zu implementieren, solltest Du den Http-Header um "If-Modified-Since" erweitern. Auf diese Weise sparst Du Dir die Dateiübertragung, wenn kein Update vorliegt. Der Header könnte z.B. so aussehen:
code:
GET http://home.pages.at/a1260/EigenePage/Dateien/Sudoku.lha HTTP/1.0
If-Modified-Since: Thu, 17 Aug 2006 17:43:34 CEST



Ich hab es eben versucht und zuerst lief es nicht(400). Aber da es vermutlich in die zweite Zeile geschrieben werden muß, hab ich noch ein ASCII 13 und 10 dazwischen geschoben und jetzt gehts. :D
Zja, hab eben Erfolgreich ne 304 bekommen. :-)


--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-19, 00:35 h

Ralf27
Posts: 2779
User
Zitat:
Original von Holger:
Du benutzt einen ganz bestimmten Basic-Dialekt und schreibst code, der nur mit genau diesem bestimmten Basic-Dialekt funktioniert. Ob Strings z.B. mit einem 0-byte abgeschlossen werden oder die Länge in einem zusätzlichen Speicher abgelegt wird, ob die einzelnen Zeichen ascii oder z.B. unicode Zeichen sind, ob überhaupt alle Zeichen erlaubt sind oder bestimmte Zeichen beim Anwenden von String-Operationen gefiltert werden oder z.B. Zeilenumbrüche konvertiert werden, hängt immer von der ganz konkreten Sprache und -implementation ab.

Das mag schon sein, aber z.b. läuft es so bei QBasic, AmigaBasic, MaxonBasic und noch bestimmt vielen anderen Basicdialekten, die auf dem ähnlichen Syntax aufbauen.
Z.b. BlitzBasic ist da ja wieder ganz was anderes. Dies hat ja mit dem eigentlich Basic wirklich gar nichts gemeinsam, außer dem Namen.

In denn mir bekannten Basicdialekten(außer BB, was ja in der Hinsicht kein Basic ist), deren Sprache fast gleich ist, kann man alle Zeichen benutzen.
Nun, fast schlimmer noch, in den ganzen Handbüchern zu Basic wird z.b. der String gerne als Speicher aller Art benutzt. Ok, ist eine Unsitte die man nicht aneignen sollte, aber es funktioniert.
Zitat:
Weißt Du z.B., ob LEFT$ eine Kopie der Zeichendaten anlegt oder einfach nur auf den gleichen Speicherbereich mit anderer Längenangabe verweist? Und ob z.B. der String-Vergleich a$=b$ nicht sofort positiv zurückgibt, wenn die Adresse und Länge übereinstimmen, bzw. immer negativ zurückgibt, wenn die Adressen nicht übereinstimmen, weil die interne Optimierung immer gleiche Strings auf die gleiche Adresse legt?
Bei LEFT$ kann es mir ja egal sein ob er nur die Zeichenkette stutzt oder die Längenangabe ändert.
Beim Stringvergleich ist es sogar im Handbuch beschrieben was passiert. Allerdings muß man nicht nachsehn um zu wissen das er einen String mit dem anderen vergleicht und wenn die beiden nicht genau gleich sind (Inhalt und Länge, bzw. gehört das ja zusammen. Mann fragt sich ja auch nicht ob a<>b bei Zahlen die Zahlen und/oder die Länge der Zahlen kontrolliert.
Und wenn er da auf Adresse kontrollieren würde, dann wäre ja wohl einiges seltsam, bzw. (...)

Um es kurz zu fassen:
Mir ist jetzt klar auf was du hinaus willst.
Zitat:
Der Unterschied ist auch der, dass man auch nach einem Jahr beim ersten Blick auf den code erkennt, dass es hier nicht um Textmanipulation, sondern um das Kopieren von (Binär-)Daten geht.
Bei Basic ist es egal was im String ist. Ok, wenn man dann den String mit PRINT ausgibt und es sind Steuerzeichen drin, dann arbeitet PRINT nach denn Steuerzeichen, der String bleibt aber der gleiche.
Zitat:
Du kannst auch alle Daten, die Du gelesen hast, bevor Read() -1 zurückgegeben hat, behalten und zu einem späteren Zeitpunkt versuchen, mit einer neuen Verbindung den Rest zu lesen, http unterstützt auch das Fortsetzen, leider unterstützt das nicht jeder server. Wenn man Pech hat, muss man also alles von vorne lesen.
Hui, ich muß schon tippen das man so eine Updatefunktion schon recht umfangreich machen kann/soll/könnte. Bzw. man hat schon recht viele Möglichkeiten und Sachen die man beachten sollte.
--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-20, 23:41 h

Ralf27
Posts: 2779
User
Ich bin fast wunschlos Gllücklich mit dem TCP:, bis auf eine Kleinigkeit, die ich zwar auch lösen könnte, aber vermutlich nicht richtig elegant:

Problem: Info ob Bytes da sind.

Bekannt/Problem: Read() wartet so lange bis Bytes da sind

Bei denn Basicbefehlen selbst gibt es die Möglichkeit auf Kontrolle ob Bytes da sind, aber leider möchte sich Basic selbst sich nicht richtig auf TCP: einlassen (entweder nur senden oder nur empfangen, kein Mischbetrieb möglich-> unbrauchbar)

Notlösung wäre 1sec PingPong, was aber wirklich nicht elegant ist.
wohl besser:
Ein zweiter Task der wartet und wenn er was bekommt halt ans Hauptprogramm übergibt. Hab leider kaum (bzw. keine)Erfahrungen damit. Bzw. würde ich zur Zeit einfach direkt vom Hauptprogramm ein externes Programm von der Platte aus starten, das halt in einem weiteren Task läuft.

Hinweis: Mir ist klar das ich da vermutlich Käse mache, bzw. das ich da zuviel von TCP: verlange.
Das was ich will geht, aber nur PingPong gefällt mir nicht so.

Danke im vorraus. :)
--
http://www.alternativercomputerclub.de.vu

[ Dieser Beitrag wurde von Ralf27 am 20.08.2006 um 23:45 Uhr geändert. ]

[ - Answer - Quote - Direct link - ]

2006-08-21, 15:12 h

thomas
Posts: 7718
User

Wenn du möchtest, kannst du dich ja mal an asynchronem I/O probieren.

http://www.amiga.org/modules/newbb/viewtopic.php?topic_id=29997&forum=2#forumpost363855

Ich geh' schonmal in Deckung...

Gruß Thomas

--
Email: thomas-rapp@web.de
Home: thomas-rapp.homepage.t-online.de/

[ - Answer - Quote - Direct link - ]

2006-08-21, 23:31 h

Ralf27
Posts: 2779
User
Zitat:
Original von thomas:

Wenn du möchtest, kannst du dich ja mal an asynchronem I/O probieren.

http://www.amiga.org/modules/newbb/viewtopic.php?topic_id=29997&forum=2#forumpost363855

Danke, das hab ich mir mal angesehn.
Zitat:
Ich geh' schonmal in Deckung...
Gute Entscheidung, denn leider hängt es noch irgendwo (oder überall... :nuke: )

code:
FUNCTION xWaitDosIO(port&)
  x=WaitPort(port&)
  msg&=GetMsg&(port&)
  packet&=PEEKL(PEEKL(msg&+mn_Node%)+ln_Name%)
  xWaitDosIO=PEEKL(packet&+dp_Res1%)
  FreeDosObject DOS_STDPKT&,packet&
 END FUNCTION

 size&=100
 buffer&=SADD(SPACE$(size&))

 in_port&=CreateMsgPort&
 IF in_port& THEN
  out_port&=CreateMsgPort&
  IF out_port& THEN
   in_file&=xOpen&(SADD("ram:test"+CHR$(0)),MODE_OLDFILE&)
   IF in_file& THEN
    out_file&=xOpen&(SADD("ram:test7"+CHR$(0)),MODE_NEWFILE&)
    IF out_file& THEN
     PRINT"Kopiere..."
     bytes_read=xRead(in_file&,buffer&,size&)
     WHILE bytes_read>0
      StartWrite out_file&,buffer&,bytes_read,out_port&
REM Kein plan was die folgende Zeile macht:
REM in C: buffer = (buffer == buffer1 ? buffer2 : buffer1)
REM siehe Text weiter unten
      StartRead in_file&,buffer&,size&,in_port&
      WaitDosIO out_port&
      bytes_read=xWaitDosIO(in_port&)
     WEND
     PRINT"fertig!"
     x=xClose(out_file&)
    ELSE
     PRINT"Kann Ausgabedatei nicht anlegen"
    END IF
    x=xClose(in_file&)
   ELSE
    PRINT"Kann Eingabedatei nicht öffnen"
   END IF
   DeleteMsgPort out_port&
  END IF
  DeleteMsgPort in_port&
 END IF
   
SUB StartRead(handler&,buffer&,size&,port&)
 IF handler&<>0 AND PEEKL(handler&+fh_Type%)<>0 THEN
  packet&=AllocDosObject(DOS_STDPKT&,TAG_END&)
  IF packet& THEN
   POKEL packet&+dp_Port%,port&
   POKEL packet&+dp_Type%,ACTION_READ&
   POKEL packet&+dp_Arg1%,PEEKL(handler&+fh_args%)
   POKEL packet&+dp_Arg2%,buffer&
   POKEL packet&+dp_Arg3%,size&
   PutMsg PEEKL(handler&+fh_Type%),PEEKL(packet&+dp_Link%)
  END IF
 END IF
END SUB
SUB StartWrite(handler&,buffer&,size&,port&)
 IF handler&<>0 AND PEEKL(handler&+fh_Type%)<>0 THEN
  packet&=AllocDosObject(DOS_STDPKT&,TAG_END&)
  IF packet& THEN
   POKEL packet&+dp_Port%,port&
   POKEL packet&+dp_Type%,ACTION_WRITE&
   POKEL packet&+dp_Arg1%,PEEKL(handler&+fh_args%)
   POKEL packet&+dp_Arg2%,buffer&
   POKEL packet&+dp_Arg3%,size&
   PutMsg PEEKL(handler&+fh_Type%),PEEKL(packet&+dp_Link%)
  END IF
 END IF
END SUB
SUB WaitDosIO(port&)
 x=WaitPort(port&)
 msg&=GetMsg&(port&)
 packet&=PEEKL(PEEKL(msg&+mn_Node%)+ln_Name%)
REM rc=PEEKL(packet&+dp_Res1%)
 FreeDosObject DOS_STDPKT&,packet&
END SUB


So, jetzt geh ich erst mal in Deckung. 8o :lach:

EDIT: Kein plan wie die Sache mit dem Buffer läuft, aber ich wäre schon froh wenn das ganze nicht einfrieren würde, bzw. richtig laufen würde...
--
http://www.alternativercomputerclub.de.vu

[ Dieser Beitrag wurde von Ralf27 am 21.08.2006 um 23:51 Uhr geändert. ]

[ - Answer - Quote - Direct link - ]

2006-08-22, 01:34 h

Holger
Posts: 8116
User
@Ralf27:
Du hast eine Function namens xWaitDosIO UND eine Function namens WaitDosIO (ohne x)? Hmm.
Außerdem gehört buffer&=SADD(SPACE$(size&)) zu den logischen Zeitbomben, wegen denen ich vor diesen String-Missbrauch warne. Der angelegte String ist in keiner einzigen Variablen gespeichert. Ich würde vom Basic Laufzeitsystem erwarten, dass solche Strings automatisch freigegeben werden. Wie lange sollte sonst die Lebenszeit des Strings sein, bis keine Zahlenvariable zufällig mit einer Adresse, die innerhalb des Strings liegt, übereinstimmt?

@thomas:
Dein Beispiel erscheint mir etwas kompliziert. Im Prinzip braucht man keine verschiedenen Reply-Ports für Lese- und Schreiboperationen. Und man kann auch den pr_MsgPort benutzen, wenn man währenddessen keine anderen DOS-Operationen durchführt...

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-22, 09:34 h

thomas
Posts: 7718
User

Zitat:
REM Kein plan was die folgende Zeile macht:
REM in C: buffer = (buffer == buffer1 ? buffer2 : buffer1)



I Basic würde man das so schreiben:

code:
if buffer = buffer1 then
   buffer = buffer2
else
   buffer = buffer1
endif


Das dient dem Doublebuffering, du hast zwei Puffer, die abwechselnd gelesen und geschrieben werden.

In deinem Code liest und schreibst du gleichzeitig den gleichen Puffer, das kann nur schief gehen. Dürfte aber nicht zum Hänger führen.


@Holger:

Ja, das Hauptprogramm ist nicht so ganz ernst gemeint, da habe ich nicht lange drüber nachgedacht. Es ging nur um die Funktionen Start... und Wait...

Und klar kann man den eigenen pr_MsgPort nehmen, aber gerade bei asynchronem I/O möchte man ja meistens etwas anderes nebenher machen, sonst bräuchte man es nicht asynchron machen.

Gruß Thomas

--
Email: thomas-rapp@web.de
Home: thomas-rapp.homepage.t-online.de/

[ - Answer - Quote - Direct link - ]

2006-08-22, 09:51 h

Ralf27
Posts: 2779
User
Zitat:
Original von Holger:
Du hast eine Function namens xWaitDosIO UND eine Function namens WaitDosIO (ohne x)? Hmm.

In C kann man wohl Unterprogramme bei dennen man wahlweise einen Wert zurückgeben kann oder nicht. In Basic kann eine SUB so keinen Wert zurück geben, außer ich häng noch eine Variable an als Rückgabewert.
Deswegen einmal ein SUB und einmal eine FUNCTION. Ich sollte das aber einfach auch mal umschreiben und eine SUB machen mit einer weiteren, angehängten Variable die eine Rückgabe ermöglicht.
Zitat:
Außerdem gehört buffer&=SADD(SPACE$(size&)) zu den logischen Zeitbomben, wegen denen ich vor diesen String-Missbrauch warne. Der angelegte String ist in keiner einzigen Variablen gespeichert. Ich würde vom Basic Laufzeitsystem erwarten, dass solche Strings automatisch freigegeben werden. Wie lange sollte sonst die Lebenszeit des Strings sein, bis keine Zahlenvariable zufällig mit einer Adresse, die innerhalb des Strings liegt, übereinstimmt?

Es ist gut beschrieben wie der Compiler mit Strings umgeht. Der Amigabasic-Interpreter läuft da auch so. Bzw. war es früher eine "unart" es genau so zu machen wie ich das da oben mach. Mir ist klar das es eine Unart ist, aber diese Vorgehensweise ist bei den beiden gennannten Dialekten "legal". Aber das frist dann Stack, was mir auch klar ist. AllocMem ist da eleganter...
--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-22, 09:54 h

Ralf27
Posts: 2779
User
Zitat:
Original von thomas:
Das dient dem Doublebuffering, du hast zwei Puffer, die abwechselnd gelesen und geschrieben werden.

Ah, danke.
Zitat:
In deinem Code liest und schreibst du gleichzeitig den gleichen Puffer, das kann nur schief gehen. Dürfte aber nicht zum Hänger führen.
Ich vermute es liegt wieder an diesen POKELn, denn mir sind die längen nicht bekannt ob es nun Bytes, Words oder auch wirklich Longs sind. Leider konnte ich da nur annahmen(ok, böses Wort beim programmieren I-) ) treffen, wenn z.b. hinter der Adresse 4 Bytes frei sind, dann vermutlich Long...


--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-22, 14:11 h

Holger
Posts: 8116
User
Zitat:
Original von Ralf27:
Es ist gut beschrieben wie der Compiler mit Strings umgeht. Der Amigabasic-Interpreter läuft da auch so. Bzw. war es früher eine "unart" es genau so zu machen wie ich das da oben mach. Mir ist klar das es eine Unart ist, aber diese Vorgehensweise ist bei den beiden gennannten Dialekten "legal". Aber das frist dann Stack, was mir auch klar ist. AllocMem ist da eleganter...


Ich glaube Dir ja, dass es gut beschrieben ist. Du sagst aber nicht, wie es nun funktionieren soll. Dass Strings auf den Stack gelegt werden, kann ich Dir so nicht glauben. Oder aber Du hast bei der Beschreibung etwas nicht richtig verstanden. Auf den Stack werden temporäre Werte gelegt. Die werden normalerweise direkt nach Abschluss der Operation freigegeben, d.h. wenn Du den Rückgabewert von SADD() an buffer& überweist, ist der temporäre String, dessen Adresse Du gerade ermittelt hast, schon Geschichte.

So funktioniert ein Stack. Daten auf den Heap können eine längere Lebenszeit haben, aber dass Basic unbenutzte String nie freigibt, ist genauso unwahrscheinlich. Also versuche, die Frage zu beantworten:
Wann wird dieser String, der in keiner Variablen gespeichert ist, freigegeben?

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-22, 14:21 h

Holger
Posts: 8116
User
Zitat:
Original von Ralf27:
Ich vermute es liegt wieder an diesen POKELn, denn mir sind die längen nicht bekannt ob es nun Bytes, Words oder auch wirklich Longs sind.

Hast Du schon einmal mit dem Gedanken gespielt, in eine Dokumentation zu schauen, in der die Datentypen der Struktureinträge enthalten sind?

An den POKEL liegt's übrigens nicht, Du hast nur das Makro BADDR() übersehen, das nicht ganz unwichtig in diesem code ist.

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-22, 14:25 h

Holger
Posts: 8116
User
Zitat:
Original von thomas:
Und klar kann man den eigenen pr_MsgPort nehmen, aber gerade bei asynchronem I/O möchte man ja meistens etwas anderes nebenher machen, sonst bräuchte man es nicht asynchron machen.


Ach, man kann es schon zur Beschleunigung des Kopiervorgangs benutzen (wenn Quelle und Ziel auf verschiedenen Datenträgern liegen), ohne andere Aktionen durchzuführen. Ansonsten würde ich das Warten auf andere Signale und Ports vermuten. Die durch asynchonrone I/O gewonnene Zeit mit synchroner I/O zu verbraten, dürfte eher die Ausnahme sein.

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-22, 14:52 h

Ralf27
Posts: 2779
User
Zitat:
Original von Holger:
Ich glaube Dir ja, dass es gut beschrieben ist. Du sagst aber nicht, wie es nun funktionieren soll. Dass Strings auf den Stack gelegt werden, kann ich Dir so nicht glauben. Oder aber Du hast bei der Beschreibung etwas nicht richtig verstanden. Auf den Stack werden temporäre Werte gelegt. Die werden normalerweise direkt nach Abschluss der Operation freigegeben, d.h. wenn Du den Rückgabewert von SADD() an buffer& überweist, ist der temporäre String, dessen Adresse Du gerade ermittelt hast, schon Geschichte.

So funktioniert ein Stack. Daten auf den Heap können eine längere Lebenszeit haben, aber dass Basic unbenutzte String nie freigibt, ist genauso unwahrscheinlich. Also versuche, die Frage zu beantworten:
Wann wird dieser String, der in keiner Variablen gespeichert ist, freigegeben?


Also, ein String wird erst freigegeben wenn ich es im Programm angeben (z.b. a$="") und bei jeder Zuweisung *kann* sich auch die Adresse im Speicher ändern. Ich nehme aber immer lieber an, das die Adresse dann immer anderst ist, bzw. hole sie mir lieber nochmal neu.
Wenn ich aber nix dieser entsprechenden Variable zuweise, dann ändert sich auch nicht die Adresse.

Darauf wird im Handbuch explizit hingewiesen.

Aber ok, ich sollte eigentlich für sowas lieber AllocMem nehmen.

Wegen Stack/Heap:
Sorry, aber vermutlich würfel ich da einiges durcheinander in Sachen Bezeichnungen. Ich muß nochmal im Handbuch nachsehn wo was genau gespeichert wird. Zur Programmierung ist mir aber zuerst das verhalten der Strings wichtiger.
--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-22, 14:54 h

Ralf27
Posts: 2779
User
Zitat:
Original von Holger:
Hast Du schon einmal mit dem Gedanken gespielt, in eine Dokumentation zu schauen, in der die Datentypen der Struktureinträge enthalten sind?

Das ist leider das Manko bei denn Basic-Includes: Da steht das leider nicht drin. Bzw. müßte ich mir dann die C-Includes vornehmen, aber das könnte/sollte ich da wirklich auch machen.
Zitat:
An den POKEL liegt's übrigens nicht, Du hast nur das Makro BADDR() übersehen, das nicht ganz unwichtig in diesem code ist.

Danke, aber.... was macht dieses Makro? Wie kann ich das einbauen?
--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-22, 14:58 h

Holger
Posts: 8116
User
Zitat:
Original von Ralf27:
Also, ein String wird erst freigegeben wenn ich es im Programm angeben (z.b. a$="") und bei jeder Zuweisung *kann* sich auch die Adresse im Speicher ändern.


Ja, perfekt, wenn Du einen String in a$ gespeichert hast. Wir reden hier aber von buffer&=SADD(SPACE$(size&)), einem String, der in keiner Variablen gespeichert ist. Du kannst ihn nicht durch eine erneute Zuweisung freigeben, weil es gar keine zugehörige Variable gibt. Dieser String ist bereits freigegeben, oder aber Du hast ein Speicherloch.

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-22, 15:00 h

whose
Posts: 2156
User
Zitat:
Original von Holger:
Zitat:
Original von Ralf27:
Es ist gut beschrieben wie der Compiler mit Strings umgeht. Der Amigabasic-Interpreter läuft da auch so. Bzw. war es früher eine "unart" es genau so zu machen wie ich das da oben mach. Mir ist klar das es eine Unart ist, aber diese Vorgehensweise ist bei den beiden gennannten Dialekten "legal". Aber das frist dann Stack, was mir auch klar ist. AllocMem ist da eleganter...


Ich glaube Dir ja, dass es gut beschrieben ist. Du sagst aber nicht, wie es nun funktionieren soll. Dass Strings auf den Stack gelegt werden, kann ich Dir so nicht glauben. Oder aber Du hast bei der Beschreibung etwas nicht richtig verstanden. Auf den Stack werden temporäre Werte gelegt. Die werden normalerweise direkt nach Abschluss der Operation freigegeben, d.h. wenn Du den Rückgabewert von SADD() an buffer& überweist, ist der temporäre String, dessen Adresse Du gerade ermittelt hast, schon Geschichte.

So funktioniert ein Stack. Daten auf den Heap können eine längere Lebenszeit haben, aber dass Basic unbenutzte String nie freigibt, ist genauso unwahrscheinlich. Also versuche, die Frage zu beantworten:
Wann wird dieser String, der in keiner Variablen gespeichert ist, freigegeben?


Soweit ich mir das noch in Erinnerung rufen kann, dürfte dieses Konstrukt platzen. Der temporäre String, den Ralf da verwendet, müßte im Heap landen (die meisten BASICs legen Strings nie auf dem Stack ab, Ausnahme BB) und direkt nach Ermittlung der Adresse wieder dem freien Bereich des Heaps zugeordnet werden. Das das ne Weile funktioniert, liegt schlicht daran, daß in BASIC eher selten temporär Speicher benötigt wird (normal landen Strings/Variablen in BASIC auch nicht im Heap). Irgendwann knallt das aber. Daher hat Ralf mit "Unsitte" gar nicht so Unrecht. Es ist eher Zufall, daß das funktioniert :D

Grüße

--
---

:boing: µA1 PPC 750GX-800
:boing: A4000 PPC 604e-233

[ - Answer - Quote - Direct link - ]

2006-08-22, 15:01 h

Ralf27
Posts: 2779
User
[quote]
Original von Holger:
Zitat:
Ja, perfekt, wenn Du einen String in a$ gespeichert hast. Wir reden hier aber von buffer&=SADD(SPACE$(size&)), einem String, der in keiner Variablen gespeichert ist. Du kannst ihn nicht durch eine erneute Zuweisung freigeben, weil es gar keine zugehörige Variable gibt. Dieser String ist bereits freigegeben, oder aber Du hast ein Speicherloch.

Hm, sorry, aber erst jetzt versteh ich das Problem. Stimmt, das kann ich nicht zweifelsfrei belegen. -> merken und umbauen. Danke.

Ich werd da bald mal ein testprogramm schreiben und mal sehn was da abgeht. Also die zeile wie sie da oben steht und dann wild variablen anmelden, etc. und dann mal nachsehn was da noch im buffer& steht.

--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]

2006-08-22, 15:03 h

Holger
Posts: 8116
User
Zitat:
Original von Ralf27:
Bzw. müßte ich mir dann die C-Includes vornehmen, aber das könnte/sollte ich da wirklich auch machen.

Ja.
Zitat:
Danke, aber.... was macht dieses Makro? Wie kann ich das einbauen?

Es multipliziert die Adresse mit vier, bzw. verschiebt um zwei Bits nach links.
Basic code:
FUNCTION BADDR&(bptr&)
BADDR&=bptr&<<2
END FUNCTION


mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-22, 15:15 h

whose
Posts: 2156
User
Zitat:
Original von Ralf27:
Zitat:
Original von Holger:
Ja, perfekt, wenn Du einen String in a$ gespeichert hast. Wir reden hier aber von buffer&=SADD(SPACE$(size&)), einem String, der in keiner Variablen gespeichert ist. Du kannst ihn nicht durch eine erneute Zuweisung freigeben, weil es gar keine zugehörige Variable gibt. Dieser String ist bereits freigegeben, oder aber Du hast ein Speicherloch.


Hm, sorry, aber erst jetzt versteh ich das Problem. Stimmt, das kann ich nicht zweifelsfrei belegen. -> merken und umbauen. Danke.

Ich werd da bald mal ein testprogramm schreiben und mal sehn was da abgeht. Also die zeile wie sie da oben steht und dann wild variablen anmelden, etc. und dann mal nachsehn was da noch im buffer& steht.


Ich denke, mit dem Test wirst Du nicht viel Erfolg haben. Die meisten BASIC-Dialekte trennen Variablenspeicher und Heap (also den Speicher für temporäre Variablen und anderes "unwichtiges" Zeugs wie Stringkonstanten, die existierenden Strings zugewiesen werden. BASIC-Compiler arbeiten meist aber gar nicht mit einem Heap im klassischen Sinn sondern nur mit Variablenbereich und Stack).

Erzeuge mal nen Haufen Strings, denen laufend andere Stringkonstanten "zugewiesen" werden (BASIC kopiert Strings meist), damit dürfte eher sichtbar werden, was mit Deinem temporären String passiert. Sollte Dein temporärer String weiter intakt bleiben, darf man davon ausgehen, daß MB für diesen Zweck einen "unsichtbaren" String im Variablenbereich anlegt, der bis zum Ende des Programms Gültigkeit besitzt (und dessen Speicher somit nicht ausdrücklich vom Programm selbst freigegeben werden kann -> dumme Sache). GFA hat z.B. auch so gearbeitet.

In AmigaBASIC ist so ein Ding (temporärer String ohne Bezeichner) meist geplatzt.

Grüße

--
---

:boing: µA1 PPC 750GX-800
:boing: A4000 PPC 604e-233


[ Dieser Beitrag wurde von whose am 22.08.2006 um 15:17 Uhr geändert. ]

[ - Answer - Quote - Direct link - ]

2006-08-22, 15:17 h

Holger
Posts: 8116
User
Zitat:
Original von Ralf27:
Ich werd da bald mal ein testprogramm schreiben und mal sehn was da abgeht. Also die zeile wie sie da oben steht und dann wild variablen anmelden, etc. und dann mal nachsehn was da noch im buffer& steht.


Das ist auch ziemliche Glückssache. Du kannst auch mittels AllocAbs&(buffer&, size&) nachschauen, ob der Speicher frei ist. Wenn die Anforderung klappt, weisst Du, dass der Speicher freigegeben wurde. Wenn nicht, kann es immer noch sein, dass a) Basic memory-pools benutzt oder b) inzwischen ein anderes Programm sich den Bereich gekrallt hat. Genausogut kannst Du auch prüfen, ob
buffer1&=SADD(SPACE$(size&))
buffer2&=SADD(SPACE$(size&))

beide Male die gleiche Adresse zurückgibt. Aber auch hier gilt: wenn beide gleich sind, weißt Du, das der Bereich recycle't wurde, wenn ungleich, hat das gar nichts zu sagen...
Wenn Du einfach
buffer_str$=SPACE$(size&)
buffer&=SADD(buffer_str$)

schreibst, hast Du den Speicher sicher reserviert. Nur das generelle Problem dieser Vorgehensweise bleibt bestehen, an anderer Stelle könnte man versehentlich der Variable buffer_str$ einen anderen Wert zuweisen und damit den Buffer ungültig machen. Aber gibt es nicht auch so etwas wie CONST in MBasic?

Zitat:
Ich nehme aber immer lieber an, das die Adresse dann immer anderst ist, bzw. hole sie mir lieber nochmal neu.
Das ist zwar an sich vernünftig, aber klar ist auch, dass das Dateisystem, dem Du eine asynchrone Anweisung geschickt hast, davon ausgeht, dass der Puffer bis zur Beantwortung des Request gültig bleibt.

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Answer - Quote - Direct link - ]

2006-08-22, 15:41 h

Ralf27
Posts: 2779
User
@Holger:

z.b.
CONST a=10

Die Sache mit dem Speicher kann man entweder statisch oder dynamisch einstellen. Z.b. als Compileroption HEAPDYNAMIC.

Hab mal im Handbuch nachgesehn da da gibt es einige getrennte Speicherbereich, mit dennen MB arbeitet. Z.b. MathStack, Labeltabelle, Heap. Man kann da mindestgröße vom Stack oder Heap angeben, oder auch von Statisch auf Dynamisch wechseln.

Ansich gibt es da wirklich sehr viele Optionen, von dennen ich nur ein paar verwende. Z.b. sind auch vortokensierte Dateien möglich. Noch nie benutzt...
--
http://www.alternativercomputerclub.de.vu

[ - Answer - Quote - Direct link - ]


1 2 -3- 4 [ - Post reply - ]


amiga-news.de Forum > Programmierung > Progamm<->Internet<->Programm [ - Search - New posts - Register - Login - ]


.
Masthead | Privacy policy | Netiquette | Advertising | Contact
Copyright © 1998-2024 by amiga-news.de - all rights reserved.
.