diff --git a/server/version/version.go b/server/version/version.go
index c97aa04..5199fe1 100644
--- a/server/version/version.go
+++ b/server/version/version.go
@@ -1,3 +1,3 @@
package version
-const Version = "MatriX.86"
+const Version = "MatriX.87"
diff --git a/web/.eslintcache b/web/.eslintcache
index 439a048..f5424b3 100644
--- a/web/.eslintcache
+++ b/web/.eslintcache
@@ -1 +1 @@
-[{"/home/yourok/MEGAWork/go/TorrServer/web/src/index.js":"1","/home/yourok/MEGAWork/go/TorrServer/web/src/App.js":"2","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Appbar.js":"3","/home/yourok/MEGAWork/go/TorrServer/web/src/components/RemoveAll.js":"4","/home/yourok/MEGAWork/go/TorrServer/web/src/components/TorrentList.js":"5","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Add.js":"6","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Upload.js":"7","/home/yourok/MEGAWork/go/TorrServer/web/src/components/About.js":"8","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Donate.js":"9","/home/yourok/MEGAWork/go/TorrServer/web/src/utils/Hosts.js":"10","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Settings.js":"11","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Torrent.js":"12","/home/yourok/MEGAWork/go/TorrServer/web/src/components/DialogCacheInfo.js":"13","/home/yourok/MEGAWork/go/TorrServer/web/src/components/DialogTorrentInfo.js":"14","/home/yourok/MEGAWork/go/TorrServer/web/src/utils/Utils.js":"15"},{"size":224,"mtime":1607328766846,"results":"16","hashOfConfig":"17"},{"size":840,"mtime":1607329513826,"results":"18","hashOfConfig":"17"},{"size":6552,"mtime":1614236662454,"results":"19","hashOfConfig":"17"},{"size":1416,"mtime":1607335705443,"results":"20","hashOfConfig":"17"},{"size":1568,"mtime":1607340137621,"results":"21","hashOfConfig":"17"},{"size":3345,"mtime":1607335694784,"results":"22","hashOfConfig":"17"},{"size":1348,"mtime":1607335733737,"results":"23","hashOfConfig":"17"},{"size":2278,"mtime":1608810011199,"results":"24","hashOfConfig":"17"},{"size":3634,"mtime":1615288639227,"results":"25","hashOfConfig":"17"},{"size":796,"mtime":1614236974272,"results":"26","hashOfConfig":"17"},{"size":8518,"mtime":1613802993837,"results":"27","hashOfConfig":"17"},{"size":5999,"mtime":1615533039159,"results":"28","hashOfConfig":"17"},{"size":6161,"mtime":1615801206075,"results":"29","hashOfConfig":"17"},{"size":7485,"mtime":1615533779808,"results":"30","hashOfConfig":"17"},{"size":419,"mtime":1614236662474,"results":"31","hashOfConfig":"17"},{"filePath":"32","messages":"33","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},"17eruzz",{"filePath":"35","messages":"36","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"47","messages":"48","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"49","messages":"50","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"51","messages":"52","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"53","messages":"54","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"55","usedDeprecatedRules":"34"},{"filePath":"56","messages":"57","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"58","messages":"59","errorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"60","usedDeprecatedRules":"61"},{"filePath":"62","messages":"63","errorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"64","usedDeprecatedRules":"34"},{"filePath":"65","messages":"66","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},"/home/yourok/MEGAWork/go/TorrServer/web/src/index.js",[],["67","68"],"/home/yourok/MEGAWork/go/TorrServer/web/src/App.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Appbar.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/RemoveAll.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/TorrentList.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Add.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Upload.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/About.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Donate.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/utils/Hosts.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Settings.js",["69"],"import ListItem from '@material-ui/core/ListItem'\nimport ListItemIcon from '@material-ui/core/ListItemIcon'\nimport ListItemText from '@material-ui/core/ListItemText'\nimport React, { useEffect } from 'react'\nimport SettingsIcon from '@material-ui/icons/Settings'\nimport Dialog from '@material-ui/core/Dialog'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport TextField from '@material-ui/core/TextField'\nimport DialogActions from '@material-ui/core/DialogActions'\nimport Button from '@material-ui/core/Button'\nimport { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core'\nimport { settingsHost, setTorrServerHost, torrserverHost } from '../utils/Hosts'\n\nexport default function SettingsDialog() {\n const [open, setOpen] = React.useState(false)\n const [settings, setSets] = React.useState({})\n const [show, setShow] = React.useState(false)\n const [tsHost, setTSHost] = React.useState(torrserverHost ? torrserverHost : window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''))\n\n const handleClickOpen = () => {\n setOpen(true)\n }\n const handleClose = () => {\n setOpen(false)\n }\n const handleCloseSave = () => {\n setOpen(false)\n let sets = JSON.parse(JSON.stringify(settings))\n sets.CacheSize *= 1024 * 1024\n sets.PreloadBufferSize *= 1024 * 1024\n fetch(settingsHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'set', sets: sets }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n }\n\n useEffect(() => {\n fetch(settingsHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'get' }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n json.CacheSize /= 1024 * 1024\n json.PreloadBufferSize /= 1024 * 1024\n setSets(json)\n setShow(true)\n },\n (error) => {\n setShow(false)\n console.log(error)\n }\n )\n .catch((e) => {\n setShow(false)\n console.log(e)\n })\n }, [tsHost])\n\n const onInputHost = (event) => {\n let host = event.target.value\n setTorrServerHost(host)\n setTSHost(host)\n }\n\n const inputForm = (event) => {\n let sets = JSON.parse(JSON.stringify(settings))\n if (event.target.type === 'number' || event.target.type === 'select-one') {\n sets[event.target.id] = Number(event.target.value)\n } else if (event.target.type === 'checkbox') {\n sets[event.target.id] = Boolean(event.target.checked)\n }\n setSets(sets)\n }\n\n return (\n
\n \n \n \n \n \n \n \n
\n )\n}\n\n","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Torrent.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/DialogCacheInfo.js",["70","71","72"],"import React, { useEffect, useRef } from 'react'\nimport Typography from '@material-ui/core/Typography'\n\nimport { getPeerString, humanizeSize } from '../utils/Utils'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport { cacheHost } from '../utils/Hosts'\n\nexport default function DialogCacheInfo(props) {\n const [hash] = React.useState(props.hash)\n const [cache, setCache] = React.useState({})\n const timerID = useRef(-1)\n const canvasRef = useRef(null)\n const dialogRef = useRef(null)\n\n useEffect(() => {\n const canvas = canvasRef.current\n const context = canvas.getContext('2d')\n\n if (hash)\n timerID.current = setInterval(() => {\n getCache(hash, (cache) => {\n setCache(cache);\n redraw(cache, canvas, context, dialogRef)\n })\n }, 100)\n else clearInterval(timerID.current)\n\n return () => {\n clearInterval(timerID.current)\n }\n }, [hash, props.open])\n\n return (\n \n \n \n Hash {cache.Hash}\n
\n Capacity {humanizeSize(cache.Capacity)}\n
\n Filled {humanizeSize(cache.Filled)}\n
\n Torrent size {cache.Torrent && cache.Torrent.torrent_size && humanizeSize(cache.Torrent.torrent_size)}\n
\n Pieces length {humanizeSize(cache.PiecesLength)}\n
\n Pieces count {cache.PiecesCount}\n
\n Peers: {getPeerString(cache.Torrent)}\n
\n Download speed {cache.Torrent && cache.Torrent.download_speed ? humanizeSize(cache.Torrent.download_speed) + '/sec' : ''}\n
\n Status {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string}\n \n \n \n \n \n
\n )\n}\n\nconst pieceSize = 12;\n\nconst colors = {\n 0: \"#eef2f4\", // empty piece\n 1: \"#00d0d0\", // donwloading\n 2: \"#009090\", // donwloading fill color\n 3: \"#3fb57a\", // downloaded\n 4: \"#9a9aff\", // reader range color\n 5: \"#000000\", // reader current position\n}\n\n\nconst map = new Map();\nconst savedCanvas = document.createElement(\"canvas\");\nsavedCanvas.ctx = savedCanvas.getContext(\"2d\");\n\n\n\nfunction redraw(cache, canvas, ctx, dialogRef) {\n if (!cache || !cache.PiecesCount || !dialogRef.current) return;\n if(dialogRef.current.offsetWidth !== canvas.width + 50 || cache.PiecesCount !== map.size) {\n canvas.width = dialogRef.current.offsetWidth - 50;\n renderPieces(canvas, ctx, cache.PiecesCount);\n }\n ctx.drawImage(savedCanvas, 0, 0);\n if (cache.Pieces) {\n Object.values(cache.Pieces).forEach(piece => {\n const cords = map.get(piece.Id);\n if (piece.Completed && piece.Size >= piece.Length) {\n ctx.fillStyle = colors[3];\n ctx.beginPath();\n ctx.rect(cords.x, cords.y, pieceSize, pieceSize);\n ctx.fill();\n } else {\n ctx.fillStyle = colors[1];\n ctx.beginPath();\n ctx.rect(cords.x, cords.y, pieceSize, pieceSize);\n ctx.fill();\n const percent = piece.Size / piece.Length\n fillPiece(ctx, piece.Id, percent)\n }\n })\n }\n cache.Readers.forEach(r => setReader(ctx, r.Start, r.Reader, r.End ))\n}\n\n\nfunction renderPieces(canvas, ctx, count) {\n const horizont = ~~(canvas.width / (pieceSize + 1));\n canvas.height = ~~(count / horizont) * (pieceSize + 1) + pieceSize + 1;\n const vertical = ~~(canvas.height / pieceSize);\n\n ctx.fillStyle = colors[0];\n\n map.clear();\n\n for(let y = 0; y < vertical; y++) {\n for(let x = 0; x < horizont; x++) {\n if(map.size >= count) break;\n map.set(map.size, { x: (pieceSize + 1) * x, y: (pieceSize + 1) * y});\n ctx.beginPath();\n ctx.rect((pieceSize + 1) * x, (pieceSize + 1) * y, pieceSize, pieceSize);\n ctx.fill();\n }\n }\n savedCanvas.width = canvas.width;\n savedCanvas.height = canvas.height;\n savedCanvas.ctx.drawImage(canvas, 0, 0);\n}\n\nfunction fillPiece(ctx, piece, percent) {\n let cords = map.get(piece);\n let offest = pieceSize * percent;\n if(offest < 1) return; //if less than one pixel a spot remains under a piece\n ctx.fillStyle = colors[2];\n ctx.beginPath();\n ctx.rect(cords.x, cords.y + (pieceSize - offest), pieceSize, offest);\n ctx.fill();\n}\n\nfunction setReader(ctx, start, reader, end) {\n ctx.strokeStyle = colors[4];\n for (let i = start; i <= end; i++) {\n cords = map.get(i)\n ctx.beginPath();\n ctx.rect(cords.x + 0.5, cords.y + 0.5, pieceSize - 1, pieceSize - 1);\n ctx.stroke();\n }\n\n let cords = map.get(reader);\n ctx.strokeStyle = colors[5];\n ctx.beginPath();\n ctx.rect(cords.x + 0.5, cords.y + 0.5, pieceSize - 1, pieceSize - 1);\n ctx.stroke();\n}\n\nfunction getCache(hash, callback) {\n try {\n fetch(cacheHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'get', hash: hash }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n callback(json)\n },\n (error) => {\n callback({})\n console.error(error)\n }\n )\n } catch (e) {\n console.error(e)\n callback({})\n }\n}",["73","74"],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/DialogTorrentInfo.js",["75","76","77","78","79"],"import React, { useEffect } from 'react'\nimport Typography from '@material-ui/core/Typography'\nimport { Button, ButtonGroup, Grid, List, ListItem } from '@material-ui/core'\nimport CachedIcon from '@material-ui/icons/Cached'\nimport LinearProgress from '@material-ui/core/LinearProgress';\n\nimport { getPeerString, humanizeSize } from '../utils/Utils'\nimport { playlistTorrHost, streamHost, viewedHost } from '../utils/Hosts'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\n\nconst style = {\n width100: {\n width: '100%',\n },\n width80: {\n width: '80%',\n },\n poster: {\n display: 'flex',\n flexDirection: 'row',\n borderRadius:'5px',\n },\n}\n\nexport default function DialogTorrentInfo(props) {\n const [torrent, setTorrent] = React.useState(props.torrent)\n const [viewed, setViewed] = React.useState(null)\n const [progress, setProgress] = React.useState(-1)\n\n useEffect(() => {\n setTorrent(props.torrent)\n if(torrent.stat==2)\n setProgress(torrent.preloaded_bytes * 100 / torrent.preload_size)\n getViewed(props.torrent.hash,(list) => {\n if (list) {\n let lst = list.map((itm) => itm.file_index)\n setViewed(lst)\n }else\n setViewed(null)\n })\n }, [props.torrent, props.open])\n\n return (\n \n
\n \n {torrent.poster &&
}\n \n {torrent.title} {torrent.name && torrent.name !== torrent.title && ' | ' + torrent.name}\n \n Peers: {getPeerString(torrent)}\n
\n Loaded: {getPreload(torrent)}\n
\n Speed: {humanizeSize(torrent.download_speed)}\n
\n Status: {torrent.stat_string}\n
\n \n \n \n {torrent.stat==2 && }\n \n
\n \n \n \n \n \n \n \n \n {getPlayableFile(torrent) &&\n getPlayableFile(torrent).map((file) => (\n \n \n \n \n ))}\n
\n \n
\n )\n}\n\nfunction remViews(hash){\n try {\n if (hash)\n fetch(viewedHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'rem', hash: hash, file_index:-1 }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n } catch (e) {\n console.error(e)\n }\n}\n\nfunction getViewed(hash, callback) {\n try {\n fetch(viewedHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'list', hash: hash }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n callback(json)\n },\n (error) => {\n callback(null)\n }\n )\n } catch (e) {\n console.error(e)\n }\n}\n\nfunction getPlayableFile(torrent){\n if (!torrent || !torrent.file_stats)\n return null\n return torrent.file_stats.filter(file => extPlayable.includes(getExt(file.path)))\n}\n\nfunction getExt(filename){\n const ext = filename.split('.').pop()\n if (ext == filename)\n return ''\n return ext.toLowerCase()\n}\n\nfunction getPreload(torrent) {\n if (torrent.preloaded_bytes > 0 && torrent.preload_size > 0 && torrent.preloaded_bytes < torrent.preload_size) {\n let progress = ((torrent.preloaded_bytes * 100) / torrent.preload_size).toFixed(2)\n return humanizeSize(torrent.preloaded_bytes) + ' / ' + humanizeSize(torrent.preload_size) + ' ' + progress + '%'\n }\n\n if (!torrent.preloaded_bytes) return humanizeSize(0)\n\n return humanizeSize(torrent.preloaded_bytes)\n}\n\nconst extPlayable = [\n// video\n \"3g2\",\n \"3gp\",\n \"aaf\",\n \"asf\",\n \"avchd\",\n \"avi\",\n \"drc\",\n \"flv\",\n \"iso\",\n \"m2v\",\n \"m2ts\",\n \"m4p\",\n \"m4v\",\n \"mkv\",\n \"mng\",\n \"mov\",\n \"mp2\",\n \"mp4\",\n \"mpe\",\n \"mpeg\",\n \"mpg\",\n \"mpv\",\n \"mxf\",\n \"nsv\",\n \"ogg\",\n \"ogv\",\n \"ts\",\n \"qt\",\n \"rm\",\n \"rmvb\",\n \"roq\",\n \"svi\",\n \"vob\",\n \"webm\",\n \"wmv\",\n \"yuv\",\n// audio\n \"aac\",\n \"aiff\",\n \"ape\",\n \"au\",\n \"flac\",\n \"gsm\",\n \"it\",\n \"m3u\",\n \"m4a\",\n \"mid\",\n \"mod\",\n \"mp3\",\n \"mpa\",\n \"pls\",\n \"ra\",\n \"s3m\",\n \"sid\",\n \"wav\",\n \"wma\",\n \"xm\"\n]\n","/home/yourok/MEGAWork/go/TorrServer/web/src/utils/Utils.js",[],{"ruleId":"80","replacedBy":"81"},{"ruleId":"82","replacedBy":"83"},{"ruleId":"84","severity":1,"message":"85","line":103,"column":29,"nodeType":"86","endLine":103,"endColumn":35},{"ruleId":"87","severity":1,"message":"88","line":147,"column":9,"nodeType":"89","messageId":"90","endLine":147,"endColumn":14},{"ruleId":"87","severity":1,"message":"88","line":149,"column":18,"nodeType":"89","messageId":"90","endLine":149,"endColumn":23},{"ruleId":"87","severity":1,"message":"88","line":149,"column":33,"nodeType":"89","messageId":"90","endLine":149,"endColumn":38},{"ruleId":"80","replacedBy":"91"},{"ruleId":"82","replacedBy":"92"},{"ruleId":"93","severity":1,"message":"94","line":33,"column":24,"nodeType":"95","messageId":"96","endLine":33,"endColumn":26},{"ruleId":"97","severity":1,"message":"98","line":42,"column":8,"nodeType":"99","endLine":42,"endColumn":35,"suggestions":"100"},{"ruleId":"93","severity":1,"message":"94","line":63,"column":30,"nodeType":"95","messageId":"96","endLine":63,"endColumn":32},{"ruleId":"93","severity":1,"message":"101","line":91,"column":152,"nodeType":"95","messageId":"96","endLine":91,"endColumn":154},{"ruleId":"93","severity":1,"message":"94","line":154,"column":13,"nodeType":"95","messageId":"96","endLine":154,"endColumn":15},"no-native-reassign",["102"],"no-negated-in-lhs",["103"],"jsx-a11y/heading-has-content","Headings must have content and the content must be accessible by a screen reader.","JSXOpeningElement","no-use-before-define","'cords' was used before it was defined.","Identifier","usedBeforeDefined",["102"],["103"],"eqeqeq","Expected '===' and instead saw '=='.","BinaryExpression","unexpected","react-hooks/exhaustive-deps","React Hook useEffect has missing dependencies: 'torrent.preload_size', 'torrent.preloaded_bytes', and 'torrent.stat'. Either include them or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setProgress' needs the current value of 'torrent.preloaded_bytes'.","ArrayExpression",["104"],"Expected '!==' and instead saw '!='.","no-global-assign","no-unsafe-negation",{"desc":"105","fix":"106"},"Update the dependencies array to be: [props.torrent, props.open, torrent.stat, torrent.preloaded_bytes, torrent.preload_size]",{"range":"107","text":"108"},[1371,1398],"[props.torrent, props.open, torrent.stat, torrent.preloaded_bytes, torrent.preload_size]"]
\ No newline at end of file
+[{"/home/yourok/MEGAWork/go/TorrServer/web/src/index.js":"1","/home/yourok/MEGAWork/go/TorrServer/web/src/App.js":"2","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Appbar.js":"3","/home/yourok/MEGAWork/go/TorrServer/web/src/components/RemoveAll.js":"4","/home/yourok/MEGAWork/go/TorrServer/web/src/components/TorrentList.js":"5","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Add.js":"6","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Upload.js":"7","/home/yourok/MEGAWork/go/TorrServer/web/src/components/About.js":"8","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Donate.js":"9","/home/yourok/MEGAWork/go/TorrServer/web/src/utils/Hosts.js":"10","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Settings.js":"11","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Torrent.js":"12","/home/yourok/MEGAWork/go/TorrServer/web/src/components/DialogCacheInfo.js":"13","/home/yourok/MEGAWork/go/TorrServer/web/src/components/DialogTorrentInfo.js":"14","/home/yourok/MEGAWork/go/TorrServer/web/src/utils/Utils.js":"15"},{"size":224,"mtime":1607328766846,"results":"16","hashOfConfig":"17"},{"size":840,"mtime":1607329513826,"results":"18","hashOfConfig":"17"},{"size":6552,"mtime":1614236662454,"results":"19","hashOfConfig":"17"},{"size":1416,"mtime":1607335705443,"results":"20","hashOfConfig":"17"},{"size":1568,"mtime":1607340137621,"results":"21","hashOfConfig":"17"},{"size":3345,"mtime":1607335694784,"results":"22","hashOfConfig":"17"},{"size":1348,"mtime":1607335733737,"results":"23","hashOfConfig":"17"},{"size":2278,"mtime":1608810011199,"results":"24","hashOfConfig":"17"},{"size":3634,"mtime":1615288639227,"results":"25","hashOfConfig":"17"},{"size":796,"mtime":1614236974272,"results":"26","hashOfConfig":"17"},{"size":8518,"mtime":1613802993837,"results":"27","hashOfConfig":"17"},{"size":5999,"mtime":1615533039159,"results":"28","hashOfConfig":"17"},{"size":6161,"mtime":1615801206075,"results":"29","hashOfConfig":"17"},{"size":7485,"mtime":1615533779808,"results":"30","hashOfConfig":"17"},{"size":419,"mtime":1614236662474,"results":"31","hashOfConfig":"17"},{"filePath":"32","messages":"33","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},"17eruzz",{"filePath":"35","messages":"36","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"47","messages":"48","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"49","messages":"50","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"51","messages":"52","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"53","messages":"54","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"55","usedDeprecatedRules":"34"},{"filePath":"56","messages":"57","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"34"},{"filePath":"58","messages":"59","errorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"60","usedDeprecatedRules":"34"},{"filePath":"61","messages":"62","errorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"63","usedDeprecatedRules":"34"},{"filePath":"64","messages":"65","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"66"},"/home/yourok/MEGAWork/go/TorrServer/web/src/index.js",[],["67","68"],"/home/yourok/MEGAWork/go/TorrServer/web/src/App.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Appbar.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/RemoveAll.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/TorrentList.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Add.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Upload.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/About.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Donate.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/utils/Hosts.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/Settings.js",["69"],"import ListItem from '@material-ui/core/ListItem'\nimport ListItemIcon from '@material-ui/core/ListItemIcon'\nimport ListItemText from '@material-ui/core/ListItemText'\nimport React, { useEffect } from 'react'\nimport SettingsIcon from '@material-ui/icons/Settings'\nimport Dialog from '@material-ui/core/Dialog'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport TextField from '@material-ui/core/TextField'\nimport DialogActions from '@material-ui/core/DialogActions'\nimport Button from '@material-ui/core/Button'\nimport { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core'\nimport { settingsHost, setTorrServerHost, torrserverHost } from '../utils/Hosts'\n\nexport default function SettingsDialog() {\n const [open, setOpen] = React.useState(false)\n const [settings, setSets] = React.useState({})\n const [show, setShow] = React.useState(false)\n const [tsHost, setTSHost] = React.useState(torrserverHost ? torrserverHost : window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''))\n\n const handleClickOpen = () => {\n setOpen(true)\n }\n const handleClose = () => {\n setOpen(false)\n }\n const handleCloseSave = () => {\n setOpen(false)\n let sets = JSON.parse(JSON.stringify(settings))\n sets.CacheSize *= 1024 * 1024\n sets.PreloadBufferSize *= 1024 * 1024\n fetch(settingsHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'set', sets: sets }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n }\n\n useEffect(() => {\n fetch(settingsHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'get' }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n json.CacheSize /= 1024 * 1024\n json.PreloadBufferSize /= 1024 * 1024\n setSets(json)\n setShow(true)\n },\n (error) => {\n setShow(false)\n console.log(error)\n }\n )\n .catch((e) => {\n setShow(false)\n console.log(e)\n })\n }, [tsHost])\n\n const onInputHost = (event) => {\n let host = event.target.value\n setTorrServerHost(host)\n setTSHost(host)\n }\n\n const inputForm = (event) => {\n let sets = JSON.parse(JSON.stringify(settings))\n if (event.target.type === 'number' || event.target.type === 'select-one') {\n sets[event.target.id] = Number(event.target.value)\n } else if (event.target.type === 'checkbox') {\n sets[event.target.id] = Boolean(event.target.checked)\n }\n setSets(sets)\n }\n\n return (\n \n \n \n \n \n \n \n \n
\n )\n}\n\n","/home/yourok/MEGAWork/go/TorrServer/web/src/components/Torrent.js",[],"/home/yourok/MEGAWork/go/TorrServer/web/src/components/DialogCacheInfo.js",["70","71","72"],"import React, { useEffect, useRef } from 'react'\nimport Typography from '@material-ui/core/Typography'\n\nimport { getPeerString, humanizeSize } from '../utils/Utils'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport { cacheHost } from '../utils/Hosts'\n\nexport default function DialogCacheInfo(props) {\n const [hash] = React.useState(props.hash)\n const [cache, setCache] = React.useState({})\n const timerID = useRef(-1)\n const canvasRef = useRef(null)\n const dialogRef = useRef(null)\n\n useEffect(() => {\n const canvas = canvasRef.current\n const context = canvas.getContext('2d')\n\n if (hash)\n timerID.current = setInterval(() => {\n getCache(hash, (cache) => {\n setCache(cache);\n redraw(cache, canvas, context, dialogRef)\n })\n }, 100)\n else clearInterval(timerID.current)\n\n return () => {\n clearInterval(timerID.current)\n }\n }, [hash, props.open])\n\n return (\n \n \n \n Hash {cache.Hash}\n
\n Capacity {humanizeSize(cache.Capacity)}\n
\n Filled {humanizeSize(cache.Filled)}\n
\n Torrent size {cache.Torrent && cache.Torrent.torrent_size && humanizeSize(cache.Torrent.torrent_size)}\n
\n Pieces length {humanizeSize(cache.PiecesLength)}\n
\n Pieces count {cache.PiecesCount}\n
\n Peers: {getPeerString(cache.Torrent)}\n
\n Download speed {cache.Torrent && cache.Torrent.download_speed ? humanizeSize(cache.Torrent.download_speed) + '/sec' : ''}\n
\n Status {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string}\n \n \n \n \n \n
\n )\n}\n\nconst pieceSize = 12;\n\nconst colors = {\n 0: \"#eef2f4\", // empty piece\n 1: \"#00d0d0\", // donwloading\n 2: \"#009090\", // donwloading fill color\n 3: \"#3fb57a\", // downloaded\n 4: \"#9a9aff\", // reader range color\n 5: \"#000000\", // reader current position\n}\n\n\nconst map = new Map();\nconst savedCanvas = document.createElement(\"canvas\");\nsavedCanvas.ctx = savedCanvas.getContext(\"2d\");\n\n\n\nfunction redraw(cache, canvas, ctx, dialogRef) {\n if (!cache || !cache.PiecesCount || !dialogRef.current) return;\n if(dialogRef.current.offsetWidth !== canvas.width + 50 || cache.PiecesCount !== map.size) {\n canvas.width = dialogRef.current.offsetWidth - 50;\n renderPieces(canvas, ctx, cache.PiecesCount);\n }\n ctx.drawImage(savedCanvas, 0, 0);\n if (cache.Pieces) {\n Object.values(cache.Pieces).forEach(piece => {\n const cords = map.get(piece.Id);\n if (piece.Completed && piece.Size >= piece.Length) {\n ctx.fillStyle = colors[3];\n ctx.beginPath();\n ctx.rect(cords.x, cords.y, pieceSize, pieceSize);\n ctx.fill();\n } else {\n ctx.fillStyle = colors[1];\n ctx.beginPath();\n ctx.rect(cords.x, cords.y, pieceSize, pieceSize);\n ctx.fill();\n const percent = piece.Size / piece.Length\n fillPiece(ctx, piece.Id, percent)\n }\n })\n }\n cache.Readers.forEach(r => setReader(ctx, r.Start, r.Reader, r.End ))\n}\n\n\nfunction renderPieces(canvas, ctx, count) {\n const horizont = ~~(canvas.width / (pieceSize + 1));\n canvas.height = ~~(count / horizont) * (pieceSize + 1) + pieceSize + 1;\n const vertical = ~~(canvas.height / pieceSize);\n\n ctx.fillStyle = colors[0];\n\n map.clear();\n\n for(let y = 0; y < vertical; y++) {\n for(let x = 0; x < horizont; x++) {\n if(map.size >= count) break;\n map.set(map.size, { x: (pieceSize + 1) * x, y: (pieceSize + 1) * y});\n ctx.beginPath();\n ctx.rect((pieceSize + 1) * x, (pieceSize + 1) * y, pieceSize, pieceSize);\n ctx.fill();\n }\n }\n savedCanvas.width = canvas.width;\n savedCanvas.height = canvas.height;\n savedCanvas.ctx.drawImage(canvas, 0, 0);\n}\n\nfunction fillPiece(ctx, piece, percent) {\n let cords = map.get(piece);\n let offest = pieceSize * percent;\n if(offest < 1) return; //if less than one pixel a spot remains under a piece\n ctx.fillStyle = colors[2];\n ctx.beginPath();\n ctx.rect(cords.x, cords.y + (pieceSize - offest), pieceSize, offest);\n ctx.fill();\n}\n\nfunction setReader(ctx, start, reader, end) {\n ctx.strokeStyle = colors[4];\n for (let i = start; i <= end; i++) {\n cords = map.get(i)\n ctx.beginPath();\n ctx.rect(cords.x + 0.5, cords.y + 0.5, pieceSize - 1, pieceSize - 1);\n ctx.stroke();\n }\n\n let cords = map.get(reader);\n ctx.strokeStyle = colors[5];\n ctx.beginPath();\n ctx.rect(cords.x + 0.5, cords.y + 0.5, pieceSize - 1, pieceSize - 1);\n ctx.stroke();\n}\n\nfunction getCache(hash, callback) {\n try {\n fetch(cacheHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'get', hash: hash }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n callback(json)\n },\n (error) => {\n callback({})\n console.error(error)\n }\n )\n } catch (e) {\n console.error(e)\n callback({})\n }\n}","/home/yourok/MEGAWork/go/TorrServer/web/src/components/DialogTorrentInfo.js",["73","74","75","76","77"],"import React, { useEffect } from 'react'\nimport Typography from '@material-ui/core/Typography'\nimport { Button, ButtonGroup, Grid, List, ListItem } from '@material-ui/core'\nimport CachedIcon from '@material-ui/icons/Cached'\nimport LinearProgress from '@material-ui/core/LinearProgress';\n\nimport { getPeerString, humanizeSize } from '../utils/Utils'\nimport { playlistTorrHost, streamHost, viewedHost } from '../utils/Hosts'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\n\nconst style = {\n width100: {\n width: '100%',\n },\n width80: {\n width: '80%',\n },\n poster: {\n display: 'flex',\n flexDirection: 'row',\n borderRadius:'5px',\n },\n}\n\nexport default function DialogTorrentInfo(props) {\n const [torrent, setTorrent] = React.useState(props.torrent)\n const [viewed, setViewed] = React.useState(null)\n const [progress, setProgress] = React.useState(-1)\n\n useEffect(() => {\n setTorrent(props.torrent)\n if(torrent.stat==2)\n setProgress(torrent.preloaded_bytes * 100 / torrent.preload_size)\n getViewed(props.torrent.hash,(list) => {\n if (list) {\n let lst = list.map((itm) => itm.file_index)\n setViewed(lst)\n }else\n setViewed(null)\n })\n }, [props.torrent, props.open])\n\n return (\n \n
\n \n {torrent.poster &&
}\n \n {torrent.title} {torrent.name && torrent.name !== torrent.title && ' | ' + torrent.name}\n \n Peers: {getPeerString(torrent)}\n
\n Loaded: {getPreload(torrent)}\n
\n Speed: {humanizeSize(torrent.download_speed)}\n
\n Status: {torrent.stat_string}\n
\n \n \n \n {torrent.stat==2 && }\n \n
\n \n \n \n \n \n \n \n \n {getPlayableFile(torrent) &&\n getPlayableFile(torrent).map((file) => (\n \n \n \n \n ))}\n
\n \n
\n )\n}\n\nfunction remViews(hash){\n try {\n if (hash)\n fetch(viewedHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'rem', hash: hash, file_index:-1 }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n } catch (e) {\n console.error(e)\n }\n}\n\nfunction getViewed(hash, callback) {\n try {\n fetch(viewedHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'list', hash: hash }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n callback(json)\n },\n (error) => {\n callback(null)\n }\n )\n } catch (e) {\n console.error(e)\n }\n}\n\nfunction getPlayableFile(torrent){\n if (!torrent || !torrent.file_stats)\n return null\n return torrent.file_stats.filter(file => extPlayable.includes(getExt(file.path)))\n}\n\nfunction getExt(filename){\n const ext = filename.split('.').pop()\n if (ext == filename)\n return ''\n return ext.toLowerCase()\n}\n\nfunction getPreload(torrent) {\n if (torrent.preloaded_bytes > 0 && torrent.preload_size > 0 && torrent.preloaded_bytes < torrent.preload_size) {\n let progress = ((torrent.preloaded_bytes * 100) / torrent.preload_size).toFixed(2)\n return humanizeSize(torrent.preloaded_bytes) + ' / ' + humanizeSize(torrent.preload_size) + ' ' + progress + '%'\n }\n\n if (!torrent.preloaded_bytes) return humanizeSize(0)\n\n return humanizeSize(torrent.preloaded_bytes)\n}\n\nconst extPlayable = [\n// video\n \"3g2\",\n \"3gp\",\n \"aaf\",\n \"asf\",\n \"avchd\",\n \"avi\",\n \"drc\",\n \"flv\",\n \"iso\",\n \"m2v\",\n \"m2ts\",\n \"m4p\",\n \"m4v\",\n \"mkv\",\n \"mng\",\n \"mov\",\n \"mp2\",\n \"mp4\",\n \"mpe\",\n \"mpeg\",\n \"mpg\",\n \"mpv\",\n \"mxf\",\n \"nsv\",\n \"ogg\",\n \"ogv\",\n \"ts\",\n \"qt\",\n \"rm\",\n \"rmvb\",\n \"roq\",\n \"svi\",\n \"vob\",\n \"webm\",\n \"wmv\",\n \"yuv\",\n// audio\n \"aac\",\n \"aiff\",\n \"ape\",\n \"au\",\n \"flac\",\n \"gsm\",\n \"it\",\n \"m3u\",\n \"m4a\",\n \"mid\",\n \"mod\",\n \"mp3\",\n \"mpa\",\n \"pls\",\n \"ra\",\n \"s3m\",\n \"sid\",\n \"wav\",\n \"wma\",\n \"xm\"\n]\n","/home/yourok/MEGAWork/go/TorrServer/web/src/utils/Utils.js",[],["78","79"],{"ruleId":"80","replacedBy":"81"},{"ruleId":"82","replacedBy":"83"},{"ruleId":"84","severity":1,"message":"85","line":103,"column":29,"nodeType":"86","endLine":103,"endColumn":35},{"ruleId":"87","severity":1,"message":"88","line":147,"column":9,"nodeType":"89","messageId":"90","endLine":147,"endColumn":14},{"ruleId":"87","severity":1,"message":"88","line":149,"column":18,"nodeType":"89","messageId":"90","endLine":149,"endColumn":23},{"ruleId":"87","severity":1,"message":"88","line":149,"column":33,"nodeType":"89","messageId":"90","endLine":149,"endColumn":38},{"ruleId":"91","severity":1,"message":"92","line":33,"column":24,"nodeType":"93","messageId":"94","endLine":33,"endColumn":26},{"ruleId":"95","severity":1,"message":"96","line":42,"column":8,"nodeType":"97","endLine":42,"endColumn":35,"suggestions":"98"},{"ruleId":"91","severity":1,"message":"92","line":63,"column":30,"nodeType":"93","messageId":"94","endLine":63,"endColumn":32},{"ruleId":"91","severity":1,"message":"99","line":91,"column":152,"nodeType":"93","messageId":"94","endLine":91,"endColumn":154},{"ruleId":"91","severity":1,"message":"92","line":154,"column":13,"nodeType":"93","messageId":"94","endLine":154,"endColumn":15},{"ruleId":"80","replacedBy":"100"},{"ruleId":"82","replacedBy":"101"},"no-native-reassign",["102"],"no-negated-in-lhs",["103"],"jsx-a11y/heading-has-content","Headings must have content and the content must be accessible by a screen reader.","JSXOpeningElement","no-use-before-define","'cords' was used before it was defined.","Identifier","usedBeforeDefined","eqeqeq","Expected '===' and instead saw '=='.","BinaryExpression","unexpected","react-hooks/exhaustive-deps","React Hook useEffect has missing dependencies: 'torrent.preload_size', 'torrent.preloaded_bytes', and 'torrent.stat'. Either include them or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setProgress' needs the current value of 'torrent.preloaded_bytes'.","ArrayExpression",["104"],"Expected '!==' and instead saw '!='.",["102"],["103"],"no-global-assign","no-unsafe-negation",{"desc":"105","fix":"106"},"Update the dependencies array to be: [props.torrent, props.open, torrent.stat, torrent.preloaded_bytes, torrent.preload_size]",{"range":"107","text":"108"},[1371,1398],"[props.torrent, props.open, torrent.stat, torrent.preloaded_bytes, torrent.preload_size]"]
\ No newline at end of file