Python Forum
File system representation in a data structure
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
File system representation in a data structure
#1
I have a problem related to a Qt app, but I figured it would be ok to post it in this section as is it not Qt specific. Still, I left the code in it's real context for the sake of the explanation. I need to build a QMenu that respect the structure of a file system. For example, the files in the 'files' variable below should output a menu like this:

Quote:folderless file
collision > bubu
sub 1 > action1
-------> sub2 > action2
-----------------> collision > action3

I must store the submenus in a dict, and as a key I used the folder name. Obviously, the problem is that there are collisions when a two subfolders share the same name (here 'collision'). Their content is merged like so;

Quote:folderless file
collision > bubu, action3
sub 1 > action1
-------> sub2 > action2
-----------------> collision > bubu, action3

I would like to find a simple way to do this, while supporting nested files. I tried replacing the value 'ref' to make it more specific, but often the result are strange and difficult to debug. Hopefully someone will think of something clever to solve this.

The relevant part is in the 'refresh' function below;

#!/usr/bin/python3
import sys
from pathlib import Path, PurePath
from PyQt5 import QtWidgets, QtCore, QtGui


class Menu(QtWidgets.QMenu):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.aboutToShow.connect(self.refresh)

    def refresh(self):
        ### For the sake of the example
        CFG_DIR = Path('/app/')
        files = \
            [Path('/app/notes/folderless file.txt'),
            Path('/app/notes/collision/bubu.txt'),
            Path('/app/notes/sub1/action1.txt'),
            Path('/app/notes/sub1/sub2/action2.txt'),
            Path('/app/notes/sub1/sub2/collision/action3.txt')]
        ###

        self.subMenus = {}
        self.items = {}
        menus = set()

        self.clear()
        for path in files:
            name = path.stem
            folders = path.relative_to(CFG_DIR / "notes")
            folders = list(PurePath(folders).parts)[:-1]

            if not folders:
                self.items[path] = QtWidgets.QAction(name, checkable=True)
                self.addAction(self.items[path])

            elif folders[0] != ".trash":
                for i, f in enumerate(reversed(folders), 0):
                    ref = f ##

                    if ref not in self.subMenus:
                        self.subMenus[ref] = QtWidgets.QMenu(f)

                    if i == 0:
                        self.items[path] = QtWidgets.QAction(name, checkable=True)
                        self.subMenus[ref].addAction(self.items[path])
                    else:
                        self.subMenus[ref].addMenu(lastMenu)

                    if i == len(folders)-1:
                        menus.add(self.subMenus[ref])
                    else:
                        lastMenu = self.subMenus[ref]

        for m in menus:
            self.addMenu(m)


class Main(QtWidgets.QSystemTrayIcon):
    def __init__(self, parent):
        super().__init__(parent)
        self.menu = Menu(self)
        self.setContextMenu(self.menu)
        self.show()


def main():
    app = QtWidgets.QApplication(sys.argv)
    widget = Main(app)
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
Reply
#2
I could get it to work by first copying the folder structure, then inserting each files in the proper nest. The result is a dict, which is then converted into a QMenu/QAction structure with a resursive fonction. All items are stored in a set to survive the garbage collection.

Quote:# First loop:
{'collision': {}, 'sub1': {'sub2': {'collision': {}}}}

# Second loop:
{'collision': {'collision': '/home/user/.config/qtpad-vim/notes/collision/collision.txt'}, 'sub1': {'sub2': {'collision': {'action3': '/home/user/.config/qtpad-vim/notes/sub1/sub2/collision/action3.txt'}, 'action2': '/home/user/.config/qtpad-vim/notes/sub1/sub2/action2.txt'}, 'action1': '/home/user/.config/qtpad-vim/notes/sub1/action1.txt'}, 'lvl0 with spaces': '/home/user/.config/qtpad-vim/notes/lvl0 with spaces.txt'}

    def _fill(self, current, dest):
        for part in current:
            if type(current[part]) is dict:
                item = QtWidgets.QMenu(part)
                dest.addMenu(item)
                self._fill(current=current[part], dest=item)
            else:
                item = QtWidgets.QAction(part, checkable=True)
                item.triggered.connect(lambda checked, path=current[part]: print(path))
                dest.addAction(item)
            self.items.add(item)

    def refresh(self):
        folder = Path(CFG_DIR / "notes")
        dirs = [x for x in folder.rglob("*") if x.is_dir()]
        files = [x for x in folder.rglob("*.txt") if x.is_file()]
        struct = {}

        for path in dirs:
            folders = path.relative_to(CFG_DIR / "notes")
            folders = PurePath(folders).parts
            last = struct
            for f in folders:
                last.setdefault(f, {})
                last = last[f]

        for path in files:
            folders = path.relative_to(CFG_DIR / "notes")
            folders = PurePath(folders).parts[:-1]
            last = struct
            for f in folders:
                last = last[f]
            last[path.stem] = str(path)

        self.clear()
        self.items = set()
        self._fill(struct, dest=self)
If someone think of a better way I would still like to know :)
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Writing a Linear Search algorithm - malformed string representation Drone4four 10 833 Jan-10-2024, 08:39 AM
Last Post: gulshan212
  Hard disk structure like a file selection dialog malonn 2 761 Aug-09-2023, 09:14 PM
Last Post: malonn
  FileNotFoundError: [WinError 2] The system cannot find the file specified NewBiee 2 1,496 Jul-31-2023, 11:42 AM
Last Post: deanhystad
  How can I add certain elements in this 2d data structure and calculate a mean TheOddCircle 3 1,508 May-27-2022, 09:09 AM
Last Post: paul18fr
  Appropriate data-structure / design for business-day relations (week/month-wise) sx999 2 2,754 Apr-23-2021, 08:09 AM
Last Post: sx999
  what data structure to use? Winfried 4 2,784 Mar-16-2021, 12:11 PM
Last Post: buran
  Yahoo_fin, Pandas: how to convert data table structure in csv file detlefschmitt 14 7,559 Feb-15-2021, 12:58 PM
Last Post: detlefschmitt
  Difference between os.system("clear") and os.system("cls") chmsrohit 7 16,495 Jan-11-2021, 06:30 PM
Last Post: ykumar34
  How to use Bunch data structure moish 2 2,832 Dec-24-2020, 06:25 PM
Last Post: deanhystad
  xml file creation from an XML file template and data from an excel file naji_python 1 2,070 Dec-21-2020, 03:24 PM
Last Post: Gribouillis

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020