Allow user to browse for network file and return path?

I'm expecting a no answer on this, but I thought I'd ask anyway...

The end user stores PDFs on a network file share that is accessible by both the client and the gateway. From our Perspective UI, they want to be able to browse to that file location and retrieve the path to a specific PDF, so we can save the path in our database.

We don't want to duplicate this source of record by uploading the PDF using the FileUpload component. We just want to retrieve the filepath, e.g. \\server\folder\file.pdf.

Since we know the path to the file share, is there a way to pull a directory listing and build a surrogate file browser to allow the user to select from?

It's just too much to ask for the end user to open File Explorer in Windows and copy/paste the file path into Perspective.

Gateway scripts cannot access to mapped drives - #6 by pturmel has some points you should be aware of regarding this topic (even though you're only reading).

Since the gateway has access, you can enumerate the files there and present the results in a Perspective tree component or series of tables. That gives you total control over what to do with those file paths.

1 Like

What's the best way to enumerate the files (and folders) from within Perspective?

Odd - that link points to a thread that contains this same post, which recursively links to itself.

Oops, so that's where it went. The editor allows one to look up another question while typing a reply. Usually I have to select which post the reply should go to. I didn't see the prompt and the post didn't show up here so I reposted it.

I'll try to fix it.

Meanwhile, try this in Script Console (which will run on your local machine).

import os
directory = "C:\<path to some directory>"
files = [f for (dir, subdirs, fs) in os.walk(directory) for f in fs if f.endswith(".pdf")]
print files	
1 Like

I would ask ChatGPT/et al for "a Jython function to traverse a given directory, given as a string parameter, returning a list of file names inside that directory, using the Java NIO Files API". Should get you at least 90% of the way there.

Python's standard library builtin os should work, but isn't really maintained, where relying on the Java standard library means you get updated code as you update Ignition, among other performance and correctness benefits.

2 Likes

Use this:

java.io.File.listFiles()

Check each result object for .directory (boolean) to do recursion, if necessary.

1 Like

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
2 Likes