AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Einbinden eines Thread Downloads

Ein Thema von Stewag · begonnen am 16. Jul 2024 · letzter Beitrag vom 22. Jul 2024
Antwort Antwort
Seite 1 von 2  1 2      
Stewag

Registriert seit: 12. Jun 2008
175 Beiträge
 
Delphi 12 Athens
 
#1

Einbinden eines Thread Downloads

  Alt 16. Jul 2024, 22:51
In einem mobilen Projekt für Android und iOS verwende ich bisher zum Download von Dateien in einen Stream System.Net.HttpClient und verarbeite dann das Ergebnis:

Code:
 mstream := TMemoryStream.Create;
    try
      DownloadFile('https://www.example.de/datei.txt', mstream);
      memWerte.LoadFromStream(mstream, TEncoding.ANSI);
    except
      on E: Exception do
        showmessage(E.Message)
    end;
mstream.Free;
Da Android die damit verbundene Wartezeit für den User nicht schätzt, habe ich eine Unit erstellt, die die Datei in einem synchronen Thread lädt. Das ist wohl das empfohlene Verfahren.
Die Unit funktioniert im Testprojekt auch und synchronisiert den Ergebnisstream in den Hauptthread über DownloadComplete zurück:

Code:
procedure TForm1.DownloadComplete(const stream: TStream; Success: Boolean);
begin
  if Success then
  begin
     memWerte.lines.LoadFromStream(stream)
  end
  else
    Label1.Text := 'Download failed';
end;
Mein Problem nun:
Wie bekomme ich das Ergebnis, das DownloadComplete liefert, am besten unter einen Hut mit dem Hauptstream?
Muss ich dazu einen Timer einrichten, der zyklisch prüft, ob memWerte.lines schon <> '' ist oder gibt es einen eleganteren Weg?

Hier der Aufruf des Thread Downloads:
Code:
procedure TForm1.Button1Click(Sender: TObject);
var
  Downloader: TFileDownloader;
  mstream: TMemoryStream;
begin
   try
     mstream := TMemoryStream.Create;
     Downloader := TFileDownloader.Create('https://www.example/datei.txt', mstream);
     Downloader.OnDownloadComplete := DownloadComplete;
     Downloader.Start;
   finally
      mstream.Free;
   end
end;
Steffen

