Problem bei SubItem-Übergabe beim rekursiven Auflisten von FTP-Verzeichnissen

  • C#
  • .NET (FX) 4.0

Es gibt 19 Antworten in diesem Thema. Der letzte Beitrag () ist von Trade.

    Problem bei SubItem-Übergabe beim rekursiven Auflisten von FTP-Verzeichnissen

    Moin,

    ich versuche aktuell, Ordner und Dateien rekursiv von einem FTP-Server aufzulisten. Dazu benutze ich die Biko Library.
    Die zurückgegebenen Items sind jeweils vom Typ FtpItem, diese haben allerdings keine SubItems-Property, sondern nur einen ParentPath, den ich halt dazu benutzen kann.

    Ich habe mir dann eine Klasse ListingItem erstellt, um dort dann die Items und SubItems rekursiv zu speichern:

    C#-Quellcode

    1. using System.Collections.Generic;
    2. namespace nUpdate.Administration.Core
    3. {
    4. public class ListingItem
    5. {
    6. public ListingItem(string text)
    7. {
    8. Text = text;
    9. }
    10. /// <summary>
    11. /// Gets or sets the text of the current item.
    12. /// </summary>
    13. public string Text { get; set; }
    14. /// <summary>
    15. /// Represents the sub items of the current item.
    16. /// </summary>
    17. public IEnumerable<ListingItem> SubItems { get; set; }
    18. }
    19. }


    Beim Aufruf der Methode, die die Dateien und Verzeichnisse auflistet, bekommt man eine FtpItemCollection zurück, welche bei mir eine Length von 697 hat. Es werden einfach alle Dateien aufgelistet, ohne Subitems oder ähnliches, was das Ganze deutlich einfacher machen würde. Jedes Item hat wie gesagt nur einen ParentPath, über den ich das Ganze machen möchte.
    Dies ist meine Methode, die dann die Subitems initialisieren soll:

    C#-Quellcode

    1. private void InitializeSubItems(ListingItem currentItem, string rootDirectoryName, int pathLength)
    2. {
    3. List<FtpItem> subItems =
    4. _listedFtpItems.Where(
    5. item =>
    6. item.ParentPath.Split('/').Count(x => x != String.Empty) == pathLength &&
    7. item.ParentPath.StartsWith(String.Format("/{0}", rootDirectoryName))).ToList();
    8. currentItem.SubItems = subItems.Select(item => new ListingItem(item.Name));
    9. foreach (var subItem in currentItem.SubItems.Where(subItem => !Path.HasExtension(subItem.Text)))
    10. {
    11. InitializeSubItems(subItem, rootDirectoryName, pathLength + 1);
    12. }
    13. }


    Das ist der Aufruf:

    C#-Quellcode

    1. try
    2. {
    3. _listedFtpItems = _ftp.ListDirectoriesAndFiles("/", true).ToList();
    4. }
    5. catch (Exception ex)
    6. {
    7. Invoke(
    8. new Action(
    9. () =>
    10. {
    11. Popup.ShowPopup(this, SystemIcons.Error, "Error while listing the server data.", ex,
    12. PopupButtons.Ok);
    13. Close();
    14. }));
    15. }
    16. if (_listedFtpItems != null)
    17. {
    18. foreach (var listedItem in _listedFtpItems.Where(item => item.ParentPath.Length < 2))
    19. {
    20. var listingItem = new ListingItem(listedItem.Name);
    21. if (!Path.HasExtension(listedItem.Name)) // Only if it is a directory
    22. InitializeSubItems(listingItem, listedItem.Name, 1);
    23. _foundItems.Add(listingItem);
    24. }
    25. }


    Das macht folgendes, es listet die Dateien und Ordner auf, listet dann diejenigen auf, deren ​ParentPath kleiner 2 ist (also nur "/"), um somit dann die Root-Items aufzulisten und dann wird mit diesem Root-Item, also einem, dass im Root-Directory des Servers liegt, ein neues ​ListingItem erzeugt.
    Mit genau diesem wird dann die Methode aufgerufen, die rekursiv alle SubItems auflisten soll.

    So, soweit geht es eigentlich, nur fügt der Code ab dem Zeitpunkt, wo die PathLength 2 ist, keine SubItems mehr hinzu, diese Property ist dann null.
    Die Ordner im Root haben SubItems, aber halt genau diese haben jeweils keine SubItems mehr. Ich weiß nicht warum. Eigentlich müsste "InitializeSubItems" das doch rekursiv laden, denn ich gebe ja immer die aktuelle ListingItem-Instanz mit.

    Ich habe dann mit Haltepunkten debuggt und festgestellt, dass die Linq-Queries alles richtig machen, beim Debuggen werden auch rekursiv alle SubItems richtig hinzugefügt, nur beim Zuweisen am Ende gibt es anscheinend ein Problem. Theoretisch müsste das aber doch eigentlich immer die selbe Instanz sein, oder?
    Ich gehe stark davon aus, dass das Problem hier liegt, denn beim Debuggen geht es, nur am Ende will er nicht richtig.

    So sollte es z. B. aussehen: ​/test/css/core.css
    Am Ende kommt aber nur das raus: ​/test/css

    Das ist bei jedem Item in ​_foundItems so.
    Es muss also am Zuweisen liegen, denn beim Debuggen wurden bei jedem Aufruf der Methode immer die richtigen SubItems rekursiv hinzugefügt, wie gesagt, die Instanz sollte aber stimmen.

    Sieht jemand den Fehler? Ich bin mit meinem Latein am Ende, da ich das Ganze intensiv planen und in Code umsetzen musste, weil das hier eben schwieriger ist.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    Beim Aufruf der Methode, die die Dateien und Verzeichnisse auflistet, bekommt man eine FtpItemCollection zurück, welche bei mir eine Length von 697 hat. Es werden einfach alle Dateien aufgelistet, ohne Subitems oder ähnliches, was das Ganze deutlich einfacher machen würde.
    Bedeutet das, du bekommst alle Ordner und Dateien auf einmal gelistet, inklusive der Unterordner?

    Warum willst du nun rekursiv downloaden, statt einfach die flache Liste durchzunudeln?
    Moin,

    ja, da ist dann jedes einzelne Element direkt drin, also auch die SubItems.
    Es gibt also mal ein Item ​test, mal ein Item ​test/test.txt usw.

    Da ich das Ganze in einem TreeView anzeigen möchte, müsste das dann rekursiv sein, sodass die Unterordner bzw. SubItems auch als untergeordnete Node zur Verfügung stehen. Ansonsten wäre es natürlich einfacher.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Genau. Mein Ansatz war eben der mit dem ParentPath, weil sich mir nur das aus den bestehenden Properties ergab.
    Das ist halt die Liste: trade-programming.de/pixelkram/bradevqhxs.png

    Und darin sind dann eben die ​FtpItems: trade-programming.de/pixelkram/ogrmaxyuwf.png
    Ein Unterordner dieses Verzeichnisses sieht dann als Item so aus: trade-programming.de/pixelkram/gsfpenzhir.png

    Ist aber genauso ein Teil der kompletten Liste. Reicht das oder brauchst Du es noch detaillierter?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    also das muss man entwickeln, und dazu braucht man Beispieldaten.
    Bilder von Beispieldaten finde ich unpraktisch - das müsste ich ja alles abschreiben.

    etwa eine Liste aller RawTexts müsste machbar sein - das ist ja eine String-Repräsentation, bei der keine Information reduziert wurde.
    Möglichst in genau der Reihenfolge, wie sie einläuft, denn vlt. lässt sich anhand der Reihenfolge was optimieren oder vereinfachen.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Nun gut, habe das jetzt auch nur abgeschrieben, aber das ist alles, was man hierfür an Daten verwerten kann.

    Item1:

    FullPath: "/downfall"
    ItemType: Directory
    Name: "downfall"
    ParentPath: "/"

    Item2:

    FullPath: "/downfall/fonts"
    ItemType: Directory
    Name: "fonts"
    ParentPath: "/downfall"

    Item3:

    FullPath: "/downloads"
    ItemType: Directory
    Name: "downloads"
    ParentPath: "/"

    Item4:

    FullPath: "/downloads/test.php"
    ItemType: File
    Name: "test.php"
    ParentPath: "/downloads"

    Item5:

    FullPath: "/downloads/test/test2"
    ItemType: Directory
    Name: "test2"
    ParentPath: "/downloads/test"

    Somit solltest Du den Aufbau recht gut verstehen können. Mir ist übrigens aufgefallen, dass ich direkt abfragen kann, ob das Item ein Directory oder File ist, sodass ich Path.HasExtension nicht benötige, aber das ist ja jetzt eine Nebensache.
    Bist Du allerdings sicher, dass man da wirklich etwas neu schreiben muss? Gibt es keine Lösung für das Problem mit der bestehenden Lösung?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Nunja, ich möchte Dir ja jetzt nicht von meinem Server die ganzen Dateien auflisten, zumal das ziemlich viele sind.
    Was fehlt Dir denn an Info noch? Bei den anderen ist es nämlich genau das Selbe, wie bei denen, die ich gerade aufgelistet habe.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Könnte man das nicht dann manuell ausweiten? Solange das diese dann drin hat, könnte ich das ja anpassen.
    Hauptsache der Algorithmus passt insofern, dass es rekursiv funktioniert.

    Jetzt muss ich trotzdem nochmal fragen:

    Trade schrieb:

    Bist Du allerdings sicher, dass man da wirklich etwas neu schreiben muss? Gibt es keine Lösung für das Problem mit der bestehenden Lösung?


    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Wie gesagt, die ListingItem-Klasse sollte genau dazu dienen. Wie wäre denn Deine Idee, die Items dann rekursiv in die Childs-Property zu bringen?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Ok, ich schaue mir das dann mal an, sobald Du was hast.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    @ErfinderDesRades
    Dein Code funktioniert super, aber irgendwie blick ich da net durch ?(

    @Trade
    Hab schnell was gemacht, was mindestens genauso gut funktioniert aber aus meiner Sicht etwas einfacher ist. Das Ziel war, auf jegliche Unterteilung der Tokens in Ordner & Dateien oä zu verzichten und nur mit dem Pfad zu arbeiten:

    C#-Quellcode

    1. ​public static Token Plain2Recursive(IEnumerable<string> input, string seperator)
    2. {
    3. Token root = new Token("Root"); //Der Haupttoken, zu welchem alle Children hinzufügefügt werden
    4. foreach(string path in input) //Alle Pfade durchloopen
    5. {
    6. Token parent = root;
    7. foreach(string node in path.Split(new string[] { seperator }, StringSplitOptions.None)) //Path in alle Zwischenelemente (Ordner) splitten
    8. {
    9. if (!parent.Children.Any(t => t.Value == node)) //Wenn der Ordner noch nicht in der Liste erfasst ist, ihn hinzufügen
    10. {
    11. Token nodeToken = new Token(node);
    12. parent.Children.Add(nodeToken); //Aktuelles Element dem letzten Element als Child hinzufügen
    13. parent = nodeToken; //Aktuelles Element als Parent für das nächste Element setzen
    14. }
    15. else
    16. {
    17. parent = parent.Children.First(t => t.Value == node); //Schon vorhandenes Child heraussuchen & als Parent für das nächste Element setzen
    18. }
    19. }
    20. }
    21. return root;
    22. }

    Und noch eine kurze Version davon (1:1 gleiche Vorgehensweise aber halt kompakt geschrieben):

    C#-Quellcode

    1. ​public static Token Plain2RecursiveMinimalistic(IEnumerable<string> input, string seperator)
    2. {
    3. Token root = new Token("");
    4. foreach (string path in input)
    5. {
    6. Token currentParent = root;
    7. foreach (string node in path.Split(new string[] { seperator }, StringSplitOptions.None))
    8. if (!currentParent.Children.Any(t => t.Value == node))
    9. currentParent.Children.Add(currentParent = new Token(node));
    10. else
    11. currentParent = currentParent.Children.First(t => t.Value == node);
    12. }
    13. return root;
    14. }

    Außerdem verwendet dieser Code eine Token-Klasse:

    C#-Quellcode

    1. ​public class Token
    2. {
    3. public Token(string value)
    4. : this(value, new List<Token>())
    5. { }
    6. public Token(string value, List<Token> children)
    7. {
    8. this.Value = value;
    9. this.Children = children;
    10. }
    11. public string Value { get; set; }
    12. public List<Token> Children { get; set; }
    13. }


    Grüße Stefan
    Danke euch beiden!
    Nach etwas rumprobieren hatte ich mich für nafet's Lösung entschieden, da sie etwas kompakter ist. Im Prinzip ist aber die Logik hinter beidem ja das Selbe.

    Es gab nur noch ein kleines Problem, dass im TreeView keine SubItems hinzugefügt wurden, obwohl es bei nafets ging. Privat konnte ich mit ihm feststellen, dass es daran lag, dass er .NET 4.5.1 nutzt und ich .NET 4.0, wo Lambdaausdrücke etc. etwas anders behandelt werden. Einfach ausgelagert und es geht prima.

    Dennoch auch danke für die Mühe, @ErfinderDesRades, konnte da ein paar Ideen mitnehmen.
    Die Lösung ist jetzt diese:

    C#-Quellcode

    1. using System.Collections.Generic;
    2. namespace nUpdate.Administration.Core
    3. {
    4. public class ListingItem
    5. {
    6. public ListingItem(string value)
    7. : this(value, new List<ListingItem>())
    8. {
    9. }
    10. public ListingItem(string value, List<ListingItem> children)
    11. {
    12. Value = value;
    13. Children = children;
    14. }
    15. public string Value { get; set; }
    16. public List<ListingItem> Children { get; set; }
    17. }
    18. }


    C#-Quellcode

    1. private void RecursiveAdd(ListingItem item, TreeNode target)
    2. {
    3. foreach (var child in item.Children)
    4. {
    5. var childNode = new TreeNode(child.Value);
    6. target.Nodes.Add(childNode);
    7. this.RecursiveAdd(child, childNode);
    8. }
    9. }
    10. public static ListingItem PlainListToRecursiveList(IEnumerable<string> inputItems, string seperator)
    11. {
    12. var root = new ListingItem("Root");
    13. foreach (string path in inputItems)
    14. {
    15. ListingItem currentParent = root;
    16. foreach (string node in path.Split(new[] { seperator }, StringSplitOptions.None))
    17. {
    18. if (currentParent.Children.All(t => t.Value != node))
    19. {
    20. var nodeToken = new ListingItem(node);
    21. currentParent.Children.Add(nodeToken);
    22. currentParent = nodeToken;
    23. }
    24. else
    25. {
    26. currentParent = currentParent.Children.First(t => t.Value == node);
    27. }
    28. }
    29. }
    30. return root;
    31. }


    C#-Quellcode

    1. if (_listedFtpItems != null)
    2. {
    3. ListingItem root = PlainListToRecursiveList(_listedFtpItems.Select(item => item.FullPath.Remove(0, 1)), "/");
    4. var rootNode = new TreeNode("Server", 0, 0);
    5. RecursiveAdd(root, rootNode);
    6. BeginInvoke(new Action(() =>
    7. {
    8. serverDataTreeView.Nodes.Add(rootNode);
    9. serverDataTreeView.ExpandAll();
    10. serverDataTreeView.SelectedNode = rootNode;
    11. }));
    12. }


    Also es kommt ja nicht oft vor, dass ich Probleme habe, aber das war schon etwas. Jedenfalls jetzt weiter an die Arbeit!

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    eine Anmerkung noch und eine kleine Verbesserung:
    Anmerkung: Der Nafets-Algo ist besser als meiner, aber ist nicht rekursiv, also solteste umbenennen.
    Verbesserung: Hier findet zweimal dieselbe Suche statt (#1 + #7), das solltest in nur einer Suche abhandeln:

    C#-Quellcode

    1. if (currentParent.Children.All(t => t.Value != node)) {
    2. var nodeToken = new ListingItem(node);
    3. currentParent.Children.Add(nodeToken);
    4. currentParent = nodeToken;
    5. }
    6. else {
    7. currentParent = currentParent.Children.First(t => t.Value == node);
    8. }

    C#-Quellcode

    1. public static Token BuildTree(IEnumerable<string> input, char seperator) {
    2. var root = new Token("");
    3. foreach (var path in input) {
    4. var parent = root;
    5. foreach (var sgm in path.Split(seperator)) {
    6. var child = parent.Children.FirstOrDefault(t => t.Value == sgm);
    7. if (child == null) parent.Children.Add(child = new Token(sgm));
    8. parent = child;
    9. }
    10. }
    11. return root;
    12. }
    (zeilen #6 - #8 bilden die Entsprechung zu obigem Ausschnitt)
    Ok, das werde ich noch ändern, danke.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!: