Add and remove torrents from web interface (#84)

This commit is contained in:
Antonio Navarro Perez 2021-11-16 13:13:58 +01:00 committed by GitHub
parent 02842b1917
commit 2f18213660
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 996 additions and 1170 deletions

View file

@ -77,7 +77,6 @@ Handlebars.registerHelper("torrent_info", function (peers, seeders, pieceSize) {
return div.outerHTML;
});
Distribyted.routes = {
_template: null,
@ -114,45 +113,6 @@ Distribyted.routes = {
Distribyted.message.error('Error getting data from server. Response: ' + response.status)
}
}).then(function (routes) {
// routes.forEach(route => {
// route.torrentStats.forEach(torrentStat => {
// fileChunks.update(torrentStat.pieceChunks, torrentStat.totalPieces, torrentStat.hash);
// var download = torrentStat.downloadedBytes / torrentStat.timePassed;
// var upload = torrentStat.uploadedBytes / torrentStat.timePassed;
// var seeders = torrentStat.seeders;
// var peers = torrentStat.peers;
// var pieceSize = torrentStat.pieceSize;
// document.getElementById("up-down-speed-text-" + torrentStat.hash).innerText =
// Humanize.ibytes(download, 1024) + "/s down, " + Humanize.ibytes(upload, 1024) + "/s up";
// document.getElementById("peers-seeders-" + torrentStat.hash).innerText =
// peers + " peers, " + seeders + " seeders."
// document.getElementById("piece-size-" + torrentStat.hash).innerText = "Piece size: " + Humanize.bytes(pieceSize, 1024)
// var className = "";
// if (seeders < 2) {
// className = "text-danger";
// } else if (seeders >= 2 && seeders < 4) {
// className = "text-warning";
// } else {
// className = "text-success";
// }
// document.getElementById("peers-seeders-" + torrentStat.hash).className = className;
// if (pieceSize <= MB) {
// className = "text-success";
// } else if (pieceSize > MB && pieceSize < (MB * 4)) {
// className = "text-warning";
// } else {
// className = "text-danger";
// }
// document.getElementById("piece-size-" + torrentStat.hash).className = className;
// });
// });
return routes;
})
.catch(function (error) {
@ -160,6 +120,26 @@ Distribyted.routes = {
});
},
deleteTorrent: function (route, torrentHash) {
var url = '/api/routes/' + route + '/torrent/' + torrentHash
return fetch(url, {
method: 'DELETE'
})
.then(function (response) {
if (response.ok) {
Distribyted.message.info('Torrent deleted.')
} else {
response.json().then(json => {
Distribyted.message.error('Error deletting torrent. Response: ' + json.error)
})
}
})
.catch(function (error) {
Distribyted.message.error('Error deletting torrent: ' + error.message)
});
},
loadView: function () {
this._getTemplate()
.then(t =>
@ -169,3 +149,33 @@ Distribyted.routes = {
);
}
}
$("#new-magnet").submit(function (event) {
event.preventDefault();
let route = $("#route-string :selected").val()
let magnet = $("#magnet-url").val()
let url = '/api/routes/' + route + '/torrent'
let body = JSON.stringify({ magnet: magnet })
console.log("LOG", url, body)
fetch(url, {
method: 'POST',
body: body
})
.then(function (response) {
if (response.ok) {
Distribyted.message.info('New magnet added.')
} else {
response.json().then(json => {
Distribyted.message.error('Error adding new magnet. Response: ' + json.error)
}).catch(function (error) {
Distribyted.message.error('Error adding new magnet: ' + response.status)
});
}
})
.catch(function (error) {
Distribyted.message.error('Error deletting torrent: ' + error.message)
});
});

