diff --git a/Sunfish/ShareWeb/$sunfish/$index.html b/Sunfish/ShareWeb/$sunfish/$index.html
index d71dfe4..8da28f7 100644
--- a/Sunfish/ShareWeb/$sunfish/$index.html
+++ b/Sunfish/ShareWeb/$sunfish/$index.html
@@ -30,7 +30,7 @@
-
+
{Name}
{Description}
{@Actions}
{%Actions:action}
{/}
diff --git a/Sunfish/ShareWeb/$sunfish/style.css b/Sunfish/ShareWeb/$sunfish/style.css
index b57d43e..2c4fcc9 100644
--- a/Sunfish/ShareWeb/$sunfish/style.css
+++ b/Sunfish/ShareWeb/$sunfish/style.css
@@ -72,6 +72,24 @@
vertical-align: bottom;
}
+#main-toolbar {
+ position: absolute;
+ right: 4rem;
+ top: 0.8rem;
+}
+
+#main-toolbar i.material-icons-round {
+ font-size: 26px;
+ cursor: pointer;
+ color: dimgrey;
+}
+
+#main-toolbar i.material-icons-round:hover {
+ font-size: 26px;
+ cursor: pointer;
+ color: #818181;
+}
+
#wrapper {
margin: 0 auto;
padding: 0.5rem;
@@ -136,7 +154,7 @@
margin-left: 0.3rem;
}
-.test{
+.test {
background-color: #4f6e05;
}
@@ -240,4 +258,93 @@
input:hover {
background-color: #cecece;
+}
+
+.upload-drop {
+ background-color: rgba(0, 0, 0, 0.75);
+}
+
+.upload-drop-icon {
+ position: absolute;
+ top: 5%;
+ height: 90%;
+ left: 25%;
+ width: 50%;
+ bottom: 25%;
+ border: 10px dashed #c1c1c1;
+ border-radius: 2rem;
+ vertical-align: middle;
+ overflow: hidden;
+}
+
+.upload-drop-icon>i.material-icons-round {
+ width: 100%;
+ color: white;
+ height: 100%;
+ vertical-align: middle;
+ font-size: 8rem;
+ margin-top: 35%;
+ text-align: center;
+}
+
+.upload-drop-icon>i.material-icons-round::before {
+ content: "Drop here to upload";
+ position: absolute;
+ top: 58%;
+ left: 0;
+ right: 0;
+ font-family: 'Open Sans';
+ font-size: 1rem;
+}
+
+.upload-list {
+ height: calc( 100% - 4rem);
+ overflow-y: auto;
+ padding: 0 1rem;
+ margin: 1rem 0;
+}
+
+.upload-progress-box {
+ height: 2rem;
+ background: #383838;
+ position: relative;
+}
+
+.upload-progress {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ background: #a6e1ff;
+}
+
+.upload-item {
+ font-family: 'Open Sans';
+ color: white;
+ margin-bottom: 1rem;
+}
+
+.upload-item-progress-box {
+ height: 1rem;
+ background: #383838;
+ border-radius: 0.5rem;
+ margin-top: 0.2rem;
+ border: 1px solid black;
+ position: relative;
+ overflow: hidden;
+}
+
+.upload-item-progress {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ background: #a6e1ff url(/$sunfish/logo.png) 100% 0.05rem/0.8rem 0.8rem no-repeat;
+}
+
+.upload-close {
+ margin-top: 0.15rem;
+ background: #6d6d6d;
+ color: white;
+ margin-left: calc( 100% - 5rem);
}
\ No newline at end of file
diff --git a/Sunfish/ShareWeb/$sunfish/sunfish-directory.js b/Sunfish/ShareWeb/$sunfish/sunfish-directory.js
index 4e4ef7f..44fca25 100644
--- a/Sunfish/ShareWeb/$sunfish/sunfish-directory.js
+++ b/Sunfish/ShareWeb/$sunfish/sunfish-directory.js
@@ -11,6 +11,7 @@
}
sunfish.deleteFile = function (sender) {
+ //TODO: use the DELTE http method
var ask;
var item = getItem(sender);
var isfolder = item.className.indexOf("directory") >= 0;
@@ -59,4 +60,233 @@
});
});
}
+
+ sunfish.openFile = function (sender) {
+ var item = getItem(sender);
+ var file = item.querySelector("div.item-name").innerText;
+ sunfish.ajax(document.location.href + file + "?action=open", {
+ ok: function (result) {
+ if (result != "OK")
+ sunfish.error("Error opening file in server", function () {
+ document.location.reload();
+ });
+ }
+ });
+ }
+
+ sunfish.newFolder = function (sender) {
+ sunfish.askString('Folder name:', "", function (name) {
+ if (!name)
+ return;
+ sunfish.ajax(document.location.href + "?action=createFolder&name=" + name, {
+ ok: function (result) {
+ if (result != "OK")
+ sunfish.error("Error creading folder", function () {
+ document.location.reload();
+ });
+ }
+ });
+ });
+ }
+
+ sunfish.newFile = function (sender) {
+ sunfish.askString('File name:', "", function (name) {
+ if (!name)
+ return;
+ sunfish.ajax(document.location.href + "?action=createFolder&name=" + name, {
+ ok: function (result) {
+ if (result != "OK")
+ sunfish.error("Error creading file", function () {
+ document.location.reload();
+ });
+ }
+ });
+ });
+ }
+
+ sunfish.uploadFile = function (sender) {
+ var efile = document.createElement("input");
+ efile.type = "file";
+ efile.onchange = () => {
+ var fileList = efile.files;
+ for (var i = 0; i < fileList.length; i++)
+ upl.upload(fileList[i]);
+ }
+ efile.click();
+ }
+
+ // UPLOAD task
+ var upl;
+ function Uploader() {
+ var pb, elist, uploads = [];
+ var edrop = sunfish.build({
+ $: "div", className: "popup-wall upload-drop", _: [
+ {
+ $: "div", className: "upload-drop-icon", _: [
+ { $: "i", className: "material-icons-round", _: ["upload"] }
+ ]
+ }
+ ]
+ });
+ var ctt = edrop.querySelector("div.upload-drop-icon");
+
+ function show() {
+ document.body.appendChild(edrop);
+ var cbutton;
+ ctt.innerHTML = "";
+ elist = sunfish.build({ $: "div", className: "upload-list" }, ctt);
+ elist.addItem = function (file, name, to) {
+ if (!pb.updater) {
+ pb.updater = function () {
+ var tpos = 0, tmax = 0;
+ uploads.forEach(u => {
+ tpos += u.pos;
+ tmax += u.length;
+ })
+ if (tmax == 0)
+ tmax = 100;
+ pb.style.width = (tpos / tmax * 100) + "%";
+ if (tpos < tmax) {
+ setTimeout(pb.updater, 100);
+ cbutton.style.display = "none";
+ } else {
+ delete pb.updater;
+ cbutton.style.display = "";
+ }
+ }
+ setTimeout(pb.updater, 100);
+ }
+ var item = sunfish.build({
+ $: "div", className: "upload-item", _: [{ $: "div", _: [to] },
+ { $: "div", className: "upload-item-progress-box", _: [{ $: "div", className: "upload-item-progress" }] }]
+ }, elist);
+ var itemProgress = item.querySelector(".upload-item-progress");
+ var me;
+ uploads.push(me = {
+ file: file,
+ name: name,
+ to: to,
+ progress: itemProgress,
+ length: file.size,
+ pos: 0,
+ eof: false
+ });
+ function updateProgress() {
+ if (me.length <= 0)
+ return;
+ var pos = me.pos;
+ if (pos > me.length)
+ pos = me.length;
+ itemProgress.style.width = (pos / me.length) * 100 + "%";
+
+ }
+ function ko(info) {
+ itemProgress.style.width = "150%";
+ itemProgress.style.backgroundColor = "tomato";
+ if (info)
+ console.error(info);
+ }
+ function step() {
+ var length = Math.min(1024 * 128, me.length - me.pos);
+ var blob = file.slice(me.pos, me.pos + length);
+ if (blob.length == 0)
+ return ko("Nothing to send");
+ sunfish.ajax(document.location.href + to, {
+ method: "PUT",
+ data: blob,
+ headers: {
+ "X-Sunfish-Offset": me.pos,
+ "X-Sunfish-Length": blob.size
+ },
+ ok: function (result) {
+ if (result != "OK")
+ ko("Upload is "+result);
+ else {
+ me.pos += blob.size;
+ updateProgress();
+ if (me.pos < me.length)
+ step();
+ else {
+ itemProgress.style.backgroundColor = "#46ad33";
+ me.eof = true;
+ }
+ }
+ },
+ ko: ko
+ });
+ }
+ step();
+ }
+ var pbb = sunfish.build({ $: "div", className: "upload-progress-box" }, ctt);
+ pb = sunfish.build({
+ $: "div", className: "upload-progress", _: [
+ {
+ $: "button", className: "upload-close", style: { display: "none" }, _: ["Close"], onclick: function () {
+ document.body.removeChild(edrop);
+ uploads = [];
+ }
+ }
+ ]
+ }, pbb);
+ cbutton = pb.querySelector(".upload-close");
+ }
+
+ document.body.addEventListener("drop", function (ev) {
+ function processWKItem(item, path) {
+ path = path || "";
+ if (item.isFile) {
+ item.file(function (file) {
+ upload(file, path + file.name);
+ });
+ } else if (item.isDirectory) {
+ // Get folder contents
+ var dirReader = item.createReader();
+ dirReader.readEntries(function (entries) {
+ for (var i = 0; i < entries.length; i++) {
+ processWKItem(entries[i], path + item.name + "/");
+ }
+ });
+ }
+ }
+ ev.preventDefault();
+ if (ev.dataTransfer.items) {
+ var items = ev.dataTransfer.items;
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ if (item.kind === 'file')
+ if (item.webkitGetAsEntry)
+ processWKItem(item.webkitGetAsEntry());
+ else
+ upload(item.getAsFile());
+ }
+ } else {
+ for (var i = 0; i < ev.dataTransfer.files.length; i++)
+ upload(ev.dataTransfer.files[i].getAsFile());
+ }
+ });
+
+ document.body.addEventListener("dragover", function (ev) {
+ ev.preventDefault();
+ document.body.appendChild(edrop);
+ });
+ document.body.addEventListener("dragleave", function (ev) {
+ ev.preventDefault();
+ //if (uploads.length == 0)
+ //document.body.removeChild(edrop); //Some strange behavior here Chrome thing?
+ });
+
+ function upload(file, to) {
+ if (!elist)
+ show();
+ to = to || file.name;
+ elist.addItem(file, file.name, to);
+ }
+
+ this.upload = upload;
+ }
+
+ window.addEventListener("load", function () {
+ upl = new Uploader();
+ });
+
})();
\ No newline at end of file
diff --git a/Sunfish/ShareWeb/$sunfish/sunfish.js b/Sunfish/ShareWeb/$sunfish/sunfish.js
index 87838fb..46684d5 100644
--- a/Sunfish/ShareWeb/$sunfish/sunfish.js
+++ b/Sunfish/ShareWeb/$sunfish/sunfish.js
@@ -27,8 +27,8 @@
delete def._;
for (var i in def) {
if (i == "style")
- for (var sn in def)
- el.style[sn] = def[sn];
+ for (var sn in def.style)
+ el.style[sn] = def.style[sn];
else
el[i] = def[i];
}
@@ -43,24 +43,48 @@
return el;
}
- function ask(question, bt1, bt2, bt3, bt4, bt5, iserror) {
+ function dialog(content, buttons, classes) {
+ var btdef = [];
+ for (var i in buttons) {
+ var bt = buttons[i];
+ if (bt.$)
+ btdef.push(bt);
+ else
+ btdef.push({
+ $: "button",
+ className: bt.class,
+ _: bt.b,
+ onclick: bt.do
+ });
+ }
+
var wall = build({ $: "div", className: "popup-wall" }, document.body);
var def = {
- $: "div", className: iserror ? "popup error" : "popup", _: [
- { $: "div", className: "body", _: [question] },
- { $: "div", className: "buttons", _: [] }
+ $: "div", className: "popup" + (classes ? " " + classes : ""), _: [
+ { $: "div", className: "body", _: content },
+ { $: "div", className: "buttons", _: btdef }
]
};
+ var dialog = build(def, wall);
+ dialog.close = function () {
+ document.body.removeChild(wall);
+ }
+ return dialog;
+ }
+
+ function ask(question, bt1, bt2, bt3, bt4, bt5, iserror) {
+ var buttons = [];
var el;
function addbt(bt) {
if (bt) {
if (bt.go)
bt.do = function () { document.location = bt.go };
- def._[1]._.push({
- $: "button", className: bt.class, _: bt.b,
- onclick: function () {
+ buttons.push({
+ class: bt.class,
+ b: bt.b,
+ do: function () {
bt.do && bt.do();
- document.body.removeChild(wall);
+ el.close();
}
});
}
@@ -70,7 +94,7 @@
addbt(bt3);
addbt(bt4);
addbt(bt5);
- return el = build(def, wall);
+ return el = dialog([question], buttons, iserror ? "error" : null);
}
function askString(text, def, cb) {
@@ -123,11 +147,30 @@
ctrl.ok && ctrl.ok(xhr.responseText, xhr);
};
xhr.open(ctrl.method || 'GET', url);
- xhr.send();
+ if (ctrl.headers)
+ for (var k in ctrl.headers)
+ xhr.setRequestHeader(k, ctrl.headers[k]);
+ if (ctrl.binary)
+ xhr.sendAsBinary(ctrl.binary, "binary/octet");
+ else
+ xhr.send(ctrl.data);
}
+ if (!XMLHttpRequest.prototype.sendAsBinary)
+ XMLHttpRequest.prototype.sendAsBinary = function (datastr, contentType) {
+ var bb = new BlobBuilder();
+ var len = datastr.length;
+ var data = new Uint8Array(len);
+ for (var i = 0; i < len; i++) {
+ data[i] = datastr.charCodeAt(i);
+ }
+ bb.append(data.buffer);
+ this.send(bb.getBlob(contentType));
+ }
+
this.init = init;
this.build = build;
+ this.dialog = dialog;
this.ask = ask;
this.askString = askString;
this.say = say;
diff --git a/Sunfish/Sunfish/Extensions.cs b/Sunfish/Sunfish/Extensions.cs
index ed3592b..f4662c0 100644
--- a/Sunfish/Sunfish/Extensions.cs
+++ b/Sunfish/Sunfish/Extensions.cs
@@ -12,10 +12,10 @@
MessageBox.Show(ex.Message, ex.GetType().Name, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
- public static void TransferFrom(this Stream s,Stream from)
+ public static void TransferFrom(this Stream s, Stream from)
{
- byte[] buf = new byte[10240];// 10Kb
- int readed=buf.Length;
+ byte[] buf = new byte[524288];// 512Kb
+ int readed = buf.Length;
while (readed == buf.Length)
{
readed = from.Read(buf, 0, buf.Length);
@@ -23,7 +23,21 @@
}
}
- public static T GetValue
(this Dictionary dict,K key, T def)
+ public static void TransferFrom(this Stream s, Stream from, long length)
+ {
+ byte[] buf = new byte[Math.Min(524288, length)];// 512Kb
+ while (length > 0)
+ {
+ int toRead = (int)Math.Min(buf.Length, length);
+ int readed = from.Read(buf, 0, toRead);
+ if (readed != toRead)
+ throw new IOException("Unexpected EOF");
+ s.Write(buf, 0, readed);
+ length -= readed;
+ }
+ }
+
+ public static T GetValue(this Dictionary dict, K key, T def)
{
T value;
if (dict.TryGetValue(key, out value))
diff --git a/Sunfish/Sunfish/Middleware/VFS.cs b/Sunfish/Sunfish/Middleware/VFS.cs
index ede52e4..ff5489b 100644
--- a/Sunfish/Sunfish/Middleware/VFS.cs
+++ b/Sunfish/Sunfish/Middleware/VFS.cs
@@ -71,6 +71,29 @@
return folder.GetItem(path);
}
+ public VFSItem Create(string path)
+ {
+ bool asDir = path.EndsWith("/");
+ if (asDir)
+ path = path.Substring(0, path.Length - 1);
+ int sep = path.LastIndexOf('/');
+ string inner = path.Substring(sep + 1);
+ path = path.Substring(0, sep);
+ VFSItem dir = GetItem(path);
+ while (dir == null)
+ {
+ sep = path.LastIndexOf('/');
+ if (sep < 0)
+ return null;
+ inner = path.Substring(sep + 1) + '/' + inner;
+ path = path.Substring(0, sep);
+ dir = GetItem(path);
+ }
+ if (!dir.Directory)
+ return null;
+ return dir.Create(inner, asDir);
+ }
+
public string[] ListFiles(string path)
{
VFSFolder folder = LocateFolder(ref path);
diff --git a/Sunfish/Sunfish/Middleware/VFSFolder.cs b/Sunfish/Sunfish/Middleware/VFSFolder.cs
index 89e3417..ae599d9 100644
--- a/Sunfish/Sunfish/Middleware/VFSFolder.cs
+++ b/Sunfish/Sunfish/Middleware/VFSFolder.cs
@@ -16,6 +16,7 @@
public abstract string[] ListDirectories(string path);
public abstract bool DeleteFile(string path);
public abstract bool DeleteFolder(string path);
- public abstract bool Rename(string from, string to);
+ public abstract bool Rename(string from, string to);
+ public abstract VFSItem Create(string path,bool asFolder);
}
}
diff --git a/Sunfish/Sunfish/Middleware/VFSFolderFileSystem.cs b/Sunfish/Sunfish/Middleware/VFSFolderFileSystem.cs
index b30297d..183e88e 100644
--- a/Sunfish/Sunfish/Middleware/VFSFolderFileSystem.cs
+++ b/Sunfish/Sunfish/Middleware/VFSFolderFileSystem.cs
@@ -33,7 +33,13 @@
public override Stream OpenWrite(string path)
{
- throw new NotImplementedException();
+ path = Path.Combine(basePath, path);
+ try
+ {
+ return File.OpenWrite(path);
+ }
+ catch { };
+ return null;
}
public override VFSItem GetItem(string path)
@@ -100,7 +106,7 @@
{
string ffrom = Path.Combine(basePath, from);
string realbase = Path.GetDirectoryName(ffrom);
- string tto= Path.Combine(realbase, to);
+ string tto = Path.Combine(realbase, to);
try
{
Directory.Move(ffrom, tto);
@@ -112,6 +118,21 @@
}
}
+ public override VFSItem Create(string path, bool asFolder)
+ {
+ string fspath = Path.Combine(basePath, path);
+ if (asFolder)
+ {
+ Directory.CreateDirectory(fspath);
+ }
+ else
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(fspath));
+ File.WriteAllBytes(fspath, new byte[0]);
+ }
+ return GetItem(path);
+ }
+
private void FSDeleteFolder(string path)
{
foreach (string d in Directory.GetDirectories(path))
@@ -125,5 +146,6 @@
{
return Path.Combine(basePath, path);
}
+
}
}
diff --git a/Sunfish/Sunfish/Middleware/VFSItem.cs b/Sunfish/Sunfish/Middleware/VFSItem.cs
index 6911dab..1d1f9eb 100644
--- a/Sunfish/Sunfish/Middleware/VFSItem.cs
+++ b/Sunfish/Sunfish/Middleware/VFSItem.cs
@@ -51,6 +51,11 @@
return Folder.Rename(Path, newName);
}
+ public VFSItem Create(string name, bool asFolder)
+ {
+ return Folder.Create(System.IO.Path.Combine(Path, name), asFolder);
+ }
+
public string Path { get; }
public string Name { get; }
public bool Directory { get; }
diff --git a/Sunfish/Sunfish/Program.cs b/Sunfish/Sunfish/Program.cs
index fa2d796..35b2bcf 100644
--- a/Sunfish/Sunfish/Program.cs
+++ b/Sunfish/Sunfish/Program.cs
@@ -7,7 +7,7 @@
{
static class Program
{
- public static string VERSION = "2.0b3";
+ public static string VERSION = "2.0b5";
private static Form1 mainform;
///
/// Punto de entrada principal para la aplicación.
diff --git a/Sunfish/Sunfish/Services/WebService.cs b/Sunfish/Sunfish/Services/WebService.cs
index b55cf8b..2daab56 100644
--- a/Sunfish/Sunfish/Services/WebService.cs
+++ b/Sunfish/Sunfish/Services/WebService.cs
@@ -63,6 +63,8 @@
action = null;
else if (string.IsNullOrEmpty(action))
action = null;
+ if (call.Request.HttpMethod == "PUT")
+ action = "upload";
switch (action)
{
case null:
@@ -77,6 +79,12 @@
case "rename":
ProcessRename(path, call);
break;
+ case "open":
+ ProcessOpen(path, call);
+ break;
+ case "upload":
+ ProcessUpload(path, call);
+ break;
default:
call.HTTPBadRequest();
break;
@@ -193,6 +201,60 @@
call.Write("KO");
}
+ private void ProcessOpen(string path, HttpCall call)
+ {
+ VFSItem fil = vfs.GetItem(path);
+ if (fil != null)
+ {
+ string fpath = ((VFSFolderFileSystem)fil.Folder).GetFSPath(fil.Path);
+ System.Diagnostics.Process.Start(fpath);
+ call.Write("OK");
+ }
+ else
+ call.Write("KO");
+ }
+
+ private void ProcessUpload(string path, HttpCall call)
+ {
+ string soffset = call.Request.Headers["X-Sunfish-Offset"];
+ string slength = call.Request.Headers["X-Sunfish-Length"];
+ if (string.IsNullOrEmpty(soffset))
+ soffset = call.Parameters["offset"];
+ if (string.IsNullOrEmpty(slength))
+ slength = call.Parameters["length"];
+ int pos, len;
+ if (string.IsNullOrEmpty(soffset) || string.IsNullOrEmpty(slength) || !int.TryParse(soffset, out pos) || !int.TryParse(slength, out len))
+ {
+ call.Write("KO: No offset or length");
+ return;
+ }
+ try
+ {
+ VFSItem fil = vfs.GetItem(path);
+ if (fil == null)
+ fil = vfs.Create(path);
+ if (fil.Directory)
+ {
+ call.Write("KO: Exists as directory");
+ return;
+ }
+ using (Stream s = fil.OpenWrite())
+ {
+ s.Position = pos;
+ using (Stream sin = call.Request.InputStream)
+ {
+ s.TransferFrom(sin, len);
+ }
+ }
+ call.Write("OK");
+ }
+ catch (Exception e)
+ {
+ call.Write("KO: " + e.GetType().Name + ":" + e.Message);
+ }
+ }
+
+
public void WriteIcon(Icon image, HttpCall call)
{
call.Response.ContentType = "image/vnd.microsoft.icon";
@@ -284,7 +346,27 @@
WebUI.InitResources();
Dictionary data = new Dictionary();
data["Breadcrumb"] = GetBreadcrumb(path);
- //data["Actions"] = actions;
+ data["Actions"] = new WebUILink[] {
+ readOnly?null:new WebUILink()
+ {
+ Icon="create_new_folder",
+ Tooltip="New folder",
+ Click="sunfish.newFolder(this)",
+ },
+ readOnly?null:new WebUILink()
+ {
+ Icon="note_add",
+ Tooltip="New file",
+ Click="sunfish.newFile(this)",
+ },
+ readOnly?null:new WebUILink()
+ {
+ Icon="upload",
+ Tooltip="Upload. Drop files or fonders here",
+ Click="sunfish.uploadFile(this)",
+ //Style="upload-drop",
+ }
+ };
data["Items"] = items;
data["Include"] = "";
WebUI.WriteTemplate("directory-index", call, data);