This worked like a champ. Posting full implementation here for the benefit of others. I'll list the utility function that gets the directory info and then the binding transform that loads a tree component.
Utility function to build directory structure
def getDirectoryStructure(unc_path, as_json=False):
'''
Traverses a directory at the given UNC path and returns its structure either as a dict/array object or JSON object.
Params:
unc_path: UNC path as a string
as_json: Set TRUE to generate JSON output.
Returns:
directory structure as either JSON string or array of dictionaries
'''
def traverse_directory(directory):
dir_dict = {
"name": directory.getName(),
"path": directory.getAbsolutePath(),
"subdirectories": [],
"files": []
}
if directory.isDirectory():
for file in directory.listFiles():
if file.isDirectory():
dir_dict["subdirectories"].append(traverse_directory(file))
else:
dir_dict["files"].append(file.getName())
return dir_dict
root = File(unc_path)
if not root.exists() or not root.isDirectory():
error_output = {"error": "Invalid path or not a directory"}
return json.dumps(error_output, indent=2) if as_json else error_output
result = traverse_directory(root)
return json.dumps(result, indent=2) if as_json else result
# Example usage:
# Get raw Python dictionary output
# Notice use of r"" to force raw string interpretation so double \\ aren't required
# print(get_directory_structure(r"\\server\shared\folder"))
#
# Get JSON string output
# print(get_directory_structure(r"\\server\shared\folder", as_json=True))
I have this function bound to a custom property dirStructure
, and the transform below loads a Tree Container.
I also have a view param validFileTypes
, which is a comma-delimited string of file types (e.g. "txt,pdf") that the transform can use to filter out the results based on file type. There's also a Boolean view param showOnlyValidFileTypes
that toggles this behavior on and off.
Transform script to load Tree Container
def transform(self, value, quality, timestamp):
# if valid file types are provided and we want to filter by file type,
# build a set of filetype extension we can use for string matching later (e.g. {".pdf",".txt'}
if len(self.view.params.validFileTypes)>0 and self.view.params.showOnlyValidFileTypes:
validExtensions = {"." + ext.strip().lower() for ext in self.view.params.validFileTypes.split(',')}
else:
validExtensions = None # null result means include all
# build a single tree node here - we will call this recursively to build the tree
def createNode(name, path, subdirectories, files, validExtensions):
node = {
"label": name, # filename only
"data": {"path": path}, # full path to file
"expanded": False,
"items": []
}
# recursively build nodes for each folder
for subDir in subdirectories:
node["items"].append(createNode(
subDir["name"],
subDir["path"],
subDir["subdirectories"],
subDir["files"],
validExtensions
))
# build node for each file
# here we are highlighting PDF files with an icon
# this can be extended for other file types of interest
for file in files:
# filter by file type if desired
if not validExtensions or file.lower().endswith(tuple(validExtensions)):
item = {
"label": file,
"data": {"path": path + "\\" + file},
"expanded": False,
"items": []
}
if file.lower().endswith(".pdf"):
item["icon"] = {
"path": "material/picture_as_pdf",
"color": "#F40F02",
"style": {}
}
node["items"].append(item)
return node
if "error" in value:
return [{"label": "Error: " + value["error"], "data": {"message": value["error"]}, "items": [], "expanded": True}]
treeStructure = [createNode(value["name"], value["path"], value["subdirectories"], value["files"], validExtensions)]
return treeStructure