File diff suppressed because one or more lines are too long

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/elastic_tabstops_lite",["require","exports","module","ace/editor","ace/config"],function(e,t,n){"use strict";var r=function(e){this.$editor=e;var t=this,n=[],r=!1;this.onAfterExec=function(){r=!1,t.processRows(n),n=[]},this.onExec=function(){r=!0},this.onChange=function(e){r&&(n.indexOf(e.start.row)==-1&&n.push(e.start.row),e.end.row!=e.start.row&&n.push(e.end.row))}};(function(){this.processRows=function(e){this.$inChange=!0;var t=[];for(var n=0,r=e.length;n<r;n++){var i=e[n];if(t.indexOf(i)>-1)continue;var s=this.$findCellWidthsForBlock(i),o=this.$setBlockCellWidthsToMax(s.cellWidths),u=s.firstRow;for(var a=0,f=o.length;a<f;a++){var l=o[a];t.push(u),this.$adjustRow(u,l),u++}}this.$inChange=!1},this.$findCellWidthsForBlock=function(e){var t=[],n,r=e;while(r>=0){n=this.$cellWidthsForRow(r);if(n.length==0)break;t.unshift(n),r--}var i=r+1;r=e;var s=this.$editor.session.getLength();while(r<s-1){r++,n=this.$cellWidthsForRow(r);if(n.length==0)break;t.push(n)}return{cellWidths:t,firstRow:i}},this.$cellWidthsForRow=function(e){var t=this.$selectionColumnsForRow(e),n=[-1].concat(this.$tabsForRow(e)),r=n.map(function(e){return 0}).slice(1),i=this.$editor.session.getLine(e);for(var s=0,o=n.length-1;s<o;s++){var u=n[s]+1,a=n[s+1],f=this.$rightmostSelectionInCell(t,a),l=i.substring(u,a);r[s]=Math.max(l.replace(/\s+$/g,"").length,f-u)}return r},this.$selectionColumnsForRow=function(e){var t=[],n=this.$editor.getCursorPosition();return this.$editor.session.getSelection().isEmpty()&&e==n.row&&t.push(n.column),t},this.$setBlockCellWidthsToMax=function(e){var t=!0,n,r,i,s=this.$izip_longest(e);for(var o=0,u=s.length;o<u;o++){var a=s[o];if(!a.push){console.error(a);continue}a.push(NaN);for(var f=0,l=a.length;f<l;f++){var c=a[f];t&&(n=f,i=0,t=!1);if(isNaN(c)){r=f;for(var h=n;h<r;h++)e[h][o]=i;t=!0}i=Math.max(i,c)}}return e},this.$rightmostSelectionInCell=function(e,t){var n=0;if(e.length){var r=[];for(var i=0,s=e.length;i<s;i++)e[i]<=t?r.push(i):r.push(0);n=Math.max.apply(Math,r)}return n},this.$tabsForRow=function(e){var t=[],n=this.$editor.session.getLine(e),r=/\t/g,i;while((i=r.exec(n))!=null)t.push(i.index);return t},this.$adjustRow=function(e,t){var n=this.$tabsForRow(e);if(n.length==0)return;var r=0,i=-1,s=this.$izip(t,n);for(var o=0,u=s.length;o<u;o++){var a=s[o][0],f=s[o][1];i+=1+a,f+=r;var l=i-f;if(l==0)continue;var c=this.$editor.session.getLine(e).substr(0,f),h=c.replace(/\s*$/g,""),p=c.length-h.length;l>0&&(this.$editor.session.getDocument().insertInLine({row:e,column:f+1},Array(l+1).join(" ")+" "),this.$editor.session.getDocument().removeInLine(e,f,f+1),r+=l),l<0&&p>=-l&&(this.$editor.session.getDocument().removeInLine(e,f+l,f),r+=l)}},this.$izip_longest=function(e){if(!e[0])return[];var t=e[0].length,n=e.length;for(var r=1;r<n;r++){var i=e[r].length;i>t&&(t=i)}var s=[];for(var o=0;o<t;o++){var u=[];for(var r=0;r<n;r++)e[r][o]===""?u.push(NaN):u.push(e[r][o]);s.push(u)}return s},this.$izip=function(e,t){var n=e.length>=t.length?t.length:e.length,r=[];for(var i=0;i<n;i++){var s=[e[i],t[i]];r.push(s)}return r}}).call(r.prototype),t.ElasticTabstopsLite=r;var i=e("../editor").Editor;e("../config").defineOptions(i.prototype,"editor",{useElasticTabstops:{set:function(e){e?(this.elasticTabstops||(this.elasticTabstops=new r(this)),this.commands.on("afterExec",this.elasticTabstops.onAfterExec),this.commands.on("exec",this.elasticTabstops.onExec),this.on("change",this.elasticTabstops.onChange)):this.elasticTabstops&&(this.commands.removeListener("afterExec",this.elasticTabstops.onAfterExec),this.commands.removeListener("exec",this.elasticTabstops.onExec),this.removeListener("change",this.elasticTabstops.onChange))}}})}); (function() {
ace.require(["ace/ext/elastic_tabstops_lite"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View file

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/ext/error_marker"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/menu_tools/overlay_page",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../../lib/dom"),i="#ace_settingsmenu, #kbshortcutmenu {background-color: #F7F7F7;color: black;box-shadow: -5px 4px 5px rgba(126, 126, 126, 0.55);padding: 1em 0.5em 2em 1em;overflow: auto;position: absolute;margin: 0;bottom: 0;right: 0;top: 0;z-index: 9991;cursor: default;}.ace_dark #ace_settingsmenu, .ace_dark #kbshortcutmenu {box-shadow: -20px 10px 25px rgba(126, 126, 126, 0.25);background-color: rgba(255, 255, 255, 0.6);color: black;}.ace_optionsMenuEntry:hover {background-color: rgba(100, 100, 100, 0.1);transition: all 0.3s}.ace_closeButton {background: rgba(245, 146, 146, 0.5);border: 1px solid #F48A8A;border-radius: 50%;padding: 7px;position: absolute;right: -8px;top: -8px;z-index: 100000;}.ace_closeButton{background: rgba(245, 146, 146, 0.9);}.ace_optionsMenuKey {color: darkslateblue;font-weight: bold;}.ace_optionsMenuCommand {color: darkcyan;font-weight: normal;}.ace_optionsMenuEntry input, .ace_optionsMenuEntry button {vertical-align: middle;}.ace_optionsMenuEntry button[ace_selected_button=true] {background: #e7e7e7;box-shadow: 1px 0px 2px 0px #adadad inset;border-color: #adadad;}.ace_optionsMenuEntry button {background: white;border: 1px solid lightgray;margin: 0px;}.ace_optionsMenuEntry button:hover{background: #f0f0f0;}";r.importCssString(i),n.exports.overlayPage=function(t,n,r){function o(e){e.keyCode===27&&u()}function u(){if(!i)return;document.removeEventListener("keydown",o),i.parentNode.removeChild(i),t&&t.focus(),i=null,r&&r()}function a(e){s=e,e&&(i.style.pointerEvents="none",n.style.pointerEvents="auto")}var i=document.createElement("div"),s=!1;return i.style.cssText="margin: 0; padding: 0; position: fixed; top:0; bottom:0; left:0; right:0;z-index: 9990; "+(t?"background-color: rgba(0, 0, 0, 0.3);":""),i.addEventListener("click",function(e){s||u()}),document.addEventListener("keydown",o),n.addEventListener("click",function(e){e.stopPropagation()}),i.appendChild(n),document.body.appendChild(i),t&&t.blur(),{close:u,setIgnoreFocusOut:a}}}),ace.define("ace/ext/menu_tools/get_editor_keyboard_shortcuts",["require","exports","module","ace/lib/keys"],function(e,t,n){"use strict";var r=e("../../lib/keys");n.exports.getEditorKeybordShortcuts=function(e){var t=r.KEY_MODS,n=[],i={};return e.keyBinding.$handlers.forEach(function(e){var t=e.commandKeyBinding;for(var r in t){var s=r.replace(/(^|-)\w/g,function(e){return e.toUpperCase()}),o=t[r];Array.isArray(o)||(o=[o]),o.forEach(function(e){typeof e!="string"&&(e=e.name),i[e]?i[e].key+="|"+s:(i[e]={key:s,command:e},n.push(i[e]))})}}),n}}),ace.define("ace/ext/keybinding_menu",["require","exports","module","ace/editor","ace/ext/menu_tools/overlay_page","ace/ext/menu_tools/get_editor_keyboard_shortcuts"],function(e,t,n){"use strict";function i(t){if(!document.getElementById("kbshortcutmenu")){var n=e("./menu_tools/overlay_page").overlayPage,r=e("./menu_tools/get_editor_keyboard_shortcuts").getEditorKeybordShortcuts,i=r(t),s=document.createElement("div"),o=i.reduce(function(e,t){return e+'<div class="ace_optionsMenuEntry"><span class="ace_optionsMenuCommand">'+t.command+"</span> : "+'<span class="ace_optionsMenuKey">'+t.key+"</span></div>"},"");s.id="kbshortcutmenu",s.innerHTML="<h1>Keyboard Shortcuts</h1>"+o+"</div>",n(t,s)}}var r=e("../editor").Editor;n.exports.init=function(e){r.prototype.showKeyboardShortcuts=function(){i(this)},e.commands.addCommands([{name:"showKeyboardShortcuts",bindKey:{win:"Ctrl-Alt-h",mac:"Command-Alt-h"},exec:function(e,t){e.showKeyboardShortcuts()}}])}}); (function() {
ace.require(["ace/ext/keybinding_menu"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/linking",["require","exports","module","ace/editor","ace/config"],function(e,t,n){function i(e){var n=e.editor,r=e.getAccelKey();if(r){var n=e.editor,i=e.getDocumentPosition(),s=n.session,o=s.getTokenAt(i.row,i.column);t.previousLinkingHover&&t.previousLinkingHover!=o&&n._emit("linkHoverOut"),n._emit("linkHover",{position:i,token:o}),t.previousLinkingHover=o}else t.previousLinkingHover&&(n._emit("linkHoverOut"),t.previousLinkingHover=!1)}function s(e){var t=e.getAccelKey(),n=e.getButton();if(n==0&&t){var r=e.editor,i=e.getDocumentPosition(),s=r.session,o=s.getTokenAt(i.row,i.column);r._emit("linkClick",{position:i,token:o})}}var r=e("../editor").Editor;e("../config").defineOptions(r.prototype,"editor",{enableLinking:{set:function(e){e?(this.on("click",s),this.on("mousemove",i)):(this.off("click",s),this.off("mousemove",i))},value:!1}}),t.previousLinkingHover=!1}); (function() {
ace.require(["ace/ext/linking"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/modelist",["require","exports","module"],function(e,t,n){"use strict";function i(e){var t=a.text,n=e.split(/[\/\\]/).pop();for(var i=0;i<r.length;i++)if(r[i].supportsFile(n)){t=r[i];break}return t}var r=[],s=function(e,t,n){this.name=e,this.caption=t,this.mode="ace/mode/"+e,this.extensions=n;var r;/\^/.test(n)?r=n.replace(/\|(\^)?/g,function(e,t){return"$|"+(t?"^":"^.*\\.")})+"$":r="^.*\\.("+n+")$",this.extRe=new RegExp(r,"gi")};s.prototype.supportsFile=function(e){return e.match(this.extRe)};var o={ABAP:["abap"],ABC:["abc"],ActionScript:["as"],ADA:["ada|adb"],Alda:["alda"],Apache_Conf:["^htaccess|^htgroups|^htpasswd|^conf|htaccess|htgroups|htpasswd"],Apex:["apex|cls|trigger|tgr"],AQL:["aql"],AsciiDoc:["asciidoc|adoc"],ASL:["dsl|asl"],Assembly_x86:["asm|a"],AutoHotKey:["ahk"],BatchFile:["bat|cmd"],C_Cpp:["cpp|c|cc|cxx|h|hh|hpp|ino"],C9Search:["c9search_results"],Cirru:["cirru|cr"],Clojure:["clj|cljs"],Cobol:["CBL|COB"],coffee:["coffee|cf|cson|^Cakefile"],ColdFusion:["cfm"],Crystal:["cr"],CSharp:["cs"],Csound_Document:["csd"],Csound_Orchestra:["orc"],Csound_Score:["sco"],CSS:["css"],Curly:["curly"],D:["d|di"],Dart:["dart"],Diff:["diff|patch"],Dockerfile:["^Dockerfile"],Dot:["dot"],Drools:["drl"],Edifact:["edi"],Eiffel:["e|ge"],EJS:["ejs"],Elixir:["ex|exs"],Elm:["elm"],Erlang:["erl|hrl"],Forth:["frt|fs|ldr|fth|4th"],Fortran:["f|f90"],FSharp:["fsi|fs|ml|mli|fsx|fsscript"],FSL:["fsl"],FTL:["ftl"],Gcode:["gcode"],Gherkin:["feature"],Gitignore:["^.gitignore"],Glsl:["glsl|frag|vert"],Gobstones:["gbs"],golang:["go"],GraphQLSchema:["gql"],Groovy:["groovy"],HAML:["haml"],Handlebars:["hbs|handlebars|tpl|mustache"],Haskell:["hs"],Haskell_Cabal:["cabal"],haXe:["hx"],Hjson:["hjson"],HTML:["html|htm|xhtml|vue|we|wpy"],HTML_Elixir:["eex|html.eex"],HTML_Ruby:["erb|rhtml|html.erb"],INI:["ini|conf|cfg|prefs"],Io:["io"],Jack:["jack"],Jade:["jade|pug"],Java:["java"],JavaScript:["js|jsm|jsx"],JSON:["json"],JSON5:["json5"],JSONiq:["jq"],JSP:["jsp"],JSSM:["jssm|jssm_state"],JSX:["jsx"],Julia:["jl"],Kotlin:["kt|kts"],LaTeX:["tex|latex|ltx|bib"],LESS:["less"],Liquid:["liquid"],Lisp:["lisp"],LiveScript:["ls"],LogiQL:["logic|lql"],LSL:["lsl"],Lua:["lua"],LuaPage:["lp"],Lucene:["lucene"],Makefile:["^Makefile|^GNUmakefile|^makefile|^OCamlMakefile|make"],Markdown:["md|markdown"],Mask:["mask"],MATLAB:["matlab"],Maze:["mz"],MediaWiki:["wiki|mediawiki"],MEL:["mel"],MIXAL:["mixal"],MUSHCode:["mc|mush"],MySQL:["mysql"],Nginx:["nginx|conf"],Nim:["nim"],Nix:["nix"],NSIS:["nsi|nsh"],Nunjucks:["nunjucks|nunjs|nj|njk"],ObjectiveC:["m|mm"],OCaml:["ml|mli"],Pascal:["pas|p"],Perl:["pl|pm"],Perl6:["p6|pl6|pm6"],pgSQL:["pgsql"],PHP:["php|inc|phtml|shtml|php3|php4|php5|phps|phpt|aw|ctp|module"],PHP_Laravel_blade:["blade.php"],Pig:["pig"],Powershell:["ps1"],Praat:["praat|praatscript|psc|proc"],Prisma:["prisma"],Prolog:["plg|prolog"],Properties:["properties"],Protobuf:["proto"],Puppet:["epp|pp"],Python:["py"],QML:["qml"],R:["r"],Razor:["cshtml|asp"],RDoc:["Rd"],Red:["red|reds"],RHTML:["Rhtml"],RST:["rst"],Ruby:["rb|ru|gemspec|rake|^Guardfile|^Rakefile|^Gemfile"],Rust:["rs"],SASS:["sass"],SCAD:["scad"],Scala:["scala|sbt"],Scheme:["scm|sm|rkt|oak|scheme"],SCSS:["scss"],SH:["sh|bash|^.bashrc"],SJS:["sjs"],Slim:["slim|skim"],Smarty:["smarty|tpl"],snippets:["snippets"],Soy_Template:["soy"],Space:["space"],SQL:["sql"],SQLServer:["sqlserver"],Stylus:["styl|stylus"],SVG:["svg"],Swift:["swift"],Tcl:["tcl"],Terraform:["tf","tfvars","terragrunt"],Tex:["tex"],Text:["txt"],Textile:["textile"],Toml:["toml"],TSX:["tsx"],Twig:["latte|twig|swig"],Typescript:["ts|typescript|str"],Vala:["vala"],VBScript:["vbs|vb"],Velocity:["vm"],Verilog:["v|vh|sv|svh"],VHDL:["vhd|vhdl"],Visualforce:["vfp|component|page"],Wollok:["wlk|wpgm|wtest"],XML:["xml|rdf|rss|wsdl|xslt|atom|mathml|mml|xul|xbl|xaml"],XQuery:["xq"],YAML:["yaml|yml"],Zeek:["zeek|bro"],Django:["html"]},u={ObjectiveC:"Objective-C",CSharp:"C#",golang:"Go",C_Cpp:"C and C++",Csound_Document:"Csound Document",Csound_Orchestra:"Csound",Csound_Score:"Csound Score",coffee:"CoffeeScript",HTML_Ruby:"HTML (Ruby)",HTML_Elixir:"HTML (Elixir)",FTL:"FreeMarker",PHP_Laravel_blade:"PHP (Blade Template)",Perl6:"Perl 6",AutoHotKey:"AutoHotkey / AutoIt"},a={};for(var f in o){var l=o[f],c=(u[f]||f).replace(/_/g," "),h=f.toLowerCase(),p=new s(h,c,l[0]);a[h]=p,r.push(p)}n.exports={getModeForPath:i,modes:r,modesByName:a}}); (function() {
ace.require(["ace/ext/modelist"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/spellcheck",["require","exports","module","ace/lib/event","ace/editor","ace/config"],function(e,t,n){"use strict";var r=e("../lib/event");t.contextMenuHandler=function(e){var t=e.target,n=t.textInput.getElement();if(!t.selection.isEmpty())return;var i=t.getCursorPosition(),s=t.session.getWordRange(i.row,i.column),o=t.session.getTextRange(s);t.session.tokenRe.lastIndex=0;if(!t.session.tokenRe.test(o))return;var u="\x01\x01",a=o+" "+u;n.value=a,n.setSelectionRange(o.length,o.length+1),n.setSelectionRange(0,0),n.setSelectionRange(0,o.length);var f=!1;r.addListener(n,"keydown",function l(){r.removeListener(n,"keydown",l),f=!0}),t.textInput.setInputHandler(function(e){if(e==a)return"";if(e.lastIndexOf(a,0)===0)return e.slice(a.length);if(e.substr(n.selectionEnd)==a)return e.slice(0,-a.length);if(e.slice(-2)==u){var r=e.slice(0,-2);if(r.slice(-1)==" ")return f?r.substring(0,n.selectionEnd):(r=r.slice(0,-1),t.session.replace(s,r),"")}return e})};var i=e("../editor").Editor;e("../config").defineOptions(i.prototype,"editor",{spellcheck:{set:function(e){var n=this.textInput.getElement();n.spellcheck=!!e,e?this.on("nativecontextmenu",t.contextMenuHandler):this.removeListener("nativecontextmenu",t.contextMenuHandler)},value:!0}})}); (function() {
ace.require(["ace/ext/spellcheck"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View file

@ -1,8 +0,0 @@
ace.define("ace/split",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/lib/event_emitter","ace/editor","ace/virtual_renderer","ace/edit_session"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/lang"),s=e("./lib/event_emitter").EventEmitter,o=e("./editor").Editor,u=e("./virtual_renderer").VirtualRenderer,a=e("./edit_session").EditSession,f=function(e,t,n){this.BELOW=1,this.BESIDE=0,this.$container=e,this.$theme=t,this.$splits=0,this.$editorCSS="",this.$editors=[],this.$orientation=this.BESIDE,this.setSplits(n||1),this.$cEditor=this.$editors[0],this.on("focus",function(e){this.$cEditor=e}.bind(this))};(function(){r.implement(this,s),this.$createEditor=function(){var e=document.createElement("div");e.className=this.$editorCSS,e.style.cssText="position: absolute; top:0px; bottom:0px",this.$container.appendChild(e);var t=new o(new u(e,this.$theme));return t.on("focus",function(){this._emit("focus",t)}.bind(this)),this.$editors.push(t),t.setFontSize(this.$fontSize),t},this.setSplits=function(e){var t;if(e<1)throw"The number of splits have to be > 0!";if(e==this.$splits)return;if(e>this.$splits){while(this.$splits<this.$editors.length&&this.$splits<e)t=this.$editors[this.$splits],this.$container.appendChild(t.container),t.setFontSize(this.$fontSize),this.$splits++;while(this.$splits<e)this.$createEditor(),this.$splits++}else while(this.$splits>e)t=this.$editors[this.$splits-1],this.$container.removeChild(t.container),this.$splits--;this.resize()},this.getSplits=function(){return this.$splits},this.getEditor=function(e){return this.$editors[e]},this.getCurrentEditor=function(){return this.$cEditor},this.focus=function(){this.$cEditor.focus()},this.blur=function(){this.$cEditor.blur()},this.setTheme=function(e){this.$editors.forEach(function(t){t.setTheme(e)})},this.setKeyboardHandler=function(e){this.$editors.forEach(function(t){t.setKeyboardHandler(e)})},this.forEach=function(e,t){this.$editors.forEach(e,t)},this.$fontSize="",this.setFontSize=function(e){this.$fontSize=e,this.forEach(function(t){t.setFontSize(e)})},this.$cloneSession=function(e){var t=new a(e.getDocument(),e.getMode()),n=e.getUndoManager();return t.setUndoManager(n),t.setTabSize(e.getTabSize()),t.setUseSoftTabs(e.getUseSoftTabs()),t.setOverwrite(e.getOverwrite()),t.setBreakpoints(e.getBreakpoints()),t.setUseWrapMode(e.getUseWrapMode()),t.setUseWorker(e.getUseWorker()),t.setWrapLimitRange(e.$wrapLimitRange.min,e.$wrapLimitRange.max),t.$foldData=e.$cloneFoldData(),t},this.setSession=function(e,t){var n;t==null?n=this.$cEditor:n=this.$editors[t];var r=this.$editors.some(function(t){return t.session===e});return r&&(e=this.$cloneSession(e)),n.setSession(e),e},this.getOrientation=function(){return this.$orientation},this.setOrientation=function(e){if(this.$orientation==e)return;this.$orientation=e,this.resize()},this.resize=function(){var e=this.$container.clientWidth,t=this.$container.clientHeight,n;if(this.$orientation==this.BESIDE){var r=e/this.$splits;for(var i=0;i<this.$splits;i++)n=this.$editors[i],n.container.style.width=r+"px",n.container.style.top="0px",n.container.style.left=i*r+"px",n.container.style.height=t+"px",n.resize()}else{var s=t/this.$splits;for(var i=0;i<this.$splits;i++)n=this.$editors[i],n.container.style.width=e+"px",n.container.style.top=i*s+"px",n.container.style.left="0px",n.container.style.height=s+"px",n.resize()}}}).call(f.prototype),t.Split=f}),ace.define("ace/ext/split",["require","exports","module","ace/split"],function(e,t,n){"use strict";n.exports=e("../split")}); (function() {
ace.require(["ace/ext/split"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/static_highlight",["require","exports","module","ace/edit_session","ace/layer/text","ace/config","ace/lib/dom","ace/lib/lang"],function(e,t,n){"use strict";function f(e){this.type=e,this.style={},this.textContent=""}var r=e("../edit_session").EditSession,i=e("../layer/text").Text,s=".ace_static_highlight {font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Droid Sans Mono', monospace;font-size: 12px;white-space: pre-wrap}.ace_static_highlight .ace_gutter {width: 2em;text-align: right;padding: 0 3px 0 0;margin-right: 3px;contain: none;}.ace_static_highlight.ace_show_gutter .ace_line {padding-left: 2.6em;}.ace_static_highlight .ace_line { position: relative; }.ace_static_highlight .ace_gutter-cell {-moz-user-select: -moz-none;-khtml-user-select: none;-webkit-user-select: none;user-select: none;top: 0;bottom: 0;left: 0;position: absolute;}.ace_static_highlight .ace_gutter-cell:before {content: counter(ace_line, decimal);counter-increment: ace_line;}.ace_static_highlight {counter-reset: ace_line;}",o=e("../config"),u=e("../lib/dom"),a=e("../lib/lang").escapeHTML;f.prototype.cloneNode=function(){return this},f.prototype.appendChild=function(e){this.textContent+=e.toString()},f.prototype.toString=function(){var e=[];if(this.type!="fragment"){e.push("<",this.type),this.className&&e.push(" class='",this.className,"'");var t=[];for(var n in this.style)t.push(n,":",this.style[n]);t.length&&e.push(" style='",t.join(""),"'"),e.push(">")}return this.textContent&&e.push(this.textContent),this.type!="fragment"&&e.push("</",this.type,">"),e.join("")};var l={createTextNode:function(e,t){return a(e)},createElement:function(e){return new f(e)},createFragment:function(){return new f("fragment")}},c=function(){this.config={},this.dom=l};c.prototype=i.prototype;var h=function(e,t,n){var r=e.className.match(/lang-(\w+)/),i=t.mode||r&&"ace/mode/"+r[1];if(!i)return!1;var s=t.theme||"ace/theme/textmate",o="",a=[];if(e.firstElementChild){var f=0;for(var l=0;l<e.childNodes.length;l++){var c=e.childNodes[l];c.nodeType==3?(f+=c.data.length,o+=c.data):a.push(f,c)}}else o=e.textContent,t.trim&&(o=o.trim());h.render(o,i,s,t.firstLineNumber,!t.showGutter,function(t){u.importCssString(t.css,"ace_highlight"),e.innerHTML=t.html;var r=e.firstChild.firstChild;for(var i=0;i<a.length;i+=2){var s=t.session.doc.indexToPosition(a[i]),o=a[i+1],f=r.children[s.row];f&&f.appendChild(o)}n&&n()})};h.render=function(e,t,n,i,s,u){function c(){var r=h.renderSync(e,t,n,i,s);return u?u(r):r}var a=1,f=r.prototype.$modes;typeof n=="string"&&(a++,o.loadModule(["theme",n],function(e){n=e,--a||c()}));var l;return t&&typeof t=="object"&&!t.getTokenizer&&(l=t,t=l.path),typeof t=="string"&&(a++,o.loadModule(["mode",t],function(e){if(!f[t]||l)f[t]=new e.Mode(l);t=f[t],--a||c()})),--a||c()},h.renderSync=function(e,t,n,i,o){i=parseInt(i||1,10);var u=new r("");u.setUseWorker(!1),u.setMode(t);var a=new c;a.setSession(u),Object.keys(a.$tabStrings).forEach(function(e){if(typeof a.$tabStrings[e]=="string"){var t=l.createFragment();t.textContent=a.$tabStrings[e],a.$tabStrings[e]=t}}),u.setValue(e);var f=u.getLength(),h=l.createElement("div");h.className=n.cssClass;var p=l.createElement("div");p.className="ace_static_highlight"+(o?"":" ace_show_gutter"),p.style["counter-reset"]="ace_line "+(i-1);for(var d=0;d<f;d++){var v=l.createElement("div");v.className="ace_line";if(!o){var m=l.createElement("span");m.className="ace_gutter ace_gutter-cell",m.textContent="",v.appendChild(m)}a.$renderLine(v,d,!1),v.textContent+="\n",p.appendChild(v)}return h.appendChild(p),{css:s+n.cssText,html:h.toString(),session:u}},n.exports=h,n.exports.highlight=h}); (function() {
ace.require(["ace/ext/static_highlight"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/statusbar",["require","exports","module","ace/lib/dom","ace/lib/lang"],function(e,t,n){"use strict";var r=e("../lib/dom"),i=e("../lib/lang"),s=function(e,t){this.element=r.createElement("div"),this.element.className="ace_status-indicator",this.element.style.cssText="display: inline-block;",t.appendChild(this.element);var n=i.delayedCall(function(){this.updateStatus(e)}.bind(this)).schedule.bind(null,100);e.on("changeStatus",n),e.on("changeSelection",n),e.on("keyboardActivity",n)};(function(){this.updateStatus=function(e){function n(e,n){e&&t.push(e,n||"|")}var t=[];n(e.keyBinding.getStatusText(e)),e.commands.recording&&n("REC");var r=e.selection,i=r.lead;if(!r.isEmpty()){var s=e.getSelectionRange();n("("+(s.end.row-s.start.row)+":"+(s.end.column-s.start.column)+")"," ")}n(i.row+":"+i.column," "),r.rangeCount&&n("["+r.rangeCount+"]"," "),t.pop(),this.element.textContent=t.join("")}}).call(s.prototype),t.StatusBar=s}); (function() {
ace.require(["ace/ext/statusbar"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/themelist",["require","exports","module"],function(e,t,n){"use strict";var r=[["Chrome"],["Clouds"],["Crimson Editor"],["Dawn"],["Dreamweaver"],["Eclipse"],["GitHub"],["IPlastic"],["Solarized Light"],["TextMate"],["Tomorrow"],["Xcode"],["Kuroir"],["KatzenMilch"],["SQL Server","sqlserver","light"],["Ambiance","ambiance","dark"],["Chaos","chaos","dark"],["Clouds Midnight","clouds_midnight","dark"],["Dracula","","dark"],["Cobalt","cobalt","dark"],["Gruvbox","gruvbox","dark"],["Green on Black","gob","dark"],["idle Fingers","idle_fingers","dark"],["krTheme","kr_theme","dark"],["Merbivore","merbivore","dark"],["Merbivore Soft","merbivore_soft","dark"],["Mono Industrial","mono_industrial","dark"],["Monokai","monokai","dark"],["Nord Dark","nord_dark","dark"],["Pastel on dark","pastel_on_dark","dark"],["Solarized Dark","solarized_dark","dark"],["Terminal","terminal","dark"],["Tomorrow Night","tomorrow_night","dark"],["Tomorrow Night Blue","tomorrow_night_blue","dark"],["Tomorrow Night Bright","tomorrow_night_bright","dark"],["Tomorrow Night 80s","tomorrow_night_eighties","dark"],["Twilight","twilight","dark"],["Vibrant Ink","vibrant_ink","dark"]];t.themesByName={},t.themes=r.map(function(e){var n=e[1]||e[0].replace(/ /g,"_").toLowerCase(),r={caption:e[0],theme:"ace/theme/"+n,isDark:e[2]=="dark",name:n};return t.themesByName[n]=r,r})}); (function() {
ace.require(["ace/ext/themelist"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View file

@ -1,8 +0,0 @@
ace.define("ace/ext/whitespace",["require","exports","module","ace/lib/lang"],function(e,t,n){"use strict";var r=e("../lib/lang");t.$detectIndentation=function(e,t){function c(e){var t=0;for(var r=e;r<n.length;r+=e)t+=n[r]||0;return t}var n=[],r=[],i=0,s=0,o=Math.min(e.length,1e3);for(var u=0;u<o;u++){var a=e[u];if(!/^\s*[^*+\-\s]/.test(a))continue;if(a[0]==" ")i++,s=-Number.MAX_VALUE;else{var f=a.match(/^ */)[0].length;if(f&&a[f]!=" "){var l=f-s;l>0&&!(s%l)&&!(f%l)&&(r[l]=(r[l]||0)+1),n[f]=(n[f]||0)+1}s=f}while(u<o&&a[a.length-1]=="\\")a=e[u++]}var h=r.reduce(function(e,t){return e+t},0),p={score:0,length:0},d=0;for(var u=1;u<12;u++){var v=c(u);u==1?(d=v,v=n[1]?.9:.8,n.length||(v=0)):v/=d,r[u]&&(v+=r[u]/h),v>p.score&&(p={score:v,length:u})}if(p.score&&p.score>1.4)var m=p.length;if(i>d+1){if(m==1||d<i/4||p.score<1.8)m=undefined;return{ch:" ",length:m}}if(d>i+1)return{ch:" ",length:m}},t.detectIndentation=function(e){var n=e.getLines(0,1e3),r=t.$detectIndentation(n)||{};return r.ch&&e.setUseSoftTabs(r.ch==" "),r.length&&e.setTabSize(r.length),r},t.trimTrailingSpace=function(e,t){var n=e.getDocument(),r=n.getAllLines(),i=t&&t.trimEmpty?-1:0,s=[],o=-1;t&&t.keepCursorPosition&&(e.selection.rangeCount?e.selection.rangeList.ranges.forEach(function(e,t,n){var r=n[t+1];if(r&&r.cursor.row==e.cursor.row)return;s.push(e.cursor)}):s.push(e.selection.getCursor()),o=0);var u=s[o]&&s[o].row;for(var a=0,f=r.length;a<f;a++){var l=r[a],c=l.search(/\s+$/);a==u&&(c<s[o].column&&c>i&&(c=s[o].column),o++,u=s[o]?s[o].row:-1),c>i&&n.removeInLine(a,c,l.length)}},t.convertIndentation=function(e,t,n){var i=e.getTabString()[0],s=e.getTabSize();n||(n=s),t||(t=i);var o=t==" "?t:r.stringRepeat(t,n),u=e.doc,a=u.getAllLines(),f={},l={};for(var c=0,h=a.length;c<h;c++){var p=a[c],d=p.match(/^\s*/)[0];if(d){var v=e.$getStringScreenWidth(d)[0],m=Math.floor(v/s),g=v%s,y=f[m]||(f[m]=r.stringRepeat(o,m));y+=l[g]||(l[g]=r.stringRepeat(" ",g)),y!=d&&(u.removeInLine(c,0,d.length),u.insertInLine({row:c,column:0},y))}}e.setTabSize(n),e.setUseSoftTabs(t==" ")},t.$parseStringArg=function(e){var t={};/t/.test(e)?t.ch=" ":/s/.test(e)&&(t.ch=" ");var n=e.match(/\d+/);return n&&(t.length=parseInt(n[0],10)),t},t.$parseArg=function(e){return e?typeof e=="string"?t.$parseStringArg(e):typeof e.text=="string"?t.$parseStringArg(e.text):e:{}},t.commands=[{name:"detectIndentation",description:"Detect indentation from content",exec:function(e){t.detectIndentation(e.session)}},{name:"trimTrailingSpace",description:"Trim trailing whitespace",exec:function(e,n){t.trimTrailingSpace(e.session,n)}},{name:"convertIndentation",description:"Convert indentation to ...",exec:function(e,n){var r=t.$parseArg(n);t.convertIndentation(e.session,r.ch,r.length)}},{name:"setIndentation",description:"Set indentation",exec:function(e,n){var r=t.$parseArg(n);r.length&&e.session.setTabSize(r.length),r.ch&&e.session.setUseSoftTabs(r.ch==" ")}}]}); (function() {
ace.require(["ace/ext/whitespace"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,8 +0,0 @@
ace.define("ace/mode/yaml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"list.markup",regex:/^(?:-{3}|\.{3})\s*(?=#|$)/},{token:"list.markup",regex:/^\s*[\-?](?:$|\s)/},{token:"constant",regex:"!![\\w//]+"},{token:"constant.language",regex:"[&\\*][a-zA-Z0-9-_]+"},{token:["meta.tag","keyword"],regex:/^(\s*\w.*?)(:(?=\s|$))/},{token:["meta.tag","keyword"],regex:/(\w+?)(\s*:(?=\s|$))/},{token:"keyword.operator",regex:"<<\\w*:\\w*"},{token:"keyword.operator",regex:"-\\s*(?=[{])"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:/[|>][-+\d]*(?:$|\s+(?:$|#))/,onMatch:function(e,t,n,r){r=r.replace(/ #.*/,"");var i=/^ *((:\s*)?-(\s*[^|>])?)?/.exec(r)[0].replace(/\S\s*$/,"").length,s=parseInt(/\d+[\s+-]*$/.exec(r));return s?(i+=s-1,this.next="mlString"):this.next="mlStringPre",n.length?(n[0]=this.next,n[1]=i):(n.push(this.next),n.push(i)),this.token},next:"mlString"},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"constant.numeric",regex:/(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)(?=[^\d-\w]|$)/},{token:"constant.numeric",regex:/[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/},{token:"constant.language.boolean",regex:"\\b(?:true|false|TRUE|FALSE|True|False|yes|no)\\b"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:/[^\s,:\[\]\{\}]+/}],mlStringPre:[{token:"indent",regex:/^ *$/},{token:"indent",regex:/^ */,onMatch:function(e,t,n){var r=n[1];return r>=e.length?(this.next="start",n.shift(),n.shift()):(n[1]=e.length-1,this.next=n[0]="mlString"),this.token},next:"mlString"},{defaultToken:"string"}],mlString:[{token:"indent",regex:/^ *$/},{token:"indent",regex:/^ */,onMatch:function(e,t,n){var r=n[1];return r>=e.length?(this.next="start",n.splice(0)):this.next="mlString",this.token},next:"mlString"},{token:"string",regex:".+"}]},this.normalizeRules()};r.inherits(s,i),t.YamlHighlightRules=s}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(){};r.inherits(o,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=this.indentationBlock(e,n);if(r)return r;var i=/\S/,o=e.getLine(n),u=o.search(i);if(u==-1||o[u]!="#")return;var a=o.length,f=e.getLength(),l=n,c=n;while(++n<f){o=e.getLine(n);var h=o.search(i);if(h==-1)continue;if(o[h]!="#")break;c=n}if(c>l){var p=e.getLine(c).length;return new s(l,a,c,p)}},this.getFoldWidget=function(e,t,n){var r=e.getLine(n),i=r.search(/\S/),s=e.getLine(n+1),o=e.getLine(n-1),u=o.search(/\S/),a=s.search(/\S/);if(i==-1)return e.foldWidgets[n-1]=u!=-1&&u<a?"start":"","";if(u==-1){if(i==a&&r[i]=="#"&&s[i]=="#")return e.foldWidgets[n-1]="",e.foldWidgets[n+1]="","start"}else if(u==i&&r[i]=="#"&&o[i]=="#"&&e.getLine(n-2).search(/\S/)==-1)return e.foldWidgets[n-1]="start",e.foldWidgets[n+1]="","";return u!=-1&&u<i?e.foldWidgets[n-1]="start":e.foldWidgets[n-1]="",i<a?"start":""}}.call(o.prototype)}),ace.define("ace/mode/yaml",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/yaml_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/folding/coffee"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./yaml_highlight_rules").YamlHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./folding/coffee").FoldMode,a=function(){this.HighlightRules=s,this.$outdent=new o,this.foldingRules=new u,this.$behaviour=this.$defaultBehaviour};r.inherits(a,i),function(){this.lineCommentStart=["#"],this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.$id="ace/mode/yaml"}.call(a.prototype),t.Mode=a}); (function() {
ace.require(["ace/mode/yaml"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View file

@ -1,8 +0,0 @@
; (function() {
ace.require(["ace/snippets/yaml"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

View file

@ -10,11 +10,12 @@
<table class="table card-table table-responsive table-responsive-large" style="width:100%">
<thead>
<tr>
<th style="width: 35%">Name</th>
<th style="width: 30%">Name</th>
<th style="width: 15%"><i class="mdi mdi-arrow-down"></i> / <i class="mdi mdi-arrow-up"></i>
</th>
<th style="width: 15%">Peers/Seeders</th>
<th style="width: 35%" class="d-none d-lg-table-cell">Status</th>
<th style="width: 5%" >Actions</th>
</tr>
</thead>
<tbody>
@ -27,6 +28,7 @@
<td class="d-none d-lg-table-cell">
{{{torrent_status pieceChunks totalPieces}}}
</td>
<td><i class="mdi mdi-delete-forever" title="delete torrent" onclick='Distribyted.routes.deleteTorrent("{{../name}}","{{hash}}")'></i></td>
</tr>
{{/torrentStats}}
</tbody>

View file

@ -4,17 +4,18 @@ import (
"fmt"
"os"
"os/signal"
"path"
"syscall"
"time"
"github.com/anacrolix/missinggo/v2/filecache"
t "github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/storage"
"github.com/distribyted/distribyted/config"
"github.com/distribyted/distribyted/fs"
"github.com/distribyted/distribyted/fuse"
"github.com/distribyted/distribyted/http"
"github.com/distribyted/distribyted/stats"
"github.com/distribyted/distribyted/torrent"
"github.com/distribyted/distribyted/torrent/loader"
"github.com/distribyted/distribyted/webdav"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
@ -78,14 +79,6 @@ func main() {
}
}
func newCache(folder string) (*filecache.Cache, error) {
if err := os.MkdirAll(folder, 0744); err != nil {
return nil, fmt.Errorf("error creating metadata folder: %w", err)
}
return filecache.NewCache(folder)
}
func load(configPath string, port, webDAVPort int, fuseAllowOther bool) error {
ch := config.NewHandler(configPath)
@ -94,21 +87,36 @@ func load(configPath string, port, webDAVPort int, fuseAllowOther bool) error {
return fmt.Errorf("error loading configuration: %w", err)
}
fc, err := newCache(conf.Torrent.MetadataFolder)
if err := os.MkdirAll(conf.Torrent.MetadataFolder, 0744); err != nil {
return fmt.Errorf("error creating metadata folder: %w", err)
}
fc, err := filecache.NewCache(path.Join(conf.Torrent.MetadataFolder, "cache"))
if err != nil {
return fmt.Errorf("error creating cache: %w", err)
}
st := storage.NewResourcePieces(fc.AsResourceProvider())
c, err := torrent.NewClient(st, conf.Torrent)
fis, err := torrent.NewFileItemStore(path.Join(conf.Torrent.MetadataFolder, "items"), 2*time.Hour)
if err != nil {
return fmt.Errorf("error starting item store: %w", err)
}
c, err := torrent.NewClient(st, fis, conf.Torrent)
if err != nil {
return fmt.Errorf("error starting torrent client: %w", err)
}
ss := stats.NewTorrent()
cl := loader.NewConfig(conf.Routes)
ss := torrent.NewStats()
th := torrent.NewHandler(c, ss)
dbl, err := loader.NewDB(path.Join(conf.Torrent.MetadataFolder, "magnetdb"))
if err != nil {
return fmt.Errorf("error starting magnet database: %w", err)
}
ts := torrent.NewService(cl, dbl, ss, c)
mh := fuse.NewHandler(fuseAllowOther || conf.Fuse.AllowOther, conf.Fuse.Path)
@ -116,36 +124,33 @@ func load(configPath string, port, webDAVPort int, fuseAllowOther bool) error {
signal.Notify(sigChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
tryClose(c, mh)
log.Info().Msg("closing items database...")
fis.Close()
log.Info().Msg("closing magnet database...")
dbl.Close()
log.Info().Msg("closing torrent client...")
c.Close()
log.Info().Msg("unmounting fuse filesystem...")
mh.Unmount()
log.Info().Msg("exiting")
os.Exit(1)
}()
ch.OnReload(func(c *config.Root, ef config.EventFunc) error {
ef("unmounting filesystems")
mh.Unmount()
th.RemoveAll()
log.Info().Msg(fmt.Sprintf("setting cache size to %d MB", conf.Torrent.GlobalCacheSize))
fc.SetCapacity(conf.Torrent.GlobalCacheSize * 1024 * 1024)
ef(fmt.Sprintf("setting cache size to %d MB", c.Torrent.GlobalCacheSize))
fc.SetCapacity(c.Torrent.GlobalCacheSize * 1024 * 1024)
for _, mp := range c.Routes {
ef(fmt.Sprintf("loading %v with %d torrents...", mp.Name, len(mp.Torrents)))
if err := th.Load(mp.Name, mp.Torrents); err != nil {
return fmt.Errorf("error loading route %v: %w", mp.Name, err)
}
ef(fmt.Sprintf("%v loaded", mp.Name))
fss, err := ts.Load()
if err != nil {
return fmt.Errorf("error when loading torrents: %w", err)
}
return mh.Mount(th.Fileststems(), ef)
})
if err := ch.Reload(nil); err != nil {
return fmt.Errorf("error reloading configuration: %w", err)
go func() {
if err := mh.Mount(fss); err != nil {
log.Info().Err(err).Msg("error mounting filesystems")
}
defer func() {
tryClose(c, mh)
}()
go func() {
@ -155,7 +160,7 @@ func load(configPath string, port, webDAVPort int, fuseAllowOther bool) error {
port = conf.WebDAV.Port
}
cfs, err := fs.NewContainerFs(th.Fileststems())
cfs, err := fs.NewContainerFs(fss)
if err != nil {
log.Error().Err(err).Msg("error adding files to webDAV")
return
@ -169,19 +174,7 @@ func load(configPath string, port, webDAVPort int, fuseAllowOther bool) error {
log.Warn().Msg("webDAV configuration not found!")
}()
err = http.New(fc, ss, ch, port)
err = http.New(fc, ss, ts, ch, port)
log.Error().Err(err).Msg("error initializing HTTP server")
return err
}
func tryClose(c *t.Client, mountService *fuse.Handler) {
log.Info().Msg("closing torrent client...")
c.Close()
log.Info().Msg("unmounting fuse filesystem...")
mountService.Unmount()
log.Info().Msg("exiting")
os.Exit(1)
}

View file

@ -16,7 +16,6 @@ type ReloadFunc func(*Root, EventFunc) error
type Handler struct {
p string
reloadFunc ReloadFunc
}
func NewHandler(path string) *Handler {
@ -71,27 +70,6 @@ func (c *Handler) Get() (*Root, error) {
return conf, nil
}
func (c *Handler) OnReload(reloadFunc ReloadFunc) {
c.reloadFunc = reloadFunc
}
func (c *Handler) Reload(ef EventFunc) error {
if ef == nil {
ef = func(string) {}
}
conf, err := c.Get()
if err != nil {
return err
}
if c.reloadFunc != nil {
return c.reloadFunc(conf, ef)
}
return nil
}
func (c *Handler) Set(b []byte) error {
if err := yaml.Unmarshal(b, &Root{}); err != nil {
return fmt.Errorf("error parsing configuration file: %w", err)

View file

@ -26,13 +26,19 @@ type storage struct {
func newStorage(factories map[string]FsFactory) *storage {
return &storage{
files: make(map[string]File, 0),
children: make(map[string]map[string]File, 0),
filesystems: make(map[string]Filesystem, 0),
files: make(map[string]File),
children: make(map[string]map[string]File),
filesystems: make(map[string]Filesystem),
factories: factories,
}
}
func (s *storage) Clear() {
s.files = make(map[string]File)
s.children = make(map[string]map[string]File)
s.filesystems = make(map[string]Filesystem)
}
func (s *storage) Has(path string) bool {
path = clean(path)
@ -100,7 +106,7 @@ func (s *storage) createParent(p string, f File) error {
}
if _, ok := s.children[base]; !ok {
s.children[base] = make(map[string]File, 0)
s.children[base] = make(map[string]File)
}
if filename != "" {
@ -118,7 +124,7 @@ func (s *storage) Children(path string) map[string]File {
return out
}
l := make(map[string]File, 0)
l := make(map[string]File)
for n, f := range s.children[path] {
l[n] = f
}

View file

@ -1,6 +1,8 @@
package fs
import (
"sync"
"github.com/anacrolix/torrent"
"github.com/distribyted/distribyted/iio"
)
@ -8,18 +10,45 @@ import (
var _ Filesystem = &Torrent{}
type Torrent struct {
ts []*torrent.Torrent
mu sync.Mutex
ts map[string]*torrent.Torrent
s *storage
loaded bool
}
func NewTorrent(ts []*torrent.Torrent) *Torrent {
func NewTorrent() *Torrent {
return &Torrent{
ts: ts,
s: newStorage(SupportedFactories),
ts: make(map[string]*torrent.Torrent),
}
}
func (fs *Torrent) AddTorrent(t *torrent.Torrent) {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.loaded = false
fs.ts[t.InfoHash().HexString()] = t
}
func (fs *Torrent) RemoveTorrent(h string) {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.s.Clear()
fs.loaded = false
delete(fs.ts, h)
}
func (fs *Torrent) load() {
if fs.loaded {
return
}
fs.mu.Lock()
defer fs.mu.Unlock()
for _, t := range fs.ts {
<-t.GotInfo()
for _, file := range t.Files() {
@ -27,6 +56,7 @@ func (fs *Torrent) load() {
}
}
fs.loaded = true
}
func (fs *Torrent) Open(filename string) (File, error) {

View file

@ -25,7 +25,8 @@ func TestTorrentFilesystem(t *testing.T) {
to, err := client.AddMagnet(testMagnet)
require.NoError(err)
tfs := NewTorrent([]*torrent.Torrent{to})
tfs := NewTorrent()
tfs.AddTorrent(to)
files, err := tfs.ReadDir("/")
require.NoError(err)

View file

@ -6,7 +6,6 @@ import (
"runtime"
"github.com/billziss-gh/cgofuse/fuse"
"github.com/distribyted/distribyted/config"
"github.com/distribyted/distribyted/fs"
"github.com/rs/zerolog/log"
)
@ -25,7 +24,7 @@ func NewHandler(fuseAllowOther bool, path string) *Handler {
}
}
func (s *Handler) Mount(fss map[string]fs.Filesystem, ef config.EventFunc) error {
func (s *Handler) Mount(fss map[string]fs.Filesystem) error {
folder := s.path
// On windows, the folder must don't exist
if runtime.GOOS == "windows" {
@ -58,6 +57,8 @@ func (s *Handler) Mount(fss map[string]fs.Filesystem, ef config.EventFunc) error
s.host = host
log.Info().Str("path", folder).Msg("starting FUSE mount")
return nil
}

48
go.mod
View file

@ -4,12 +4,14 @@ go 1.17
require (
github.com/RoaringBitmap/roaring v0.9.4 // indirect
github.com/anacrolix/log v0.9.0
github.com/anacrolix/dht/v2 v2.12.1
github.com/anacrolix/log v0.10.0
github.com/anacrolix/missinggo/v2 v2.5.2
github.com/anacrolix/multiless v0.2.0 // indirect
github.com/anacrolix/torrent v1.33.0
github.com/anacrolix/torrent v1.37.0
github.com/billziss-gh/cgofuse v1.5.0
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/dgraph-io/badger/v3 v3.2103.2
github.com/elliotchance/orderedmap v1.4.0 // indirect
github.com/gin-contrib/static v0.0.1
github.com/gin-gonic/gin v1.7.4
@ -19,16 +21,16 @@ require (
github.com/leodido/go-urn v1.2.1 // indirect
github.com/pion/dtls/v2 v2.0.10 // indirect
github.com/pion/mdns v0.0.5 // indirect
github.com/pion/rtp v1.7.2 // indirect
github.com/pion/sctp v1.7.12 // indirect
github.com/pion/rtp v1.7.4 // indirect
github.com/pion/sctp v1.8.0 // indirect
github.com/rs/zerolog v1.25.0
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.3.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20211007125505-59d4e928ea9d
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
@ -36,9 +38,8 @@ require (
require (
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508 // indirect
github.com/anacrolix/chansync v0.3.0-0.0.20211007004133-3f72684c4a93 // indirect
github.com/anacrolix/confluence v1.8.0 // indirect
github.com/anacrolix/dht/v2 v2.10.6-0.20211007004332-99263ec9c1c8 // indirect
github.com/anacrolix/chansync v0.3.0 // indirect
github.com/anacrolix/confluence v1.10.0 // indirect
github.com/anacrolix/envpprof v1.1.1 // indirect
github.com/anacrolix/go-libutp v1.0.4 // indirect
github.com/anacrolix/missinggo v1.3.0 // indirect
@ -49,38 +50,48 @@ require (
github.com/anacrolix/upnp v0.1.2-0.20200416075019-5e9378ed1425 // indirect
github.com/anacrolix/utp v0.1.0 // indirect
github.com/benbjohnson/immutable v0.3.0 // indirect
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/bits-and-blooms/bitset v1.2.1 // indirect
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/klauspost/compress v1.12.3 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/pion/datachannel v1.4.21 // indirect
github.com/pion/ice/v2 v2.1.12 // indirect
github.com/pion/interceptor v0.0.15 // indirect
github.com/pion/datachannel v1.5.2 // indirect
github.com/pion/ice/v2 v2.1.13 // indirect
github.com/pion/interceptor v0.1.0 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.6 // indirect
github.com/pion/rtcp v1.2.8 // indirect
github.com/pion/sdp/v3 v3.0.4 // indirect
github.com/pion/srtp/v2 v2.0.5 // indirect
github.com/pion/stun v0.3.5 // indirect
github.com/pion/transport v0.12.3 // indirect
github.com/pion/turn/v2 v2.0.5 // indirect
github.com/pion/udp v0.1.1 // indirect
github.com/pion/webrtc/v3 v3.0.32 // indirect
github.com/pion/webrtc/v3 v3.1.9 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/dnscache v0.0.0-20210201191234-295bba877686 // indirect
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
@ -88,8 +99,9 @@ require (
github.com/willf/bitset v1.1.11 // indirect
github.com/willf/bloom v2.0.3+incompatible // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.opencensus.io v0.22.5 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.26.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)

641
go.sum

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,15 @@
package http
import (
"errors"
"io"
"io/ioutil"
"net/http"
"sort"
"github.com/anacrolix/missinggo/v2/filecache"
"github.com/distribyted/distribyted/config"
"github.com/distribyted/distribyted/stats"
"github.com/distribyted/distribyted/torrent"
"github.com/gin-gonic/gin"
)
var apiStatusHandler = func(fc *filecache.Cache, ss *stats.Torrent) gin.HandlerFunc {
var apiStatusHandler = func(fc *filecache.Cache, ss *torrent.Stats) gin.HandlerFunc {
return func(ctx *gin.Context) {
// TODO move to a struct
ctx.JSON(http.StatusOK, gin.H{
@ -25,81 +21,43 @@ var apiStatusHandler = func(fc *filecache.Cache, ss *stats.Torrent) gin.HandlerF
}
}
var apiRoutesHandler = func(ss *stats.Torrent) gin.HandlerFunc {
var apiRoutesHandler = func(ss *torrent.Stats) gin.HandlerFunc {
return func(ctx *gin.Context) {
s := ss.RoutesStats()
sort.Sort(stats.ByName(s))
sort.Sort(torrent.ByName(s))
ctx.JSON(http.StatusOK, s)
}
}
var apiGetConfigFile = func(ch *config.Handler) gin.HandlerFunc {
var apiAddTorrentHandler = func(s *torrent.Service) gin.HandlerFunc {
return func(ctx *gin.Context) {
rc, err := ch.GetRaw()
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
route := ctx.Param("route")
var json RouteAdd
if err := ctx.ShouldBindJSON(&json); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ctx.Data(http.StatusOK, "text/x-yaml", rc)
if err := s.AddMagnet(route, json.Magnet); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, nil)
}
}
var apiSetConfigFile = func(ch *config.Handler) gin.HandlerFunc {
var apiDelTorrentHandler = func(s *torrent.Service) gin.HandlerFunc {
return func(ctx *gin.Context) {
if ctx.Request.Body == nil {
ctx.AbortWithError(http.StatusInternalServerError, errors.New("no config file sent"))
route := ctx.Param("route")
hash := ctx.Param("torrent_hash")
if err := s.RemoveFromHash(route, hash); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c, err := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
if len(c) == 0 {
ctx.AbortWithError(http.StatusInternalServerError, errors.New("no config file sent"))
return
}
if err := ch.Set(c); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
ctx.JSON(http.StatusOK, gin.H{
"message": "config file saved",
})
}
}
var apiStreamEvents = func(events chan string) gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Stream(func(w io.Writer) bool {
if msg, ok := <-events; ok {
ctx.SSEvent("event", msg)
return true
}
return false
})
}
}
var apiReloadServer = func(ch *config.Handler, events chan string) gin.HandlerFunc {
return func(ctx *gin.Context) {
events <- "starting reload configuration process..."
err := ch.Reload(
func(m string) {
events <- m
})
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
}
ctx.JSON(http.StatusOK, gin.H{
"message": "reload process finished with no errors",
})
ctx.JSON(http.StatusOK, nil)
}
}

View file

@ -6,14 +6,14 @@ import (
"github.com/anacrolix/missinggo/v2/filecache"
"github.com/distribyted/distribyted"
"github.com/distribyted/distribyted/config"
"github.com/distribyted/distribyted/stats"
"github.com/distribyted/distribyted/torrent"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/shurcooL/httpfs/html/vfstemplate"
)
func New(fc *filecache.Cache, ss *stats.Torrent, ch *config.Handler, port int) error {
func New(fc *filecache.Cache, ss *torrent.Stats, s *torrent.Service, ch *config.Handler, port int) error {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
@ -30,18 +30,14 @@ func New(fc *filecache.Cache, ss *stats.Torrent, ch *config.Handler, port int) e
r.GET("/", indexHandler)
r.GET("/routes", routesHandler(ss))
r.GET("/config", configHandler)
eventChan := make(chan string)
api := r.Group("/api")
{
api.GET("/status", apiStatusHandler(fc, ss))
api.GET("/routes", apiRoutesHandler(ss))
api.GET("/config", apiGetConfigFile(ch))
api.POST("/config", apiSetConfigFile(ch))
api.POST("/reload", apiReloadServer(ch, eventChan))
api.GET("/events", apiStreamEvents(eventChan))
api.POST("/routes/:route/torrent", apiAddTorrentHandler(s))
api.DELETE("/routes/:route/torrent/:torrent_hash", apiDelTorrentHandler(s))
}
log.Info().Str("host", fmt.Sprintf("0.0.0.0:%d", port)).Msg("starting webserver")

9
http/model.go Normal file
View file

@ -0,0 +1,9 @@
package http
type RouteAdd struct {
Magnet string `json:"magnet" binding:"required"`
}
type Error struct {
Error string `json:"error"`
}

View file

@ -3,7 +3,7 @@ package http
import (
"net/http"
"github.com/distribyted/distribyted/stats"
"github.com/distribyted/distribyted/torrent"
"github.com/gin-gonic/gin"
)
@ -11,12 +11,8 @@ var indexHandler = func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
}
var routesHandler = func(ss *stats.Torrent) gin.HandlerFunc {
var routesHandler = func(ss *torrent.Stats) gin.HandlerFunc {
return func(c *gin.Context) {
c.HTML(http.StatusOK, "routes.html", ss.RoutesStats())
}
}
var configHandler = func(c *gin.Context) {
c.HTML(http.StatusOK, "config.html", nil)
}

27
log/badger.go Normal file
View file

@ -0,0 +1,27 @@
package log
import (
"strings"
"github.com/rs/zerolog"
)
type Badger struct {
L zerolog.Logger
}
func (l *Badger) Errorf(m string, f ...interface{}) {
l.L.Error().Msgf(strings.ReplaceAll(m, "\n", ""), f...)
}
func (l *Badger) Warningf(m string, f ...interface{}) {
l.L.Warn().Msgf(strings.ReplaceAll(m, "\n", ""), f...)
}
func (l *Badger) Infof(m string, f ...interface{}) {
l.L.Info().Msgf(strings.ReplaceAll(m, "\n", ""), f...)
}
func (l *Badger) Debugf(m string, f ...interface{}) {
l.L.Debug().Msgf(strings.ReplaceAll(m, "\n", ""), f...)
}

39
log/torrent.go Normal file
View file

@ -0,0 +1,39 @@
package log
import (
"github.com/anacrolix/log"
"github.com/rs/zerolog"
)
var _ log.LoggerImpl = &Torrent{}
type Torrent struct {
L zerolog.Logger
}
func (l *Torrent) Log(m log.Msg) {
level, ok := m.GetLevel()
e := l.L.Info()
if !ok {
level = log.Debug
}
switch level {
case log.Debug:
e = l.L.Debug()
case log.Info:
e = l.L.Debug().Str("error-type", "info")
case log.Warning:
e = l.L.Warn()
case log.Error:
e = l.L.Warn().Str("error-type", "error")
case log.Critical:
e = l.L.Warn().Str("error-type", "critical")
case log.Fatal:
e = l.L.Warn().Str("error-type", "fatal")
}
e.Msgf(m.String())
}

View file

@ -1,105 +0,0 @@
<!DOCTYPE html>
<head>
{{template "header.html" "Configuration"}}
<style>
#editor {
/** Setting height is also important, otherwise editor wont showup**/
height: 400px;
}
</style>
</head>
<body class="header-fixed sidebar-fixed sidebar-dark header-light" id="body">
<div class="wrapper">
{{template "navbar.html" "config"}}
<div class="page-wrapper">
<!-- Header -->
<header class="main-header " id="header">
<nav class="navbar navbar-static-top navbar-expand-lg">
<!-- Sidebar toggle button -->
<button id="sidebar-toggler" class="sidebar-toggle">
<span class="sr-only">Toggle navigation</span>
</button>
</nav>
</header>
<div class="content-wrapper">
<div class="content">
<div class="row">
<!-- Large Modal -->
<div class="modal fade" id="distribyted-reload-info-modal" tabindex="-1" role="dialog"
aria-labelledby="distribyted-reload-info-modal" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="distribyted-reload-info-modal-title">Reload status</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<ul id="distribyted-reload-info-text" class="list-group"></ul>
<div class="card-body d-flex align-items-center justify-content-center">
<div class="sk-wave" id="distribyted-reload-info-loading"
style="display:none">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger btn-pill"
data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-default">
<div class="card-header card-header-border-bottom">
<h2>Yaml Editor</h2>
</div>
<div class="card-body">
<form>
<div class="form-group">
<div id="editor"></div>
</div>
<div class="form-footer pt-4 pt-5 mt-4 border-top">
<button type="button" class="btn btn-primary btn-default"
onclick="javascript:Distribyted.config.save()">Save</button>
<button type="button" class="btn btn-secondary btn-default"
data-toggle="modal" data-target="#distribyted-reload-info-modal"
onclick="javascript:Distribyted.config.reload()">Reload server</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<footer class="footer mt-auto">
<div class="copyright bg-white">
</div>
</footer>
</div>
{{template "footer.html"}}
<script src="assets/plugins/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/plugins/ace/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/plugins/js-yaml/js-yaml.min.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/config.js" type="text/javascript" charset="utf-8"></script>
<script>
Distribyted.config.loadView();
</script>
</body>
</html>

View file

@ -37,16 +37,16 @@
<span class="nav-text">Routes</span>
</a>
</li>
{{if eq . "config"}}
<!-- {{if eq . "watched-folders"}}
<li class="active">
{{else}}
<li>
{{end}}
<a class="sidenav-item-link" href="/config">
<i class="mdi mdi-book-open-page-variant"></i>
<span class="nav-text">Yaml Config</span>
<a class="sidenav-item-link" href="/watched">
<i class="mdi mdi-folder-upload-outline"></i>
<span class="nav-text">Watched Folders</span>
</a>
</li>
</li> -->
</ul>
</div>

View file

@ -21,8 +21,36 @@
</header>
<div class="content-wrapper">
<div class="content" id="template_target">
<div class="content">
<div id="template_target"></div>
<div class="card card-default">
<div class="card-header card-header-border-bottom">
<h2>Add New Magnet to a Route</h2>
</div>
<div class="card-body">
<form id="new-magnet">
<div class="form-group">
<label>Magnet Link</label>
<input type="text" id="magnet-url" class="form-control"
placeholder="Enter Valid Magnet Link">
</div>
<div class="form-group">
<label>Route</label>
<select class="form-control" id="route-string">
{{range .}}
<option value="{{.Name}}">{{.Name}}</option>
{{end}}
</select>
</div>
<div class="form-footer pt-4 pt-5 mt-4 border-top">
<button type="submit" class="btn btn-primary btn-default">Add</button>
</div>
</form>
</div>
</div>
</div>
<footer class="footer mt-auto">
<div class="copyright bg-white">
</div>

View file

@ -1,20 +1,34 @@
package torrent
import (
"github.com/anacrolix/log"
"time"
"github.com/anacrolix/dht/v2"
"github.com/anacrolix/dht/v2/bep44"
tlog "github.com/anacrolix/log"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/storage"
"github.com/distribyted/distribyted/config"
dlog "github.com/distribyted/distribyted/log"
"github.com/rs/zerolog/log"
)
func NewClient(st storage.ClientImpl, cfg *config.TorrentGlobal) (*torrent.Client, error) {
func NewClient(st storage.ClientImpl, fis bep44.Store, cfg *config.TorrentGlobal) (*torrent.Client, error) {
// TODO download and upload limits
torrentCfg := torrent.NewDefaultClientConfig()
torrentCfg.Logger = log.Discard
torrentCfg.Seed = true
// torrentCfg.DisableWebseeds = true
torrentCfg.DefaultStorage = st
torrentCfg.DisableIPv6 = cfg.DisableIPv6
l := log.Logger.With().Str("component", "torrent-client").Logger()
torrentCfg.Logger = tlog.Logger{LoggerImpl: &dlog.Torrent{L: l}}
torrentCfg.ConfigureAnacrolixDhtServer = func(cfg *dht.ServerConfig) {
cfg.Store = fis
cfg.Exp = 2 * time.Hour
cfg.NoSecurity = false
}
return torrent.NewClient(torrentCfg)
}

View file

@ -1,80 +0,0 @@
package torrent
import (
"fmt"
"sync"
"github.com/anacrolix/torrent"
"github.com/distribyted/distribyted/config"
"github.com/distribyted/distribyted/fs"
"github.com/distribyted/distribyted/stats"
"github.com/rs/zerolog/log"
)
type Handler struct {
c *torrent.Client
s *stats.Torrent
fssMu sync.Mutex
fss map[string]fs.Filesystem
}
func NewHandler(c *torrent.Client, s *stats.Torrent) *Handler {
return &Handler{
c: c,
s: s,
fss: make(map[string]fs.Filesystem),
}
}
func (s *Handler) Load(route string, ts []*config.Torrent) error {
var torrents []*torrent.Torrent
for _, mpcTorrent := range ts {
var t *torrent.Torrent
var err error
switch {
case mpcTorrent.MagnetURI != "":
t, err = s.c.AddMagnet(mpcTorrent.MagnetURI)
case mpcTorrent.TorrentPath != "":
t, err = s.c.AddTorrentFromFile(mpcTorrent.TorrentPath)
default:
err = fmt.Errorf("no magnet URI or torrent path provided")
}
if err != nil {
return err
}
// only get info if name is not available
if t.Name() == "" {
log.Info().Str("hash", t.InfoHash().String()).Msg("getting torrent info")
<-t.GotInfo()
}
s.s.Add(route, t)
torrents = append(torrents, t)
log.Info().Str("name", t.Name()).Str("route", route).Msg("torrent added to mountpoint")
}
folder := "/" + route
s.fssMu.Lock()
defer s.fssMu.Unlock()
s.fss[folder] = fs.NewTorrent(torrents)
return nil
}
func (s *Handler) Fileststems() map[string]fs.Filesystem {
return s.fss
}
func (s *Handler) RemoveAll() error {
s.fssMu.Lock()
defer s.fssMu.Unlock()
s.fss = make(map[string]fs.Filesystem)
s.s.RemoveAll()
return nil
}

45
torrent/loader/config.go Normal file
View file

@ -0,0 +1,45 @@
package loader
import "github.com/distribyted/distribyted/config"
var _ Loader = &Config{}
type Config struct {
c []*config.Route
}
func NewConfig(r []*config.Route) *Config {
return &Config{
c: r,
}
}
func (l *Config) ListMagnets() (map[string][]string, error) {
out := make(map[string][]string)
for _, r := range l.c {
for _, t := range r.Torrents {
if t.MagnetURI == "" {
continue
}
out[r.Name] = append(out[r.Name], t.MagnetURI)
}
}
return out, nil
}
func (l *Config) ListTorrentPaths() (map[string][]string, error) {
out := make(map[string][]string)
for _, r := range l.c {
for _, t := range r.Torrents {
if t.TorrentPath == "" {
continue
}
out[r.Name] = append(out[r.Name], t.TorrentPath)
}
}
return out, nil
}

107
torrent/loader/db.go Normal file
View file

@ -0,0 +1,107 @@
package loader
import (
"path"
"github.com/anacrolix/torrent/metainfo"
"github.com/dgraph-io/badger/v3"
dlog "github.com/distribyted/distribyted/log"
"github.com/rs/zerolog/log"
)
var _ LoaderAdder = &DB{}
const routeRootKey = "/route/"
type DB struct {
db *badger.DB
}
func NewDB(path string) (*DB, error) {
l := log.Logger.With().Str("component", "torrent-store").Logger()
db, err := badger.Open(badger.DefaultOptions(path).WithLogger(&dlog.Badger{L: l}))
if err != nil {
return nil, err
}
err = db.RunValueLogGC(0.5)
if err != nil && err != badger.ErrNoRewrite {
return nil, err
}
return &DB{
db: db,
}, nil
}
func (l *DB) AddMagnet(r, m string) error {
err := l.db.Update(func(txn *badger.Txn) error {
spec, err := metainfo.ParseMagnetUri(m)
if err != nil {
return err
}
ih := spec.InfoHash.HexString()
rp := path.Join(routeRootKey, ih, r)
return txn.Set([]byte(rp), []byte(m))
})
if err != nil {
return err
}
return l.db.Sync()
}
func (l *DB) RemoveFromHash(r, h string) (bool, error) {
tx := l.db.NewTransaction(true)
defer tx.Discard()
var mh metainfo.Hash
if err := mh.FromHexString(h); err != nil {
return false, err
}
rp := path.Join(routeRootKey, h, r)
if _, err := tx.Get([]byte(rp)); err != nil {
return false, nil
}
if err := tx.Delete([]byte(rp)); err != nil {
return false, err
}
return true, tx.Commit()
}
func (l *DB) ListMagnets() (map[string][]string, error) {
tx := l.db.NewTransaction(false)
defer tx.Discard()
it := tx.NewIterator(badger.DefaultIteratorOptions)
defer it.Close()
prefix := []byte(routeRootKey)
out := make(map[string][]string)
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
_, r := path.Split(string(it.Item().Key()))
i := it.Item()
if err := i.Value(func(v []byte) error {
out[r] = append(out[r], string(v))
return nil
}); err != nil {
return nil, err
}
}
return out, nil
}
func (l *DB) ListTorrentPaths() (map[string][]string, error) {
return nil, nil
}
func (l *DB) Close() error {
return l.db.Close()
}

62
torrent/loader/db_test.go Normal file
View file

@ -0,0 +1,62 @@
package loader
import (
"os"
"testing"
"github.com/anacrolix/torrent/storage"
"github.com/stretchr/testify/require"
)
const m1 = "magnet:?xt=urn:btih:c9e15763f722f23e98a29decdfae341b98d53056"
func TestDB(t *testing.T) {
require := require.New(t)
tmpService, err := os.MkdirTemp("", "service")
require.NoError(err)
tmpStorage, err := os.MkdirTemp("", "storage")
require.NoError(err)
cs := storage.NewFile(tmpStorage)
defer cs.Close()
s, err := NewDB(tmpService)
require.NoError(err)
defer s.Close()
err = s.AddMagnet("route1", "WRONG MAGNET")
require.Error(err)
err = s.AddMagnet("route1", m1)
require.NoError(err)
err = s.AddMagnet("route2", m1)
require.NoError(err)
l, err := s.ListMagnets()
require.NoError(err)
require.Len(l, 2)
require.Len(l["route1"], 1)
require.Equal(l["route1"][0], m1)
require.Len(l["route2"], 1)
require.Equal(l["route2"][0], m1)
removed, err := s.RemoveFromHash("other", "c9e15763f722f23e98a29decdfae341b98d53056")
require.NoError(err)
require.False(removed)
removed, err = s.RemoveFromHash("route1", "c9e15763f722f23e98a29decdfae341b98d53056")
require.NoError(err)
require.True(removed)
l, err = s.ListMagnets()
require.NoError(err)
require.Len(l, 1)
require.Len(l["route2"], 1)
require.Equal(l["route2"][0], m1)
require.NoError(s.Close())
require.NoError(cs.Close())
}

13
torrent/loader/loader.go Normal file
View file

@ -0,0 +1,13 @@
package loader
type Loader interface {
ListMagnets() (map[string][]string, error)
ListTorrentPaths() (map[string][]string, error)
}
type LoaderAdder interface {
Loader
RemoveFromHash(r, h string) (bool, error)
AddMagnet(r, m string) error
}

179
torrent/service.go Normal file
View file

@ -0,0 +1,179 @@
package torrent
import (
"errors"
"fmt"
"path"
"sync"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo"
"github.com/distribyted/distribyted/fs"
"github.com/distribyted/distribyted/torrent/loader"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
type Service struct {
c *torrent.Client
s *Stats
mu sync.Mutex
fss map[string]fs.Filesystem
cfgLoader loader.Loader
db loader.LoaderAdder
log zerolog.Logger
}
func NewService(cfg loader.Loader, db loader.LoaderAdder, stats *Stats, c *torrent.Client) *Service {
l := log.Logger.With().Str("component", "torrent-service").Logger()
return &Service{
log: l,
s: stats,
c: c,
fss: make(map[string]fs.Filesystem),
cfgLoader: cfg,
db: db,
}
}
func (s *Service) Load() (map[string]fs.Filesystem, error) {
// Load from config
s.log.Info().Msg("adding torrents from configuration")
if err := s.load(s.cfgLoader); err != nil {
return nil, err
}
// Load from DB
s.log.Info().Msg("adding torrents from database")
return s.fss, s.load(s.db)
}
func (s *Service) load(l loader.Loader) error {
list, err := l.ListMagnets()
if err != nil {
return err
}
for r, ms := range list {
for _, m := range ms {
if err := s.addMagnet(r, m); err != nil {
return err
}
}
}
list, err = l.ListTorrentPaths()
if err != nil {
return err
}
for r, ms := range list {
for _, p := range ms {
if err := s.addTorrentPath(r, p); err != nil {
return err
}
}
}
return nil
}
func (s *Service) AddMagnet(r, m string) error {
if err := s.addMagnet(r, m); err != nil {
return err
}
// Add to db
return s.db.AddMagnet(r, m)
}
func (s *Service) addTorrentPath(r, p string) error {
// Add to client
t, err := s.c.AddTorrentFromFile(p)
if err != nil {
return err
}
return s.addTorrent(r, t)
}
func (s *Service) addMagnet(r, m string) error {
// Add to client
t, err := s.c.AddMagnet(m)
if err != nil {
return err
}
return s.addTorrent(r, t)
}
func (s *Service) addTorrent(r string, t *torrent.Torrent) error {
// only get info if name is not available
if t.Info() == nil {
s.log.Info().Str("hash", t.InfoHash().String()).Msg("getting torrent info")
<-t.GotInfo()
}
// Add to stats
s.s.Add(r, t)
// Add to filesystems
folder := path.Join("/", r)
s.mu.Lock()
defer s.mu.Unlock()
_, ok := s.fss[folder]
if !ok {
s.fss[folder] = fs.NewTorrent()
}
tfs, ok := s.fss[folder].(*fs.Torrent)
if !ok {
return errors.New("error adding torrent to filesystem")
}
tfs.AddTorrent(t)
s.log.Info().Str("name", t.Info().Name).Str("route", r).Msg("torrent added")
return nil
}
func (s *Service) RemoveFromHash(r, h string) error {
// Remove from db
deleted, err := s.db.RemoveFromHash(r, h)
if err != nil {
return err
}
if !deleted {
return fmt.Errorf("element with hash %v on route %v cannot be removed", h, r)
}
// Remove from stats
s.s.Del(r, h)
// Remove from fs
folder := path.Join("/", r)
tfs, ok := s.fss[folder].(*fs.Torrent)
if !ok {
return errors.New("error removing torrent from filesystem")
}
tfs.RemoveTorrent(h)
// Remove from client
var mh metainfo.Hash
if err := mh.FromHexString(h); err != nil {
return err
}
t, ok := s.c.Torrent(metainfo.NewHashFromHex(h))
if ok {
t.Drop()
}
return nil
}

View file

@ -1,7 +1,9 @@
package stats
package torrent
import (
"errors"
"sort"
"sync"
"time"
"github.com/anacrolix/torrent"
@ -37,6 +39,12 @@ type TorrentStats struct {
PieceSize int64 `json:"pieceSize"`
}
type byName []*TorrentStats
func (a byName) Len() int { return len(a) }
func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name }
type GlobalTorrentStats struct {
DownloadedBytes int64 `json:"downloadedBytes"`
UploadedBytes int64 `json:"uploadedBytes"`
@ -54,7 +62,7 @@ func (a ByName) Len() int { return len(a) }
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
type stats struct {
type stat struct {
totalDownloadBytes int64
downloadBytes int64
totalUploadBytes int64
@ -64,39 +72,58 @@ type stats struct {
time time.Time
}
type Torrent struct {
type Stats struct {
mut sync.Mutex
torrents map[string]*torrent.Torrent
torrentsByRoute map[string][]*torrent.Torrent
previousStats map[string]*stats
torrentsByRoute map[string]map[string]*torrent.Torrent
previousStats map[string]*stat
gTime time.Time
}
func NewTorrent() *Torrent {
return &Torrent{
func NewStats() *Stats {
return &Stats{
gTime: time.Now(),
torrents: make(map[string]*torrent.Torrent),
torrentsByRoute: make(map[string][]*torrent.Torrent),
previousStats: make(map[string]*stats),
torrentsByRoute: make(map[string]map[string]*torrent.Torrent),
previousStats: make(map[string]*stat),
}
}
func (s *Torrent) Add(route string, t *torrent.Torrent) {
s.torrents[t.InfoHash().String()] = t
s.previousStats[t.InfoHash().String()] = &stats{}
func (s *Stats) Add(route string, t *torrent.Torrent) {
s.mut.Lock()
defer s.mut.Unlock()
tbr := s.torrentsByRoute[route]
s.torrentsByRoute[route] = append(tbr, t)
h := t.InfoHash().String()
s.torrents[h] = t
s.previousStats[h] = &stat{}
_, ok := s.torrentsByRoute[route]
if !ok {
s.torrentsByRoute[route] = make(map[string]*torrent.Torrent)
}
s.torrentsByRoute[route][h] = t
}
func (s *Torrent) RemoveAll() {
// TODO lock
s.torrents = make(map[string]*torrent.Torrent)
s.previousStats = make(map[string]*stats)
s.torrentsByRoute = make(map[string][]*torrent.Torrent)
func (s *Stats) Del(route, hash string) {
s.mut.Lock()
defer s.mut.Unlock()
delete(s.torrents, hash)
delete(s.previousStats, hash)
ts, ok := s.torrentsByRoute[route]
if !ok {
return
}
delete(ts, hash)
}
func (s *Torrent) Stats(hash string) (*TorrentStats, error) {
func (s *Stats) Stats(hash string) (*TorrentStats, error) {
s.mut.Lock()
defer s.mut.Unlock()
t, ok := s.torrents[hash]
if !(ok) {
return nil, ErrTorrentNotFound
@ -107,7 +134,10 @@ func (s *Torrent) Stats(hash string) (*TorrentStats, error) {
return s.stats(now, t, true), nil
}
func (s *Torrent) RoutesStats() []*RouteStats {
func (s *Stats) RoutesStats() []*RouteStats {
s.mut.Lock()
defer s.mut.Unlock()
now := time.Now()
var out []*RouteStats
@ -117,6 +147,9 @@ func (s *Torrent) RoutesStats() []*RouteStats {
ts := s.stats(now, t, true)
tStats = append(tStats, ts)
}
sort.Sort(byName(tStats))
rs := &RouteStats{
Name: r,
TorrentStats: tStats,
@ -127,7 +160,10 @@ func (s *Torrent) RoutesStats() []*RouteStats {
return out
}
func (s *Torrent) GlobalStats() *GlobalTorrentStats {
func (s *Stats) GlobalStats() *GlobalTorrentStats {
s.mut.Lock()
defer s.mut.Unlock()
now := time.Now()
var totalDownload int64
@ -148,9 +184,12 @@ func (s *Torrent) GlobalStats() *GlobalTorrentStats {
}
}
func (s *Torrent) stats(now time.Time, t *torrent.Torrent, chunks bool) *TorrentStats {
func (s *Stats) stats(now time.Time, t *torrent.Torrent, chunks bool) *TorrentStats {
ts := &TorrentStats{}
prev := s.previousStats[t.InfoHash().String()]
prev, ok := s.previousStats[t.InfoHash().String()]
if !ok {
return &TorrentStats{}
}
if s.returnPreviousMeasurements(now) {
ts.DownloadedBytes = prev.downloadBytes
ts.UploadedBytes = prev.uploadBytes
@ -158,7 +197,7 @@ func (s *Torrent) stats(now time.Time, t *torrent.Torrent, chunks bool) *Torrent
st := t.Stats()
rd := st.BytesReadData.Int64()
wd := st.BytesWrittenData.Int64()
ist := &stats{
ist := &stat{
downloadBytes: rd - prev.totalDownloadBytes,
uploadBytes: wd - prev.totalUploadBytes,
totalDownloadBytes: rd,
@ -217,6 +256,6 @@ func (s *Torrent) stats(now time.Time, t *torrent.Torrent, chunks bool) *Torrent
const gap time.Duration = 2 * time.Second
func (s *Torrent) returnPreviousMeasurements(now time.Time) bool {
func (s *Stats) returnPreviousMeasurements(now time.Time) bool {
return now.Sub(s.gTime) < gap
}

92
torrent/store.go Normal file
View file

@ -0,0 +1,92 @@
package torrent
import (
"bytes"
"encoding/gob"
"time"
"github.com/anacrolix/dht/v2/bep44"
"github.com/dgraph-io/badger/v3"
dlog "github.com/distribyted/distribyted/log"
"github.com/rs/zerolog/log"
)
var _ bep44.Store = &FileItemStore{}
type FileItemStore struct {
ttl time.Duration
db *badger.DB
}
func NewFileItemStore(path string, itemsTTL time.Duration) (*FileItemStore, error) {
l := log.Logger.With().Str("component", "item-store").Logger()
db, err := badger.Open(badger.DefaultOptions(path).WithLogger(&dlog.Badger{L: l}))
if err != nil {
return nil, err
}
err = db.RunValueLogGC(0.5)
if err != nil && err != badger.ErrNoRewrite {
return nil, err
}
return &FileItemStore{
db: db,
ttl: itemsTTL,
}, nil
}
func (fis *FileItemStore) Put(i *bep44.Item) error {
tx := fis.db.NewTransaction(true)
defer tx.Discard()
key := i.Target()
var value bytes.Buffer
enc := gob.NewEncoder(&value)
if err := enc.Encode(i); err != nil {
return err
}
e := badger.NewEntry(key[:], value.Bytes()).WithTTL(fis.ttl)
if err := tx.SetEntry(e); err != nil {
return err
}
return tx.Commit()
}
func (fis *FileItemStore) Get(t bep44.Target) (*bep44.Item, error) {
tx := fis.db.NewTransaction(false)
defer tx.Discard()
dbi, err := tx.Get(t[:])
if err == badger.ErrKeyNotFound {
return nil, bep44.ErrItemNotFound
}
if err != nil {
return nil, err
}
valb, err := dbi.ValueCopy(nil)
if err != nil {
return nil, err
}
buf := bytes.NewBuffer(valb)
dec := gob.NewDecoder(buf)
var i *bep44.Item
if err := dec.Decode(&i); err != nil {
return nil, err
}
return i, nil
}
func (fis *FileItemStore) Del(t bep44.Target) error {
// ignore this
return nil
}
func (fis *FileItemStore) Close() error {
return fis.db.Close()
}