Add path into file and folder entries & allow non recursive listings

This commit is contained in:
Gina Häußge 2016-07-01 17:51:21 +02:00
parent 72bc30eae5
commit 08de33e27d
3 changed files with 131 additions and 87 deletions

View file

@ -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

View file

@ -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
}
}

View file

@ -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) {