Geändert von Stewag (16. Jul 2024 um 23:27 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#2

AW: Einbinden eines Thread Downloads

  Alt 16. Jul 2024, 23:35
Ich würde mal sagen, dass es eine saublöde Idee ist, mstream freizugeben, bevor der Thread fertig ist, sogar als er noch nichtmal gestartet wurde.


Ich würde den Stream im Execute des Threads erstellen und dort auch freigeben, nach dem OnDownloadComplete (egal ob mit oder ohne Synchronize)
$2B or not $2B

Geändert von himitsu (16. Jul 2024 um 23:48 Uhr)
  Mit Zitat antworten Zitat
Stewag

Registriert seit: 12. Jun 2008
175 Beiträge
 
Delphi 12 Athens
 
#3

AW: Einbinden eines Thread Downloads

  Alt 17. Jul 2024, 10:32
Himitsu hat natürlich Recht!
Das .Free habe ich erst spontan, nachträglich, im Forum nachgesetzt, weil mir der Befehl zu fehlen schien und ich befürchtete, als Erstes eins deswegen auf den Deckel zu bekommen.

Nun ist es umgekehrt

Hier der korrekte Aufruf:

Code:
procedure TForm1.Button1Click(Sender: TObject);
var
  Downloader: TFileDownloader;
  mstream: TMemoryStream;
begin
  mstream := TMemoryStream.Create;
  Downloader := TFileDownloader.Create('https://www.example/datei.txt', mstream);
  Downloader.OnDownloadComplete := DownloadComplete;
  Downloader.Start;
end;
Meine ursprüngliche Frage ist damit aber nicht beantwortet.

Hier die (lauffähige) Unit:
Code:
unit SynchronousHttpClientStreamDownloader;

interface

uses
  System.Classes, System.SysUtils, System.Net.HttpClient, System.Net.URLClient;

type
  TDownloadCompleteEvent = procedure(const stream: TStream; Success: Boolean) of object;

  TFileDownloader = class(TThread)
  private
    FURL: string;
    FStream: TStream;
    FHttpClient: THTTPClient;
    FOnDownloadComplete: TDownloadCompleteEvent;
    FSuccess: Boolean;
    procedure DownloadFile;
    procedure DoDownloadComplete;
  protected
    procedure Execute; override;
  public
    constructor Create(const URL: String; aStream: TStream);
    destructor Destroy; override;
    property OnDownloadComplete: TDownloadCompleteEvent read FOnDownloadComplete write FOnDownloadComplete;
  end;

implementation

{ TFileDownloader }

constructor TFileDownloader.Create(const URL: String; aStream: TStream);
begin
  inherited Create(True);
  FreeOnTerminate := True;
  FURL := URL;
  FStream := aStream;
  FHttpClient := THTTPClient.Create;
end;

destructor TFileDownloader.Destroy;
begin
  FHttpClient.Free;
  inherited;
end;

procedure TFileDownloader.DownloadFile;
var
  vHTTP: THTTPClient;
begin
  try
    FStream := TMemorystream.Create; // FStream muss an dieser Stelle erzeugt werden, im Execute klappt es nicht. Free muss aber im Execute stehen
    Assert(FStream <> nil);

    vHTTP := THTTPClient.Create;
    vHTTP.CustomHeaders['Pragma'] := 'no-cache';

    try
      vHTTP.Get(FURL, FStream);
      FSuccess := True;
    finally
      vHTTP.Free;
    end;
  except
    on E: Exception do
    begin
      FSuccess := False;
    end;
  end;
end;

procedure TFileDownloader.DoDownloadComplete;
begin
  if Assigned(FOnDownloadComplete) then
  begin
    FOnDownloadComplete(FStream, FSuccess);
    FStream.Free
  end
end;

procedure TFileDownloader.Execute;
begin
  DownloadFile;
  Synchronize(DoDownloadComplete);
end;

end.
Gibt es dazu Anmerkungen?
Steffen

Geändert von Stewag (17. Jul 2024 um 10:35 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von DeddyH
DeddyH

Registriert seit: 17. Sep 2006
Ort: Barchfeld
27.625 Beiträge
 
Delphi 12 Athens
 
#4

AW: Einbinden eines Thread Downloads

  Alt 17. Jul 2024, 10:39
Du gibst den Stream nur frei, wenn dem Ereignis ein Handler zugewiesen wurde?
Detlef
"Ich habe Angst vor dem Tag, an dem die Technologie unsere menschlichen Interaktionen übertrumpft. Die Welt wird eine Generation von Idioten bekommen." (Albert Einstein)
Dieser Tag ist längst gekommen
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#5

AW: Einbinden eines Thread Downloads

  Alt 17. Jul 2024, 11:16
Warum den THTTPClient doppelt erstellen?
Im Excecute ist es OK, das im Create war eh ungenutzt.

Assert(FStream <> nil);
Kann niemals NIL sein, denn wenn "doch", dann wirft das Create bereits eine Exception und es kommt dort nie vorbei.

FOnDownloadComplete erst beim Ausführen zu prüfen ist auch etwas unpraktisch.
Alles war ja sinnlos, wenn diese Methode fehlt, da der Download nur als Stream und von diesem Event verwendet wird.
Ohne das, wurde es vollkommen nutzlos runtergeladen.
Also hier am Besten gleich zu Beginn prüfen und z.B. eine Exception werfen, bereits im Create.

Delphi-Quellcode:
constructor TFileDownloader.Create(const URL: String; OnDownloadComplete: TDownloadCompleteEvent);
begin
  if not Assigned(OnDownloadComplete) then
    raise Exception.Create('peng');
  inherited Create(False); // und jetzt außen kein Start mehr, da es am Ende des Create von selst startet
  FreeOnTerminate := True;
  FURL := URL;
  FOnDownloadComplete := OnDownloadComplete;
  // und der Rest im Execute/DownloadFile
end;
Für die Funktion zwar nicht relevant, aber
TDownloadCompleteEvent = reference to procedure(Stream: TStream; Success: Boolean);
hat den Vorteil, dass man hier nicht nur Methoden und KlassenMethoden übergegen kann,
sondern auch einfache Prozeduren oder anonyme Methoden.

Den Stream intern erstellen, da wo er auch freigegeben wird, nicht extern.
Nja, da FHttpClient, FSuccess und eigentlich auch FStream im Grunde ausschließlich während der Laufzeit des Threads nötig sind und bezüglich einem threadsaven Zugriffs nicht von außen abgreifbar sein sollten, sind sie als lokale Variable im Execute eh besser aufgehoben.
Das Event als Property, auf welches man auch nach dem Start extern zugreifen kann, fliegt auch raus.
Sowas lässt sich wundeschön als Startparameter übergeben, also als Parameter ans Create und das Start aka Suspended=False ebenfalls.
Die restlichen Variablen/Felder müssen nicht unbedingt global sein.

Durch das FreeOnTerminate sind die Thread-Variable und seine Property von Extern sowieso nicht mehr zu verwenden, da sie per se als ungültig zu betrachten sind, denn von extern weißt du nicht, ob der Thread schon fertig ist und freigegeben wurde.

Selbst wenn der Thread blieb, dann dort als ClassProcedure rein,
aber jetzt einfach mal blöd als einface Prozedur.

Delphi-Quellcode:
  TFileDownloader = class(TThread)
  private
    FURL: string;
    FStream: TStream;
    FOnDownloadComplete: TDownloadCompleteEvent;
    procedure DownloadFile;
    procedure DoDownloadComplete;
  protected
    procedure Execute; override;
  public
    class procedure Download(const URL: String; OnDownloadComplete: TDownloadCompleteEvent); static;
  end;
Und weil jetzt vom der Threadableitung im Grunde nichts mehr übrig ist, lass ich des nachfolgend einfach mal weg.
Delphi-Quellcode:
uses
  System.RTLConsts, System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;

type
  TDownloadCompleteEvent = reference to procedure(Stream: TStream; Success: Boolean);

procedure ThreadedDownloadFile(const URL: String; OnDownloadComplete: TDownloadCompleteEvent);
begin
  if (URL = '') or not Assigned(OnDownloadComplete) then
    raise EArgumentException.CreateRes(@SArgumentNil);
  TThread.CreateAnonymousThread(
    procedure
    var
      HTTP: THTTPClient;
      Stream: TMemorystream;
      Success: Boolean;
    begin
      TThread.NameThreadForDebugging('ThreadedDownloadFile');
      HTTP := THTTPClient.Create;
      Stream := TMemoryStream.Create;
      try
        HTTP.CustomHeaders['Pragma'] := 'no-cache';
        try
          HTTP.Get(URL, Stream);
          Success := True;
        except
          Success := False;
        end;
        TThread.Synchronize(nil,
          procedure
          begin
            OnDownloadComplete(Stream, Success);
          end);
      finally
        Stream.Free;
        HTTP.Free;
      end;
    end).Start;
end;



ThreadedDownloadFile('http://sonst.wo/dat.ei', YourDownloadComplete);

// oder
ThreadedDownloadFile('http://sonst.wo/dat.ei',
  procedure(const URL: String; Stream: TStream; Success: Boolean)
  begin
    if Success then
      Machwas(Stream);
  end);
Gut, dass man im OnDownloadComplete zwar mitbekommt, ob es nicht ging, aber nicht warum ... statt eines doofen Success könnte man z.B. die Exception oder ihre Message übergeben.

Für mehrere Downloads das selbe OnDownloadComplete, dann auch die URL mit als Parameter dort rein, damit man dort weiß was es ist.
Delphi-Quellcode:
uses
  System.RTLConsts, System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;

type
  TDownloadCompleteEvent = reference to procedure(const URL: String; Stream: TStream; Error: Exception);

procedure ThreadedDownloadFile(const URL: String; OnDownloadComplete: TDownloadCompleteEvent);
begin
  if (URL = '') or not Assigned(OnDownloadComplete) then
    raise EArgumentException.CreateRes(@SArgumentNil);
  TThread.CreateAnonymousThread(
    procedure
    var
      HTTP: THTTPClient;
      Stream: TMemorystream;
      Success: Boolean;
      DError: Exception;
    begin
      TThread.NameThreadForDebugging('ThreadedDownloadFile');
      DError := nil;
      HTTP := THTTPClient.Create;
      Stream := TMemoryStream.Create;
      try
        HTTP.CustomHeaders['Pragma'] := 'no-cache';
        try
          HTTP.Get(URL, Stream);
          {TThread.Synchronize(nil,
            procedure
            begin
              OnDownloadComplete(URL, Stream, nil);
            end);}

        except
          {on E: Exception do begin
            TempErr := E;  // durch einen Bug muß es kopiert werden, auch wenn der Compiler sich bei OnDownloadComplete(URL, nil, E); nicht beschwert ... E ist im Sync leider NIL
            TThread.Synchronize(nil,
              procedure
              begin
                //OnDownloadComplete(URL, nil, E);  // siehe Bugreport im alten QualityPotal
                OnDownloadComplete(URL, nil, TempErr);
              end);
          end;}

          FreeAndNil(Stream); // für das if-Assigned im OnDownloadComplete ... oder einfach lassen, auch wenn es eh nichts sinnvolles enthält, und nur auf Assigned(Error) prüfen
          DError := AcquireExceptionObject as Exception;
        end;
        TThread.Synchronize(nil,
          procedure
          begin
            OnDownloadComplete(URL, Stream, DError);
          end);
      finally
        DError.Free;
        Stream.Free;
        HTTP.Free;
      end;
    end).Start;
end;



ThreadedDownloadFile('http://sonst.wo/dat.ei', YourDownloadComplete);

// oder
ThreadedDownloadFile('http://sonst.wo/dat.ei',
  procedure(const URL: String; Stream: TStream; Error: Exception)
  begin
    if Assigned(Stream) then begin // if not Assigned(Error) then
      Machwas(Stream);
      ShowMessage('Download complete: ' + URL);
    end;
  end);
$2B or not $2B

Geändert von himitsu (17. Jul 2024 um 11:43 Uhr)
  Mit Zitat antworten Zitat
Stewag

Registriert seit: 12. Jun 2008
175 Beiträge
 
Delphi 12 Athens
 
#6

AW: Einbinden eines Thread Downloads

  Alt 19. Jul 2024, 18:17
Danke für deine Hinweise und Code, Himitsu!

Da ich mit threads aber kaum Erfahrung habe, bekomme ich damit leider keine funktionierende SyncThreadedDownload Unit hin.

Verstehe ich das richtig, dass weder Constructor noch Destructor dafür benötigt werden?

Dies läuft bei mir auch nicht:
Code:
TDownloadCompleteEvent = reference to procedure(Stream: TStream; Success: Boolean);
Vor allem aber verstehe ich nicht, wie/wo deine Prozedur ThreadedDownloadFile einzubinden ist.

Eigentlich sollte doch eine Unit, die eine Datei in einem synchronen Thread lädt, ein übliches Mittel in jedem Werkzeugkasten sein. Deshalb wundert es mich, dass ich auch nach intensivem googeln nichts dergleichen finde.

Könnte hier vielleicht jemand netterweise seine entsprechende Unit (Firemonkey!) zur Verfügung stellen?

Da niemand etwas anderes erwähnt hat, ist es wohl erforderlich, dass nach dem Aufruf des Download Threads ein Timer zyklisch prüft, ob OnDownladComplete schon ein Ergebnis bereit gestellt hat?
Steffen
  Mit Zitat antworten Zitat
Benutzerbild von Olli73
Olli73

Registriert seit: 25. Apr 2008
Ort: Neunkirchen
755 Beiträge
 
#7

AW: Einbinden eines Thread Downloads

  Alt 19. Jul 2024, 18:27
Da niemand etwas anderes erwähnt hat, ist es wohl erforderlich, dass nach dem Aufruf des Download Threads ein Timer zyklisch prüft, ob OnDownladComplete schon ein Ergebnis bereit gestellt hat?
Genau an der Stelle (OnDownloadComplete) weißt du doch, das du fertig bist. Also warum noch ein Timer?
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#8

AW: Einbinden eines Thread Downloads

  Alt 19. Jul 2024, 19:24
Es gibt Fremd-Bibliotheken, die sowas drin haben.
Das Ganze kann man ja noch mit vielen Dingen erweitern und somit nicht allen Recht machen ... den Einen isses zuviel und Anderen fehlt immer irgendwas.
* z.B. ein Pooling, damit man 200 Dateien downloaden kann, aber nicht zu viele Threads gleichzeitig laufen.
* schönere Fehlernehandlung
* Proxy
* uvm.
Je nach Anforderung muß man sich dann was Passendes suchen.


Es gibt verschiedene Arten von Prozeduren/Methoden
und genauso gibt es verschiedene Arten von MethodenZeigern, welche Referenzen auf diese Methoden aufnehmen können.
Delphi-Quellcode:
type
  TEvent1 = reference to procedure(const URL: String; Stream: TStream; Error: Exception); // Proc1, Proc2, Proc3, Proc4 und Inline5
  TEvent2 = procedure(const URL: String; Stream: TStream; Error: Exception) of object; // Proc2, Proc3
  TEvent3 = procedure(const URL: String; Stream: TStream; Error: Exception); // Proc1 (glaub auch Proc4)

// Prozedur
procedure Proc1(const URL: String; Stream: TStream; Error: Exception);

type
  TDemo = class

    // Methode
    procedure Proc2(const URL: String; Stream: TStream; Error: Exception);

    // KlassenMethode
    class procedure Proc3(const URL: String; Stream: TStream; Error: Exception);

    // statische KlassenMethode
    class procedure Proc4(const URL: String; Stream: TStream; Error: Exception); static;

  end;

// anonyme Methode, also "Inline5" im Code
begin
  eineVariable := procedure(const URL: String; Stream: TStream; Error: Exception)
    begin
    end;

  AlsParameter(procedure(const URL: String; Stream: TStream; Error: Exception)
    begin
    end);
end;

// eingebettete Prozedur
procedure {TDemo.}Test;
  procedure Proc6(const URL: String; Stream: TStream; Error: Exception);
  begin
  end;
begin
  Proc6(…);
end;





Freigeben muß man den Thread ja nicht selbst, denn durch das FreeOnTerminate gibt er sich selbst frei.
Weil man extern nicht weiß wann, darf extern grundsätzlich niemals "ungeschützt" auf eine Variable zugegriffen werden, welche auf diesen Thread zeigt.
Extern garkeine Variable, mit dem Objektzeiger, und schon kommt auch niemand auf dumme Ideen.

Ohne FreeOnTerminate könnte man extern z.B. auf Thread.Terminated prüfen
und anschließend selbst den den Thread freigeben, bzw. vorher z.B. die runtergeladenen Daten von ihm abholen.

Man kann selbst TThread ableiten
und seinen Code ins Execute einfügen, sowie eventuell auch ins Create.

Im Prnzip gibt es aber bereits eine fertige Ableitung des TThread,
die eine Prozedur bekommt, welche sie dann im Thread ausführt.
Delphi-Referenz durchsuchenTThread.CreateAnonymousThread

Delphi-Quellcode:
type
  TAnonymousThread = class(TThread)
  private
    FProc: TProc;
  protected
    procedure Execute; override;
  public
    constructor Create(const AProc: TProc);
  end;

class function TThread.CreateAnonymousThread(const ThreadProc: TProc): TThread;
begin
  Result := TAnonymousThread.Create(ThreadProc);
end;

constructor TAnonymousThread.Create(const AProc: TProc);
begin
  inherited Create(True);
  FreeOnTerminate := True;
  FProc := AProc;
end;

procedure TAnonymousThread.Execute;
begin
  FProc();
end;
Also anstatt einer eigenen TThread-Klasse, hatte ich einfach nur dieses Fertige benutzt.
  • Die Aktionen zum Erstellen kann man ja vor oder im Create erledigen.
  • Und das für's Execute wurde hier einfach als Methode/Event reingegeben, anstatt es direkt reinzuschreiben.

Ohne den auskommentierten Code, für eine alternative Behandlung, ist es hier recht kurz gehalten (passt alles komplett auf den Bildschirm),
und durch die anonymen (eingebetteten) Methoden liegt hier der Code funktional übersichtlich in seiner Ablaufreihenfolge.
Aber grundsätzlich ist es auch kein Problem Vieles in externe Methoden auszulagern, aber dann am Besten schön in einer Klasse gekapselt.

Bei dem eingebetteten Synchronize kann man es auch ausnutzen, dass sich Variablen "automatisch" vom Übergeordneten Code dort reingeben lässt.
(Delphi verschiebt diese Variablen "heimlich" in ein Interface, mit Referenzzähung ... bis der aufrufende Code und der Eingebetette alle beendet wurden)
$2B or not $2B

Geändert von himitsu (19. Jul 2024 um 19:40 Uhr)
  Mit Zitat antworten Zitat
Stewag

Registriert seit: 12. Jun 2008
175 Beiträge
 
Delphi 12 Athens
 
#9

AW: Einbinden eines Thread Downloads

  Alt 19. Jul 2024, 22:42
Zitat:
Genau an der Stelle (OnDownloadComplete) weißt du doch, das du fertig bist. Also warum noch ein Timer?
Natürlich, dummer Denkfehler!

Himitsu ist sehr klug und verdient sicher seine Brötchen mit Software.
Ich bin Hobbyprogrammierer, verstehe leider nur Bahnhof und habe kein Problem damit auch Code einzusetzen, den ich nicht bis ins Letzte durchdrungen habe.
Steffen

Geändert von Stewag (19. Jul 2024 um 22:47 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von jaenicke
jaenicke

Registriert seit: 10. Jun 2003
Ort: Berlin
9.648 Beiträge
 
Delphi 11 Alexandria
 
#10

AW: Einbinden eines Thread Downloads

  Alt 20. Jul 2024, 09:32
Da ich mit threads aber kaum Erfahrung habe, bekomme ich damit leider keine funktionierende SyncThreadedDownload Unit hin.
Der Quelltext ist doch komplett fertig inkl. Beispiel zur Nutzung. Da du nicht geschrieben hast, was du damit gemacht hast und welche Fehler dann kamen, kann man dazu auch schlecht etwas sagen.

Wenn du den Code in eine separate Unit packen möchtest, musst du die Funktion natürlich unter interface deklarieren, damit sie aus anderen Units sichtbar ist:
Delphi-Quellcode:
unit SyncThreadedDownload;

interface

uses
  System.RTLConsts, System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;

type
  TDownloadCompleteEvent = reference to procedure(const URL: String; Stream: TStream; Error: Exception);

procedure ThreadedDownloadFile(const URL: String; OnDownloadComplete: TDownloadCompleteEvent);

implementation

procedure ThreadedDownloadFile(const URL: String; OnDownloadComplete: TDownloadCompleteEvent);
begin
...
end;

end.
Dann schreibst du die Unit SyncThreadedDownload dort, wo du sie nutzen möchtest, unter uses. Dann kannst du einfach das Beispiel von himitsu 1:1 verwenden:
Delphi-Quellcode:
ThreadedDownloadFile('http://sonst.wo/dat.ei',
  procedure(const URL: String; Stream: TStream; Error: Exception)
  begin
    if Assigned(Stream) then begin // if not Assigned(Error) then
      Machwas(Stream);
      ShowMessage('Download complete: ' + URL);
    end;
  end);
Statt Machwas(Stream) musst du natürlich den heruntergeladenen Stream mit den Daten verwenden, um die Datei z.B. zu verarbeiten oder zu speichern. Und natürlich musst du auch die korrekte Adresse der Datei eintragen.
Sebastian Jänicke
AppCentral
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:19 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz