Add path into file and folder entries & allow non recursive listings
This commit is contained in:
parent
72bc30eae5
commit
08de33e27d
3 changed files with 131 additions and 87 deletions
|
|
@ -77,14 +77,20 @@ class StorageInterface(object):
|
|||
|
||||
{
|
||||
"some_folder": {
|
||||
"name": "some_folder",
|
||||
"path": "some_folder",
|
||||
"type": "folder",
|
||||
"children": {
|
||||
"some_sub_folder": {
|
||||
"name": "some_sub_folder",
|
||||
"path": "some_folder/some_sub_folder",
|
||||
"type": "folder",
|
||||
"typePath": ["folder"],
|
||||
"children": { ... }
|
||||
},
|
||||
"some_file.gcode": {
|
||||
"name": "some_file.gcode",
|
||||
"path": "some_folder/some_file.gcode",
|
||||
"type": "machinecode",
|
||||
"typePath": ["machinecode", "gcode"],
|
||||
"hash": "<sha1 hash>",
|
||||
|
|
@ -94,6 +100,8 @@ class StorageInterface(object):
|
|||
...
|
||||
}
|
||||
"test.gcode": {
|
||||
"name": "test.gcode",
|
||||
"path": "test.gcode",
|
||||
"type": "machinecode",
|
||||
"typePath": ["machinecode", "gcode"],
|
||||
"hash": "<sha1 hash>",
|
||||
|
|
@ -101,6 +109,8 @@ class StorageInterface(object):
|
|||
...
|
||||
},
|
||||
"test.stl": {
|
||||
"name": "test.stl",
|
||||
"path": "test.stl",
|
||||
"type": "model",
|
||||
"typePath": ["model", "stl"],
|
||||
"hash": "<sha1 hash>",
|
||||
|
|
@ -1023,7 +1033,7 @@ class LocalFileStorage(StorageInterface):
|
|||
if metadata_dirty:
|
||||
self._save_metadata(path, metadata)
|
||||
|
||||
def _list_folder(self, path, filter=None, recursive=True):
|
||||
def _list_folder(self, path, base="", filter=None, recursive=True):
|
||||
metadata = self._get_metadata(path)
|
||||
if not metadata:
|
||||
metadata = dict()
|
||||
|
|
@ -1036,6 +1046,7 @@ class LocalFileStorage(StorageInterface):
|
|||
continue
|
||||
|
||||
entry_path = os.path.join(path, entry)
|
||||
path_in_location = entry if not base else base + entry
|
||||
|
||||
# file handling
|
||||
if os.path.isfile(entry_path):
|
||||
|
|
@ -1059,6 +1070,7 @@ class LocalFileStorage(StorageInterface):
|
|||
extended_entry_data = dict()
|
||||
extended_entry_data.update(entry_data)
|
||||
extended_entry_data["name"] = entry
|
||||
extended_entry_data["path"] = path_in_location
|
||||
extended_entry_data["type"] = file_type
|
||||
extended_entry_data["typePath"] = type_path
|
||||
stat = os.stat(entry_path)
|
||||
|
|
@ -1069,14 +1081,17 @@ class LocalFileStorage(StorageInterface):
|
|||
result[entry] = extended_entry_data
|
||||
|
||||
# folder recursion
|
||||
elif os.path.isdir(entry_path) and recursive:
|
||||
sub_result = self._list_folder(entry_path, filter=filter, recursive=recursive)
|
||||
elif os.path.isdir(entry_path):
|
||||
entry_data = dict(
|
||||
name=entry,
|
||||
path=path_in_location,
|
||||
type="folder",
|
||||
type_path=["folder"],
|
||||
children=sub_result
|
||||
type_path=["folder"]
|
||||
)
|
||||
if recursive:
|
||||
sub_result = self._list_folder(entry_path, base=path_in_location + "/", filter=filter,
|
||||
recursive=recursive)
|
||||
entry_data["children"] = sub_result
|
||||
|
||||
if not filter or filter(entry, entry_data):
|
||||
def get_size():
|
||||
|
|
@ -1090,7 +1105,8 @@ class LocalFileStorage(StorageInterface):
|
|||
# only add folders passing the optional filter
|
||||
extended_entry_data = dict()
|
||||
extended_entry_data.update(entry_data)
|
||||
extended_entry_data["size"] = get_size()
|
||||
if recursive:
|
||||
extended_entry_data["size"] = get_size()
|
||||
|
||||
result[entry] = extended_entry_data
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ def readGcodeFilesForOrigin(origin):
|
|||
|
||||
recursive = False
|
||||
if "recursive" in request.values:
|
||||
recursive = request.values["recursive"] == 'true'
|
||||
recursive = request.values["recursive"] in valid_boolean_trues
|
||||
|
||||
files = _getFileList(origin, recursive=recursive)
|
||||
|
||||
|
|
@ -54,47 +54,18 @@ def readGcodeFilesForOrigin(origin):
|
|||
return jsonify(files=files)
|
||||
|
||||
|
||||
def _getFileDetails(origin, path):
|
||||
files = _getFileList(origin, recursive=True)
|
||||
path = path.split('/')
|
||||
|
||||
if len(path) == 1:
|
||||
# shortcut for files in the root folder
|
||||
name = path[0]
|
||||
for f in files:
|
||||
if f["name"] == name:
|
||||
return f
|
||||
def _getFileDetails(origin, path, recursive=True):
|
||||
parent, path = fileManager.split_path(origin, path)
|
||||
files = _getFileList(origin, path=parent, recursive=recursive)
|
||||
|
||||
for f in files:
|
||||
if f["name"] == path:
|
||||
return f
|
||||
else:
|
||||
return None
|
||||
|
||||
node = files
|
||||
while path:
|
||||
segment = path.pop(0)
|
||||
for f in node:
|
||||
if not f["name"] == segment:
|
||||
# wrong name => next!
|
||||
continue
|
||||
|
||||
if not path:
|
||||
# no path left and name matches => found it!
|
||||
return f
|
||||
|
||||
if not f["type"] == "folder":
|
||||
# path left but not a folder => that doesn't work
|
||||
return None
|
||||
|
||||
# we'll use this folder's children as the next iteration
|
||||
node = f["children"]
|
||||
break
|
||||
else:
|
||||
# nothing matched the name, we can't find it
|
||||
return None
|
||||
|
||||
# nothing returned until now => not found
|
||||
return None
|
||||
|
||||
|
||||
def _getFileList(origin, filter=None, recursive=False):
|
||||
def _getFileList(origin, path=None, filter=None, recursive=False):
|
||||
if origin == FileDestinations.SDCARD:
|
||||
sdFileList = printer.get_sd_files()
|
||||
|
||||
|
|
@ -117,26 +88,33 @@ def _getFileList(origin, filter=None, recursive=False):
|
|||
if filter:
|
||||
filter_func = lambda entry, entry_data: octoprint.filemanager.valid_file_type(entry, type=filter)
|
||||
|
||||
files = fileManager.list_files(origin, filter=filter_func, recursive=recursive)[origin].values()
|
||||
files = fileManager.list_files(origin, path=path, filter=filter_func, recursive=recursive)[origin].values()
|
||||
|
||||
def analyse_recursively(files, path=None):
|
||||
if path is None:
|
||||
path = ""
|
||||
|
||||
for file in files:
|
||||
file["origin"] = FileDestinations.LOCAL
|
||||
for file_or_folder in files:
|
||||
file_or_folder["origin"] = FileDestinations.LOCAL
|
||||
|
||||
if file["type"] == "folder":
|
||||
file["children"] = analyse_recursively(file["children"].values(), path + file["name"] + "/")
|
||||
if file_or_folder["type"] == "folder":
|
||||
if "children" in file_or_folder:
|
||||
file_or_folder["children"] = analyse_recursively(file_or_folder["children"].values(), path + file_or_folder["name"] + "/")
|
||||
|
||||
file_or_folder.update({
|
||||
"refs": {
|
||||
"resource": url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=path + file_or_folder["name"], _external=True)
|
||||
}
|
||||
})
|
||||
else:
|
||||
if "analysis" in file and octoprint.filemanager.valid_file_type(file["name"], type="gcode"):
|
||||
file["gcodeAnalysis"] = file["analysis"]
|
||||
del file["analysis"]
|
||||
if "analysis" in file_or_folder and octoprint.filemanager.valid_file_type(file_or_folder["name"], type="gcode"):
|
||||
file_or_folder["gcodeAnalysis"] = file_or_folder["analysis"]
|
||||
del file_or_folder["analysis"]
|
||||
|
||||
if "history" in file and octoprint.filemanager.valid_file_type(file["name"], type="gcode"):
|
||||
if "history" in file_or_folder and octoprint.filemanager.valid_file_type(file_or_folder["name"], type="gcode"):
|
||||
# convert print log
|
||||
history = file["history"]
|
||||
del file["history"]
|
||||
history = file_or_folder["history"]
|
||||
del file_or_folder["history"]
|
||||
success = 0
|
||||
failure = 0
|
||||
last = None
|
||||
|
|
@ -156,12 +134,12 @@ def _getFileList(origin, filter=None, recursive=False):
|
|||
)
|
||||
if "printTime" in last:
|
||||
prints["last"]["printTime"] = last["printTime"]
|
||||
file["prints"] = prints
|
||||
file_or_folder["prints"] = prints
|
||||
|
||||
file.update({
|
||||
file_or_folder.update({
|
||||
"refs": {
|
||||
"resource": url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=path + file["name"], _external=True),
|
||||
"download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + path + file["name"]
|
||||
"resource": url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=path + file_or_folder["name"], _external=True),
|
||||
"download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + path + file_or_folder["name"]
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -251,7 +229,7 @@ def uploadGcodeFile(target):
|
|||
return make_response("Can not upload file %s, wrong format?" % upload.filename, 415)
|
||||
|
||||
if "path" in request.values and request.values["path"]:
|
||||
# FileDestinations.LOCAL = should normally be target, but can't because SDCard handling isn't implemented yet
|
||||
# we currently only support uploads to sdcard via local, so first target is local instead of "target"
|
||||
futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, request.values["path"])
|
||||
|
||||
# prohibit overwriting currently selected file while it's being printed
|
||||
|
|
@ -284,7 +262,6 @@ def uploadGcodeFile(target):
|
|||
if octoprint.filemanager.valid_file_type(added_file, "gcode") and (selectAfterUpload or printAfterSelect or (currentFilename == filename and currentOrigin == destination)):
|
||||
printer.select_file(absFilename, destination == FileDestinations.SDCARD, printAfterSelect)
|
||||
|
||||
# FileDestinations.LOCAL = should normally be target, but can't because SDCard handling isn't implemented yet
|
||||
futureFullPath = fileManager.join_path(FileDestinations.LOCAL, futurePath, futureFilename)
|
||||
|
||||
try:
|
||||
|
|
@ -316,7 +293,8 @@ def uploadGcodeFile(target):
|
|||
location = url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=filename, _external=True)
|
||||
files.update({
|
||||
FileDestinations.LOCAL: {
|
||||
"name": filename,
|
||||
"name": futureFilename,
|
||||
"path": filename,
|
||||
"origin": FileDestinations.LOCAL,
|
||||
"refs": {
|
||||
"resource": location,
|
||||
|
|
@ -330,6 +308,7 @@ def uploadGcodeFile(target):
|
|||
files.update({
|
||||
FileDestinations.SDCARD: {
|
||||
"name": sdFilename,
|
||||
"path": sdFilename,
|
||||
"origin": FileDestinations.SDCARD,
|
||||
"refs": {
|
||||
"resource": location
|
||||
|
|
@ -340,6 +319,7 @@ def uploadGcodeFile(target):
|
|||
r = make_response(jsonify(files=files, done=done), 201)
|
||||
r.headers["Location"] = location
|
||||
return r
|
||||
|
||||
elif "foldername" in request.values:
|
||||
foldername = request.values["foldername"]
|
||||
|
||||
|
|
@ -350,29 +330,49 @@ def uploadGcodeFile(target):
|
|||
if not futureName or not futurePath:
|
||||
return make_response("Can't create a folder with an empty name", 400)
|
||||
|
||||
if "path" in request.values and request.values["path"]:
|
||||
futurePath = fileManager.sanitize_path(FileDestinations.LOCAL,
|
||||
request.values["path"])
|
||||
|
||||
futureFullPath = fileManager.join_path(target, futurePath, futureName)
|
||||
if octoprint.filemanager.valid_file_type(futureName):
|
||||
return make_response("Can't create a folder named %s, please try another name" % futureName, 409)
|
||||
|
||||
try:
|
||||
fileManager.add_folder(target, futureFullPath)
|
||||
added_folder = fileManager.add_folder(target, futureFullPath)
|
||||
except octoprint.filemanager.storage.StorageError as e:
|
||||
if e.code == octoprint.filemanager.storage.StorageError.INVALID_DIRECTORY:
|
||||
return make_response("Could not create folder {}, invalid directory".format(futureName))
|
||||
else:
|
||||
return make_response("Could not create folder {}".format(futureName))
|
||||
|
||||
location = url_for(".readGcodeFile",
|
||||
target=FileDestinations.LOCAL,
|
||||
filename=added_folder,
|
||||
_external=True)
|
||||
folder = dict(name=futureName,
|
||||
path=added_folder,
|
||||
origin=target,
|
||||
refs=dict(resource=location))
|
||||
|
||||
r = make_response(jsonify(folder=folder, done=True), 201)
|
||||
r.headers["Location"] = location
|
||||
return r
|
||||
|
||||
else:
|
||||
return make_response("No file to upload and no folder to create", 400)
|
||||
|
||||
return NO_CONTENT
|
||||
|
||||
|
||||
@api.route("/files/<string:target>/<path:filename>", methods=["GET"])
|
||||
def readGcodeFile(target, filename):
|
||||
if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]:
|
||||
return make_response("Unknown target: %s" % target, 404)
|
||||
|
||||
file = _getFileDetails(target, filename)
|
||||
recursive = False
|
||||
if "recursive" in request.values:
|
||||
recursive = request.values["recursive"] in valid_boolean_trues
|
||||
|
||||
file = _getFileDetails(target, filename, recursive=recursive)
|
||||
if not file:
|
||||
return make_response("File not found on '%s': %s" % (target, filename), 404)
|
||||
|
||||
|
|
@ -453,17 +453,18 @@ def gcodeFileCommand(filename, target):
|
|||
name, _ = os.path.splitext(filename)
|
||||
gcode_name = name + ".gco"
|
||||
|
||||
full_path = gcode_name
|
||||
if "path" in data and data["path"]:
|
||||
gcode_name = fileManager.join_path(target, data["path"], gcode_name)
|
||||
full_path = fileManager.join_path(target, data["path"], gcode_name)
|
||||
else:
|
||||
path, _ = fileManager.split_path(target, filename)
|
||||
if path:
|
||||
gcode_name = fileManager.join_path(target, path, gcode_name)
|
||||
full_path = fileManager.join_path(target, path, gcode_name)
|
||||
|
||||
# prohibit overwriting the file that is currently being printed
|
||||
currentOrigin, currentFilename = _getCurrentFile()
|
||||
if currentFilename == gcode_name and currentOrigin == target and (printer.is_printing() or printer.is_paused()):
|
||||
make_response("Trying to slice into file that is currently being printed: %s" % gcode_name, 409)
|
||||
if currentFilename == full_path and currentOrigin == target and (printer.is_printing() or printer.is_paused()):
|
||||
make_response("Trying to slice into file that is currently being printed: %s" % full_path, 409)
|
||||
|
||||
if "profile" in data.keys() and data["profile"]:
|
||||
profile = data["profile"]
|
||||
|
|
@ -500,35 +501,36 @@ def gcodeFileCommand(filename, target):
|
|||
for key in override_keys:
|
||||
overrides[key[len("profile."):]] = data[key]
|
||||
|
||||
def slicing_done(target, gcode_name, select_after_slicing, print_after_slicing):
|
||||
def slicing_done(target, path, select_after_slicing, print_after_slicing):
|
||||
if select_after_slicing or print_after_slicing:
|
||||
sd = False
|
||||
if target == FileDestinations.SDCARD:
|
||||
filenameToSelect = gcode_name
|
||||
filenameToSelect = path
|
||||
sd = True
|
||||
else:
|
||||
filenameToSelect = fileManager.path_on_disk(target, gcode_name)
|
||||
filenameToSelect = fileManager.path_on_disk(target, path)
|
||||
printer.select_file(filenameToSelect, sd, print_after_slicing)
|
||||
|
||||
try:
|
||||
fileManager.slice(slicer, target, filename, target, gcode_name,
|
||||
fileManager.slice(slicer, target, filename, target, full_path,
|
||||
profile=profile,
|
||||
printer_profile_id=printerProfile,
|
||||
position=position,
|
||||
overrides=overrides,
|
||||
callback=slicing_done,
|
||||
callback_args=(target, gcode_name, select_after_slicing, print_after_slicing))
|
||||
callback_args=(target, full_path, select_after_slicing, print_after_slicing))
|
||||
except octoprint.slicing.UnknownProfile:
|
||||
return make_response("Profile {profile} doesn't exist".format(**locals()), 400)
|
||||
|
||||
files = {}
|
||||
location = url_for(".readGcodeFile", target=target, filename=gcode_name, _external=True)
|
||||
location = url_for(".readGcodeFile", target=target, filename=full_path, _external=True)
|
||||
result = {
|
||||
"name": gcode_name,
|
||||
"path": full_path,
|
||||
"origin": FileDestinations.LOCAL,
|
||||
"refs": {
|
||||
"resource": location,
|
||||
"download": url_for("index", _external=True) + "downloads/files/" + target + "/" + gcode_name
|
||||
"download": url_for("index", _external=True) + "downloads/files/" + target + "/" + full_path
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,9 +166,9 @@ $(function() {
|
|||
} else {
|
||||
self.listHelper.selectItem(function(item) {
|
||||
if (item.type == "folder") {
|
||||
return _.startsWith(filename, OctoPrint.files.pathForElement(item) + "/");
|
||||
return _.startsWith(filename, item.path + "/");
|
||||
} else {
|
||||
return OctoPrint.files.pathForElement(item) == filename;
|
||||
return item.path == filename;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -198,7 +198,7 @@ $(function() {
|
|||
if (self._otherRequestInProgress) return;
|
||||
|
||||
self._otherRequestInProgress = true;
|
||||
OctoPrint.files.list({ data: { recursive: true} })
|
||||
OctoPrint.files.list(true)
|
||||
.done(function(response) {
|
||||
self.fromResponse(response, filenameToFocus, locationToFocus, switchToPath);
|
||||
})
|
||||
|
|
@ -243,7 +243,7 @@ $(function() {
|
|||
};
|
||||
|
||||
self.changeFolder = function(data) {
|
||||
self.currentPath(OctoPrint.files.pathForElement(data));
|
||||
self.currentPath(data.path);
|
||||
self.listHelper.updateItems(data.children);
|
||||
self.highlightCurrentFilename();
|
||||
};
|
||||
|
|
@ -255,7 +255,7 @@ $(function() {
|
|||
};
|
||||
|
||||
self.changeFolderByPath = function(path) {
|
||||
var element = OctoPrint.files.elementByPath(path, { children: self.allItems() });
|
||||
var element = self.elementByPath(path);
|
||||
if (element) {
|
||||
self.currentPath(path);
|
||||
self.listHelper.updateItems(element.children);
|
||||
|
|
@ -268,6 +268,7 @@ $(function() {
|
|||
|
||||
self.showAddFolderDialog = function() {
|
||||
if (self.addFolderDialog) {
|
||||
self.addFolderName("");
|
||||
self.addFolderDialog.modal("show");
|
||||
}
|
||||
};
|
||||
|
|
@ -287,7 +288,7 @@ $(function() {
|
|||
if (!file) {
|
||||
return;
|
||||
}
|
||||
OctoPrint.files.select(file.origin, OctoPrint.files.pathForElement(file))
|
||||
OctoPrint.files.select(file.origin, file.path)
|
||||
.done(function() {
|
||||
var withinPrintDimensions = self.evaluatePrintDimensions(file, true);
|
||||
if (withinPrintDimensions && printAfterLoad) {
|
||||
|
|
@ -315,9 +316,9 @@ $(function() {
|
|||
filenameToFocus = fileToFocus.name;
|
||||
}
|
||||
|
||||
OctoPrint.files.delete(file.origin, OctoPrint.files.pathForElement(file))
|
||||
OctoPrint.files.delete(file.origin, file.path)
|
||||
.done(function() {
|
||||
self.requestData(undefined, filenameToFocus, OctoPrint.files.pathForElement(file.parent));
|
||||
self.requestData(undefined, filenameToFocus, (file.parent ? file.parent.path : ""));
|
||||
})
|
||||
};
|
||||
|
||||
|
|
@ -326,7 +327,7 @@ $(function() {
|
|||
return;
|
||||
}
|
||||
|
||||
self.slicing.show(file.origin, OctoPrint.files.pathForElement(file), true);
|
||||
self.slicing.show(file.origin, file.path, true);
|
||||
};
|
||||
|
||||
self.initSdCard = function() {
|
||||
|
|
@ -556,6 +557,31 @@ $(function() {
|
|||
return false;
|
||||
};
|
||||
|
||||
self.elementByPath = function(path, root) {
|
||||
root = root || {children: self.allItems()};
|
||||
|
||||
var recursiveSearch = function(location, element) {
|
||||
if (location.length == 0) {
|
||||
return element;
|
||||
}
|
||||
|
||||
if (!element.hasOwnProperty("children")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var name = location.shift();
|
||||
for (var i = 0; i < element.children.length; i++) {
|
||||
if (name == element.children[i].name) {
|
||||
return recursiveSearch(location, element.children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return recursiveSearch(path.split("/"), root);
|
||||
};
|
||||
|
||||
self.onUserLoggedIn = function(user) {
|
||||
self.uploadButton.fileupload("enable");
|
||||
if (self.uploadSdButton) {
|
||||
|
|
@ -645,7 +671,7 @@ $(function() {
|
|||
};
|
||||
|
||||
self.onEventTransferDone = function(payload) {
|
||||
self.requestData(payload.remote, "sdcard");
|
||||
self.requestData(undefined, payload.remote, "sdcard");
|
||||
};
|
||||
|
||||
self.onServerConnect = self.onServerReconnect = function(payload) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue