basic functions working

This commit is contained in:
ston1th 2019-05-02 14:17:24 +02:00
commit ffeeae5702
22 changed files with 420 additions and 129 deletions

View file

@ -10,6 +10,8 @@ var (
RawPrefix = "/raw"
IndexPrefix = "/index"
UploadPrefix = "/upload"
NewPrefix = "/new"
MovePrefix = "/move"
DeletePrefix = "/delete"

View file

@ -20,7 +20,7 @@ type Result struct {
type Path struct {
Name string
Abs string
Indexed bool
Flag bool
}
type Paths []Path

View file

@ -23,7 +23,7 @@ func (db *DB) GetPaths(res []core.Result) error {
func (db *DB) GetIndexed(p core.Paths) {
for i, v := range p {
p[i].Indexed = db.IsIndexed(v.Abs)
p[i].Flag = db.IsIndexed(v.Abs)
}
}

View file

@ -3,13 +3,22 @@ package fs
import (
"errors"
"git.giftfish.de/ston1th/docstore/pkg/core"
"io"
"os"
"path/filepath"
"strings"
)
type Mode int
const (
Undefined Mode = iota
IsDir
IsFile
)
func (fs *Filesystem) GetFile(path string) (f *os.File, fi os.FileInfo, err error) {
path = strings.TrimPrefix(filepath.Clean(path), core.RawPrefix)
path = strings.TrimPrefix(Clean(path), core.RawPrefix)
p := filepath.Join(fs.Base, path)
f, err = os.Open(p)
if err != nil {
@ -25,8 +34,8 @@ func (fs *Filesystem) GetFile(path string) (f *os.File, fi os.FileInfo, err erro
return
}
func (fs *Filesystem) Mode(path string) (mode FileMode, err error) {
path = filepath.Clean(path)
func (fs *Filesystem) Mode(path string) (mode Mode, err error) {
path = Clean(path)
p := filepath.Join(fs.Base, path)
fi, err := os.Stat(p)
if err != nil {
@ -41,17 +50,46 @@ func (fs *Filesystem) Mode(path string) (mode FileMode, err error) {
return
}
func (fs *Filesystem) NewFile(r io.Reader, path, orig, name string) (newfile string, err error) {
path = Clean(path)
orig = Clean(orig)
_, orig = filepath.Split(orig)
name = Clean(name)
_, name = filepath.Split(name)
if name == "" {
name = orig
}
newfile = filepath.Join(path, name)
f, err := os.OpenFile(filepath.Join(fs.Base, newfile), os.O_RDWR|os.O_CREATE, FileMode)
if err != nil {
return
}
defer f.Close()
_, err = io.Copy(f, r)
return
}
func (fs *Filesystem) Mkdir(path, name string) error {
path = Clean(path)
name = Clean(name)
return os.Mkdir(filepath.Join(fs.Base, path, name), DirMode)
}
func (fs *Filesystem) Move(path, dest, name string) (string, error) {
path = filepath.Clean(path)
path = Clean(path)
if name == "" {
_, name = filepath.Split(path)
}
newfile := filepath.Clean(filepath.Join(dest, name))
return newfile, os.Rename(filepath.Join(fs.Base, path), filepath.Join(fs.Base, newfile))
fullpath := filepath.Join(fs.Base, path)
if fullpath == fs.Base {
return "", errors.New("the root path can not be moved")
}
newfile := Clean(filepath.Join(dest, name))
return newfile, os.Rename(fullpath, filepath.Join(fs.Base, newfile))
}
func (fs *Filesystem) Delete(path string) (removed []string, err error) {
path = filepath.Clean(path)
path = Clean(path)
fullpath := filepath.Join(fs.Base, path)
if fullpath == fs.Base {
return nil, errors.New("the root path can not be deleted")

View file

@ -9,12 +9,9 @@ import (
"strings"
)
type FileMode int
const (
Undefined FileMode = iota
IsDir
IsFile
DirMode = 0750
FileMode = 0640
)
// Directory represents a directory and its contents
@ -40,7 +37,7 @@ func NewFilesystem(base string) (fs *Filesystem, err error) {
}
func (fs *Filesystem) ReadDir(path string) (d *Directory) {
path = filepath.Clean(path)
path = Clean(path)
combined := filepath.Join(fs.Base, path)
f, err := os.Open(combined)
if err != nil {
@ -70,7 +67,12 @@ func (fs *Filesystem) ReadDir(path string) (d *Directory) {
return
}
func (fs *Filesystem) Recursive() (p core.Paths) {
func (fs *Filesystem) Recursive(p string) (paths core.Paths) {
p = Clean(p)
p, _ = filepath.Split(p)
if len(p) >= 2 {
p = p[:len(p)-1]
}
filepath.Walk(fs.Base, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
@ -80,16 +82,16 @@ func (fs *Filesystem) Recursive() (p core.Paths) {
if a == "" {
a = "/"
}
p = append(p, core.Path{Abs: a})
paths = append(paths, core.Path{Abs: a, Flag: a == p})
}
return nil
})
sort.Sort(p)
sort.Sort(paths)
return
}
func (fs *Filesystem) RecursiveFiles(p string) (files []string) {
p = filepath.Clean(p)
p = Clean(p)
fullpath := filepath.Join(fs.Base, p)
filepath.Walk(fullpath, func(path string, info os.FileInfo, err error) error {
if err != nil {
@ -104,7 +106,7 @@ func (fs *Filesystem) RecursiveFiles(p string) (files []string) {
}
func Paths(path string) (p core.Paths) {
path = filepath.Clean(path)
path = Clean(path)
s := strings.Split(path, string(filepath.Separator))
for i, v := range s[1:] {
if v == "" {
@ -117,7 +119,7 @@ func Paths(path string) (p core.Paths) {
p = append(p, core.Path{Name: v, Abs: filepath.Join(p[i-1].Abs, v)})
}
if len(p) != 0 {
p[len(p)-1].Indexed = true
p[len(p)-1].Flag = true
}
return
}

9
pkg/fs/helper.go Normal file
View file

@ -0,0 +1,9 @@
package fs
import "path/filepath"
const sep = "/"
func Clean(path string) string {
return filepath.FromSlash(filepath.Clean(sep + path))
}

View file

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/fs"
"git.giftfish.de/ston1th/docstore/pkg/log"
"git.giftfish.de/ston1th/godrop/v2"
"os"
@ -72,7 +73,7 @@ func New(base string, langs []string, timeout time.Duration) (s *Scanner, err er
}
func (s *Scanner) Scan(file string) (filename, text string, err error) {
file = filepath.Clean(file)
file = fs.Clean(file)
scanfile := filepath.Join(s.base, file)
var pdffile string
pdf := isPDF(scanfile)
@ -91,7 +92,7 @@ func (s *Scanner) Scan(file string) (filename, text string, err error) {
return
}
pdffile = filepath.Join(s.base, filename)
f, err = os.OpenFile(pdffile, os.O_RDWR|os.O_CREATE, 0640)
f, err = os.OpenFile(pdffile, os.O_RDWR|os.O_CREATE, fs.FileMode)
if err != nil {
f.Close()
return

View file

@ -9,6 +9,7 @@ import (
"git.giftfish.de/ston1th/jwt/v3"
"github.com/gorilla/mux"
"html/template"
"io"
"net/http"
"net/url"
"reflect"
@ -27,7 +28,7 @@ func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) *Context
h.Set("X-Frame-Options", "sameorigin")
h.Set("X-Content-Type-Options", "nosniff")
h.Set("X-XSS-Protection", "1; mode=block")
h.Set("Content-Security-Policy", "default-src 'none';object-src 'self';frame-src 'self';style-src 'self';img-src 'self' https: data:;connect-src 'self';frame-ancestors 'self'")
h.Set("Content-Security-Policy", "default-src 'none';object-src 'self';frame-src 'self';style-src 'self';img-src 'self' data:;connect-src 'self';frame-ancestors 'self'")
h.Set("Referrer-Policy", "same-origin")
return &Context{
Request: r,
@ -202,6 +203,15 @@ func (c *Context) Var(name string) (ret string) {
return
}
func (c *Context) File(name string) (io.Reader, string, error) {
var filename string
f, fh, err := c.Request.FormFile(name)
if fh != nil {
filename = fh.Filename
}
return f, filename, err
}
// CheckXsrf validates the xsrf token
func (c *Context) CheckXsrf() (ok bool) {
ok = checkXsrf(c.Form("token"), c.Token.RawSig()[:keySize])

View file

@ -216,18 +216,85 @@ func indexHandler(ctx *Context) {
}
}
func uploadHandler(ctx *Context) {
path := strings.TrimPrefix(ctx.Path(), core.UploadPrefix)
ctx.Data = webData{
Title: "Upload",
BodyTitle: "Upload",
}
ctx.Data.Path = path
ctx.Template("uploadHandler")
switch ctx.Method() {
case "GET":
ctx.Exec()
case "POST":
f, orig, err := ctx.File("file")
if err != nil {
ctx.Error(err)
return
}
newfile, err := ctx.Srv.FS.NewFile(f, path, orig, ctx.Form("name"))
if err != nil {
ctx.Error(err)
return
}
ctx.Redirect(newfile, http.StatusFound)
}
}
func newDirHandler(ctx *Context) {
path := strings.TrimPrefix(ctx.Path(), core.NewPrefix)
ctx.Data = webData{
Title: "Create Directory",
BodyTitle: "Create Directory",
}
ctx.Data.Path = path
ctx.Template("newDirHandler")
switch ctx.Method() {
case "GET":
ctx.Exec()
case "POST":
err := ctx.Srv.FS.Mkdir(path, ctx.Form("name"))
if err != nil {
ctx.Error(err)
return
}
ctx.Redirect(path, http.StatusFound)
}
}
func moveHandler(ctx *Context) {
path := strings.TrimPrefix(ctx.Path(), core.MovePrefix)
mode, err := ctx.Srv.FS.Mode(path)
if err != nil {
ctx.Error(err)
return
}
switch mode {
case fs.IsFile:
ctx.Data = webData{
Title: "Move File",
BodyTitle: "Move File",
}
case fs.IsDir:
ctx.Data = webData{
Title: "Move Directory",
BodyTitle: "Move Directory",
}
default:
ctx.Data = webData{
Title: "Move",
BodyTitle: "Move",
}
ctx.Error(noSuchFile)
return
}
ctx.Data.Path = path
ctx.Data.Paths = ctx.Srv.FS.Recursive(path)
_, ctx.Data.Data = filepath.Split(path)
ctx.Template("moveHandler")
switch ctx.Method() {
case "GET":
ctx.Data.Path = path
ctx.Data.Paths = ctx.Srv.FS.Recursive()
_, ctx.Data.Data = filepath.Split(path)
ctx.Exec()
case "POST":
name := ctx.Form("name")
@ -320,6 +387,7 @@ func dirHandler(ctx *Context) {
}
dir := ctx.Srv.FS.ReadDir(path)
ctx.Srv.DB.GetIndexed(dir.Files)
ctx.Data.Path = path
ctx.Data.Data = dir
ctx.Data.Paths = fs.Paths(path)
ctx.Exec()
@ -328,11 +396,14 @@ func dirHandler(ctx *Context) {
Title: "Document Viewer",
}
ctx.Template("fileHandler")
ctx.Data.Data = core.RawPrefix + path
ctx.Data.Data = core.Path{
Name: path,
Abs: core.RawPrefix + path,
Flag: ctx.Srv.DB.IsIndexed(path),
}
ctx.Data.Paths = fs.Paths(path)
ctx.Exec()
}
case "POST":
}
}

View file

@ -50,6 +50,20 @@ var prefixRoutes = []route{
indexHandler)),
[]string{"GET", "POST"},
},
{
core.UploadPrefix,
jwtHandler(
authHandler(
uploadHandler)),
[]string{"GET", "POST"},
},
{
core.NewPrefix,
jwtHandler(
authHandler(
newDirHandler)),
[]string{"GET", "POST"},
},
{
core.MovePrefix,
jwtHandler(
@ -69,7 +83,7 @@ var prefixRoutes = []route{
jwtHandler(
authHandler(
dirHandler)),
[]string{"GET", "POST", "PUT", "DELETE"},
[]string{"GET"},
},
}

File diff suppressed because one or more lines are too long

View file

@ -82,6 +82,8 @@ func (s *HTTPServer) loadTemplates() {
s.templ["dirHandler"] = parse(index, menu, dir)
s.templ["fileHandler"] = parse(index, menu, file)
s.templ["uploadHandler"] = parse(index, menu, upload)
s.templ["newDirHandler"] = parse(index, menu, newDir)
s.templ["moveHandler"] = parse(index, menu, move)
s.templ["deleteHandler"] = parse(index, menu, delete)
s.templ["notFoundHandler"] = parse(index, menu, notFound)

File diff suppressed because one or more lines are too long

View file

@ -41,6 +41,9 @@ a.anchor {
top: -70px;
visibility: hidden;
}
a.mono {
font-family: monospace;
}
.alert-primary {
background-color: #375a7f;
border-color: #375a7f;
@ -71,10 +74,6 @@ h5 {
h6 {
font-size: 1em;
}
.md-text {
font-family: 'Courier New';
font-size: 14;
}
ul.toc {
list-style-type: disc;
margin-bottom: 0 !important;
@ -119,6 +118,12 @@ input.menu {
.menu-search-input {
background-color: #2f2f2f !important;
}
.custom-file-label {
background-color: #444 !important;
}
.custom-file-label::after {
color: #fff;
}
html, body, .container, .content {
height: 100% !important;
}
@ -132,21 +137,18 @@ html, body, .container, .content {
margin-bottom: 20px;
width: 250px;
}
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -60px;
}
.push {
height: 20px;
}
.full-width {
width: 100%;
}
.footer-wrapper {
.relative {
position: relative;
padding-top: 20px;
height: 50px;
color: #888888;
}
.btn-breadcrumb {
position: absolute;
right: 1rem;
top: 38%;
transform: translateY(-50%);
}

View file

@ -1,12 +1,20 @@
{{define "body"}}
<div class="row">
<ol class="breadcrumb full-width">
<div class="nav-container full-width relative">
<ol class="breadcrumb full-width">
<li class="breadcrumb-item"></li>
<li class="breadcrumb-item"><a href="/">root</a></li>
{{range $item := .Paths}}
<li class="breadcrumb-item{{if $item.Indexed}} active{{end}}">{{if $item.Indexed}}{{$item.Name}}{{else}}<a href="{{$item.Abs}}">{{$item.Name}}</a>{{end}}</li>
<li class="breadcrumb-item{{if $item.Flag}} active{{end}}">{{if $item.Flag}}{{$item.Name}}{{else}}<a href="{{$item.Abs}}">{{$item.Name}}</a>{{end}}</li>
{{end}}
</ol>
</ol>
<div class="btn-group btn-breadcrumb">
<a href="/upload{{.Path}}" class="btn btn-sm btn-success">Upload</a>
<a href="/new{{.Path}}" class="btn btn-sm btn-primary">New Directory</a>
<a href="/move{{.Path}}" class="btn btn-sm btn-primary">Move</a>
<a href="/delete{{.Path}}" class="btn btn-sm btn-danger">Delete</a>
</div>
</div>
</div>
<div class="row">
<table class="table table-stripped table-hover">
@ -18,10 +26,10 @@
</tr>
</thead>
<tbody>
{{if .Data.Dirs}}
{{if .Data}}
{{range $item := .Data.Dirs}}
<tr>
<td><a href="{{$item.Abs}}">{{$item.Name}}/</a></td>
<th><a href="{{$item.Abs}}">{{$item.Name}}/</a></th>
<td>Directory</td>
<td><div class="btn-group">
<a href="/move{{$item.Abs}}" class="btn btn-sm btn-primary">Move</a>
@ -29,15 +37,13 @@
</div></td>
</tr>
{{end}}
{{end}}
{{if .Data.Files}}
{{range $item := .Data.Files}}
<tr>
<td><a href="{{$item.Abs}}">{{$item.Name}}</a></td>
<th><a href="{{$item.Abs}}">{{$item.Name}}</a></th>
<td>File</td>
<td><div class="btn-group">
{{if not $item.Indexed}}
<a href="/index{{$item.Abs}}" class="btn btn-sm btn-primary">Index</a>
{{if not $item.Flag}}
<a href="/index{{$item.Abs}}" class="btn btn-sm btn-success">Index</a>
{{end}}
<a href="/move{{$item.Abs}}" class="btn btn-sm btn-primary">Move</a>
<a href="/delete{{$item.Abs}}" class="btn btn-sm btn-danger">Delete</a>

View file

@ -1,14 +1,23 @@
{{define "body"}}
<div class="row">
<ol class="breadcrumb full-width">
<div class="nav-container full-width relative">
<ol class="breadcrumb full-width">
<li class="breadcrumb-item"></li>
<li class="breadcrumb-item"><a href="/">root</a></li>
{{range $item := .Paths}}
<li class="breadcrumb-item{{if $item.Indexed}} active{{end}}">{{if $item.Indexed}}{{$item.Name}}{{else}}<a href="{{$item.Abs}}">{{$item.Name}}</a>{{end}}</li>
<li class="breadcrumb-item{{if $item.Flag}} active{{end}}">{{if $item.Flag}}{{$item.Name}}{{else}}<a href="{{$item.Abs}}">{{$item.Name}}</a>{{end}}</li>
{{end}}
</ol>
</ol>
<div class="btn-group btn-breadcrumb">
{{if not .Data.Flag}}
<a href="/index{{.Data.Name}}" class="btn btn-sm btn-success">Index</a>
{{end}}
<a href="/move{{.Data.Name}}" class="btn btn-sm btn-primary">Move</a>
<a href="/delete{{.Data.Name}}" class="btn btn-sm btn-danger">Delete</a>
</div>
</div>
</div>
<div class="row">
<embed src="{{.Data}}" width="100%" height="700px" toolbar="1" statusbar="1" navpanes="1"></embed>
<embed src="{{.Data.Abs}}" width="100%" height="700px" toolbar="1" statusbar="1" navpanes="1"></embed>
</div>
{{end}}

View file

@ -3,8 +3,8 @@
<head>
<title>DocStore | {{.Title}}</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" integrity="sha256-bgotk/IPq4Yvt6s8AQGPTM5ZjPjQ6rrBRa2EyxpnFSc="></link>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-iYnSZSZLepCN7vabI6deUcbcw+1UNCmKhXlJ75O+S6Y="></link>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-3YeveuySvBgp2GItUS8eV1R/9T1zcPMyvw22SyVf8qo="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-L50A9TBaaysKaYOQWW5+H5nHTNLFLH4+F7Au3o4enAM="></link>
<meta name="theme-color" content="#375a7f">
<meta name="description" content="{{.Title}}">
</head>

View file

@ -2,6 +2,6 @@
<div class="alert alert-danger msg">
<h4>Error!</h4>
<p>{{.Data}}</p>
<a href="{{.Path}}" class="btn btn-sm btn-primary">Back</a>
</div>
<a href="{{.Path}}" class="btn btn-sm btn-primary">Back</a>
{{end}}

View file

@ -12,7 +12,7 @@
<label class="col-form-label" for="destination">Destination</label>
<select class="form-control input-sm" id="destination" name="destination" required>
{{range $item := .Paths}}
<option value="{{$item.Abs}}">{{$item.Abs}}</option>
<option value="{{$item.Abs}}"{{if $item.Flag}} selected{{end}}>{{$item.Abs}}</option>
{{end}}
</select>
</div>
@ -21,6 +21,7 @@
<input class="form-control input-sm" type="text" id="name" name="name" autocomplete="off" value="{{.Data}}">
</div>
<button class="btn btn-sm btn-primary" type="submit">Move</button>
<a href="{{.Path}}" class="btn btn-sm btn-primary">Back</a>
</form>
</div>
</div>

22
templates/newDir.html Normal file
View file

@ -0,0 +1,22 @@
{{define "body"}}
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}}</strong>
</div>
<div class="card-body">
<p>Create new directory in <a href="{{.Path}}">{{.Path}}</a>.</p>
<form class="form-horizontal" action="/new{{.Path}}" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="name">Name</label>
<input class="form-control input-sm" type="text" id="name" name="name" autocomplete="off" value="{{.Data}}">
</div>
<button class="btn btn-sm btn-primary" type="submit">Create</button>
</form>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -23,7 +23,7 @@
<div class="col">
{{if .Data}}
{{range $item := .Data}}
<a href="{{$item.Path}}"><b>{{$item.Path}}</b></a>
<a class="mono" href="{{$item.Path}}"><b>{{$item.Path}}</b></a>
<pre class="wrap">{{$item.HTML}}</pre><br>
{{end}}
{{else}}

30
templates/upload.html Normal file
View file

@ -0,0 +1,30 @@
{{define "body"}}
<div class="row">
<div class="col-xs-4 offset-4">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong>{{.BodyTitle}}</strong>
</div>
<div class="card-body">
<form class="form-horizontal" action="/upload{{.Path}}" method="post" enctype="multipart/form-data">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="name">Name</label>
<input class="form-control input-sm" type="text" id="name" name="name" autocomplete="off" value="{{.Data}}">
</div>
<div class="form-group">
<div class="input-group mb-3">
<div class="custom-file">
<input type="file" class="custom-file-input" id="file" name="file">
<label class="custom-file-label" for="file">Choose file</label>
</div>
</div>
</div>
<button class="btn btn-sm btn-success" type="submit">Upload</button>
<a href="{{.Path}}" class="btn btn-sm btn-primary">Back</a>
</form>
</div>
</div>
</div>
</div>
{{end}}