test(files, users): add e2e test and fix issues
This commit is contained in:
parent
b065684a47
commit
0265baf1b1
15 changed files with 5015 additions and 5327 deletions
3
go.mod
3
go.mod
|
@ -5,9 +5,10 @@ go 1.13
|
||||||
require (
|
require (
|
||||||
github.com/boltdb/bolt v1.3.1
|
github.com/boltdb/bolt v1.3.1
|
||||||
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 // indirect
|
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 // indirect
|
||||||
|
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
||||||
github.com/gin-gonic/gin v1.6.3
|
github.com/gin-gonic/gin v1.6.3
|
||||||
github.com/ihexxa/gocfg v0.0.0-20201206115732-ab537e3b1086
|
github.com/ihexxa/gocfg v0.0.0-20201206115732-ab537e3b1086
|
||||||
github.com/ihexxa/multipart v0.0.0-20201201114901-e265a43930c0
|
github.com/ihexxa/multipart v0.0.0-20201207132919-72f6e0e58b25
|
||||||
github.com/jessevdk/go-flags v1.4.0
|
github.com/jessevdk/go-flags v1.4.0
|
||||||
github.com/parnurzeal/gorequest v0.2.16
|
github.com/parnurzeal/gorequest v0.2.16
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
|
15
go.sum
15
go.sum
|
@ -3,22 +3,29 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 h1:Am81SElhR3XCQBunTisljzNkNese2T1FiV8jP79+dqg=
|
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 h1:Am81SElhR3XCQBunTisljzNkNese2T1FiV8jP79+dqg=
|
||||||
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
|
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
|
||||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e h1:8bZpGwoPxkaivQPrAbWl+7zjjUcbFUnYp7yQcx2r2N0=
|
||||||
|
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ=
|
||||||
|
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
@ -28,14 +35,19 @@ github.com/ihexxa/gocfg v0.0.0-20201206115732-ab537e3b1086 h1:1IzU4pzD8NyEHgJGLO
|
||||||
github.com/ihexxa/gocfg v0.0.0-20201206115732-ab537e3b1086/go.mod h1:oqDTq1ywx4Qi9DdhFwwMHoPCYv6Txrfj2SY5WWcgiJs=
|
github.com/ihexxa/gocfg v0.0.0-20201206115732-ab537e3b1086/go.mod h1:oqDTq1ywx4Qi9DdhFwwMHoPCYv6Txrfj2SY5WWcgiJs=
|
||||||
github.com/ihexxa/multipart v0.0.0-20201201114901-e265a43930c0 h1:L2fR7u66Kgh0DYOZWJPxOj23JrwEgUqOOivZHp6qfG8=
|
github.com/ihexxa/multipart v0.0.0-20201201114901-e265a43930c0 h1:L2fR7u66Kgh0DYOZWJPxOj23JrwEgUqOOivZHp6qfG8=
|
||||||
github.com/ihexxa/multipart v0.0.0-20201201114901-e265a43930c0/go.mod h1:rhOAe/52S/J1fq1VnXvKX8FHuo65I+IcYUozW4M7+wE=
|
github.com/ihexxa/multipart v0.0.0-20201201114901-e265a43930c0/go.mod h1:rhOAe/52S/J1fq1VnXvKX8FHuo65I+IcYUozW4M7+wE=
|
||||||
|
github.com/ihexxa/multipart v0.0.0-20201207132919-72f6e0e58b25 h1:gQCaP2qoFWCTz17jj9EUhE/plgqJwk3nHbcS4RHQYCw=
|
||||||
|
github.com/ihexxa/multipart v0.0.0-20201207132919-72f6e0e58b25/go.mod h1:rhOAe/52S/J1fq1VnXvKX8FHuo65I+IcYUozW4M7+wE=
|
||||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
@ -74,6 +86,7 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/Lt
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||||
|
@ -85,6 +98,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
|
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
75
package.json
75
package.json
|
@ -1,75 +1,4 @@
|
||||||
{
|
{
|
||||||
"name": "quickshare",
|
"private": true,
|
||||||
"version": "0.2.2",
|
"workspaces": ["src/client/web"]
|
||||||
"description": "A succinct file sharing server.",
|
|
||||||
"main": "",
|
|
||||||
"scripts": {
|
|
||||||
"build": "rm -rf ./dist && rm -rf ./public/dist && yarn build-client && goreleaser --snapshot",
|
|
||||||
"build-client": "webpack --config ./client/webpack.config.prod.js",
|
|
||||||
"build-client-dev": "webpack --config ./client/webpack.config.dev.js",
|
|
||||||
"clean": "go clean && rm -r ./dist && rm -r ./public/dist",
|
|
||||||
"check-fmt": "yarn prettier -l '@(client|docs)/*.@(js|jsx|md)'",
|
|
||||||
"fmt": "go fmt ./... && yarn prettier --write '@(client|docs)/*.@(js|jsx|md)'",
|
|
||||||
"setup": "yarn && dep ensure",
|
|
||||||
"start": "yarn build-client && go run server.go",
|
|
||||||
"test": "go test -v ./... && yarn jest --coverage"
|
|
||||||
},
|
|
||||||
"author": "hexxa",
|
|
||||||
"license": "LGPL-3.0",
|
|
||||||
"devDependencies": {
|
|
||||||
"auto-bump": "^0.0.9",
|
|
||||||
"babel-core": "^6.26.0",
|
|
||||||
"babel-jest": "^21.2.0",
|
|
||||||
"babel-loader": "^6.4.1",
|
|
||||||
"babel-preset-env": "^1.2.2",
|
|
||||||
"babel-preset-es2015": "^6.24.0",
|
|
||||||
"babel-preset-react": "^6.24.1",
|
|
||||||
"babel-preset-stage-0": "^6.24.1",
|
|
||||||
"babel-preset-stage-2": "^6.24.1",
|
|
||||||
"clean-webpack-plugin": "^0.1.19",
|
|
||||||
"css-loader": "^0.28.11",
|
|
||||||
"enzyme": "^3.3.0",
|
|
||||||
"enzyme-adapter-react-15": "^1.0.5",
|
|
||||||
"eslint": "^4.12.0",
|
|
||||||
"eslint-config-airbnb": "^16.0.0",
|
|
||||||
"eslint-plugin-import": "^2.7.0",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
|
||||||
"eslint-plugin-react": "^7.4.0",
|
|
||||||
"html-webpack-plugin": "^3.2.0",
|
|
||||||
"jest": "^21.2.1",
|
|
||||||
"md5": "^2.2.1",
|
|
||||||
"prettier": "^1.13.3",
|
|
||||||
"react-addons-test-utils": "^15.6.2",
|
|
||||||
"react-test-renderer": "15",
|
|
||||||
"regenerator-runtime": "^0.11.1",
|
|
||||||
"style-loader": "^0.21.0",
|
|
||||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
|
||||||
"uuid": "^3.1.0",
|
|
||||||
"value-equal": "^0.4.0",
|
|
||||||
"webpack": "^2.0.0",
|
|
||||||
"webpack-dev-server": "^2.9.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"axios": "^0.16.2",
|
|
||||||
"byte-size": "^4.0.2",
|
|
||||||
"debounce": "^1.1.0",
|
|
||||||
"file-loader": "^1.1.11",
|
|
||||||
"immutable": "^3.8.2",
|
|
||||||
"lodash.throttle": "^4.1.1",
|
|
||||||
"react": "^15.6.2",
|
|
||||||
"react-copy-to-clipboard": "^5.0.1",
|
|
||||||
"react-dom": "^15.6.2",
|
|
||||||
"react-icons": "^2.2.7",
|
|
||||||
"webpack-merge": "^4.1.2"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.jsx?$": "babel-jest"
|
|
||||||
},
|
|
||||||
"setupFiles": [
|
|
||||||
"./client/tests/enzyme_setup.js"
|
|
||||||
],
|
|
||||||
"verbose": true
|
|
||||||
},
|
|
||||||
"autoBump": {}
|
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ package client
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/handlers/fileshdr"
|
"github.com/ihexxa/quickshare/src/handlers/fileshdr"
|
||||||
"github.com/parnurzeal/gorequest"
|
"github.com/parnurzeal/gorequest"
|
||||||
|
@ -25,7 +26,7 @@ func (cl *FilesClient) url(urlpath string) string {
|
||||||
return fmt.Sprintf("%s%s", cl.addr, urlpath)
|
return fmt.Sprintf("%s%s", cl.addr, urlpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *FilesClient) Create(filepath string, size int64) (gorequest.Response, string, []error) {
|
func (cl *FilesClient) Create(filepath string, size int64) (*http.Response, string, []error) {
|
||||||
return cl.r.Post(cl.url("/v1/fs/files")).
|
return cl.r.Post(cl.url("/v1/fs/files")).
|
||||||
Send(fileshdr.CreateReq{
|
Send(fileshdr.CreateReq{
|
||||||
Path: filepath,
|
Path: filepath,
|
||||||
|
@ -34,13 +35,13 @@ func (cl *FilesClient) Create(filepath string, size int64) (gorequest.Response,
|
||||||
End()
|
End()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *FilesClient) Delete(filepath string) (gorequest.Response, string, []error) {
|
func (cl *FilesClient) Delete(filepath string) (*http.Response, string, []error) {
|
||||||
return cl.r.Delete(cl.url("/v1/fs/files")).
|
return cl.r.Delete(cl.url("/v1/fs/files")).
|
||||||
Param(fileshdr.FilePathQuery, filepath).
|
Param(fileshdr.FilePathQuery, filepath).
|
||||||
End()
|
End()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *FilesClient) Metadata(filepath string) (gorequest.Response, *fileshdr.MetadataResp, []error) {
|
func (cl *FilesClient) Metadata(filepath string) (*http.Response, *fileshdr.MetadataResp, []error) {
|
||||||
resp, body, errs := cl.r.Get(cl.url("/v1/fs/metadata")).
|
resp, body, errs := cl.r.Get(cl.url("/v1/fs/metadata")).
|
||||||
Param(fileshdr.FilePathQuery, filepath).
|
Param(fileshdr.FilePathQuery, filepath).
|
||||||
End()
|
End()
|
||||||
|
@ -54,13 +55,13 @@ func (cl *FilesClient) Metadata(filepath string) (gorequest.Response, *fileshdr.
|
||||||
return resp, mResp, nil
|
return resp, mResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *FilesClient) Mkdir(dirpath string) (gorequest.Response, string, []error) {
|
func (cl *FilesClient) Mkdir(dirpath string) (*http.Response, string, []error) {
|
||||||
return cl.r.Post(cl.url("/v1/fs/dirs")).
|
return cl.r.Post(cl.url("/v1/fs/dirs")).
|
||||||
Send(fileshdr.MkdirReq{Path: dirpath}).
|
Send(fileshdr.MkdirReq{Path: dirpath}).
|
||||||
End()
|
End()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *FilesClient) Move(oldpath, newpath string) (gorequest.Response, string, []error) {
|
func (cl *FilesClient) Move(oldpath, newpath string) (*http.Response, string, []error) {
|
||||||
return cl.r.Patch(cl.url("/v1/fs/files/move")).
|
return cl.r.Patch(cl.url("/v1/fs/files/move")).
|
||||||
Send(fileshdr.MoveReq{
|
Send(fileshdr.MoveReq{
|
||||||
OldPath: oldpath,
|
OldPath: oldpath,
|
||||||
|
@ -69,7 +70,7 @@ func (cl *FilesClient) Move(oldpath, newpath string) (gorequest.Response, string
|
||||||
End()
|
End()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *FilesClient) UploadChunk(filepath string, content string, offset int64) (gorequest.Response, string, []error) {
|
func (cl *FilesClient) UploadChunk(filepath string, content string, offset int64) (*http.Response, string, []error) {
|
||||||
return cl.r.Patch(cl.url("/v1/fs/files/chunks")).
|
return cl.r.Patch(cl.url("/v1/fs/files/chunks")).
|
||||||
Send(fileshdr.UploadChunkReq{
|
Send(fileshdr.UploadChunkReq{
|
||||||
Path: filepath,
|
Path: filepath,
|
||||||
|
@ -79,7 +80,7 @@ func (cl *FilesClient) UploadChunk(filepath string, content string, offset int64
|
||||||
End()
|
End()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *FilesClient) UploadStatus(filepath string) (gorequest.Response, *fileshdr.UploadStatusResp, []error) {
|
func (cl *FilesClient) UploadStatus(filepath string) (*http.Response, *fileshdr.UploadStatusResp, []error) {
|
||||||
resp, body, errs := cl.r.Get(cl.url("/v1/fs/files/chunks")).
|
resp, body, errs := cl.r.Get(cl.url("/v1/fs/files/chunks")).
|
||||||
Param(fileshdr.FilePathQuery, filepath).
|
Param(fileshdr.FilePathQuery, filepath).
|
||||||
End()
|
End()
|
||||||
|
@ -93,8 +94,8 @@ func (cl *FilesClient) UploadStatus(filepath string) (gorequest.Response, *files
|
||||||
return resp, uResp, nil
|
return resp, uResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *FilesClient) Download(filepath string, headers map[string]string) (gorequest.Response, string, []error) {
|
func (cl *FilesClient) Download(filepath string, headers map[string]string) (*http.Response, string, []error) {
|
||||||
r := cl.r.Get(cl.url("/v1/fs/files/chunks")).
|
r := cl.r.Get(cl.url("/v1/fs/files")).
|
||||||
Param(fileshdr.FilePathQuery, filepath)
|
Param(fileshdr.FilePathQuery, filepath)
|
||||||
for key, val := range headers {
|
for key, val := range headers {
|
||||||
r = r.Set(key, val)
|
r = r.Set(key, val)
|
||||||
|
@ -102,7 +103,7 @@ func (cl *FilesClient) Download(filepath string, headers map[string]string) (gor
|
||||||
return r.End()
|
return r.End()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *FilesClient) List(dirPath string) (gorequest.Response, *fileshdr.ListResp, []error) {
|
func (cl *FilesClient) List(dirPath string) (*http.Response, *fileshdr.ListResp, []error) {
|
||||||
resp, body, errs := cl.r.Get(cl.url("/v1/fs/dirs")).
|
resp, body, errs := cl.r.Get(cl.url("/v1/fs/dirs")).
|
||||||
Param(fileshdr.ListDirQuery, dirPath).
|
Param(fileshdr.ListDirQuery, dirPath).
|
||||||
End()
|
End()
|
||||||
|
|
30
src/client/settings.go
Normal file
30
src/client/settings.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/parnurzeal/gorequest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SettingsClient struct {
|
||||||
|
addr string
|
||||||
|
r *gorequest.SuperAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSettingsClient(addr string) *SettingsClient {
|
||||||
|
gr := gorequest.New()
|
||||||
|
return &SettingsClient{
|
||||||
|
addr: addr,
|
||||||
|
r: gr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *SettingsClient) url(urlpath string) string {
|
||||||
|
return fmt.Sprintf("%s%s", cl.addr, urlpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *SettingsClient) Health() (*http.Response, string, []error) {
|
||||||
|
return cl.r.Options(cl.url("/v1/settings/health")).
|
||||||
|
End()
|
||||||
|
}
|
|
@ -56,12 +56,16 @@ func (fs *LocalFS) Root() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeOpens assumes that it is called after opensMtx.Lock()
|
// closeOpens assumes that it is called after opensMtx.Lock()
|
||||||
func (fs *LocalFS) closeOpens(closeAll bool) error {
|
func (fs *LocalFS) closeOpens(closeAll bool, exclude map[string]bool) error {
|
||||||
batch := fs.opensCleanSize
|
batch := fs.opensCleanSize
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
for key, info := range fs.opens {
|
for key, info := range fs.opens {
|
||||||
if batch <= 0 && !closeAll {
|
if exclude[key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !closeAll && batch <= 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
batch--
|
batch--
|
||||||
|
@ -83,7 +87,7 @@ func (fs *LocalFS) closeOpens(closeAll bool) error {
|
||||||
func (fs *LocalFS) Sync() error {
|
func (fs *LocalFS) Sync() error {
|
||||||
fs.opensMtx.Lock()
|
fs.opensMtx.Lock()
|
||||||
defer fs.opensMtx.Unlock()
|
defer fs.opensMtx.Unlock()
|
||||||
return fs.closeOpens(true)
|
return fs.closeOpens(true, map[string]bool{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// check refers implementation of Dir.Open() in http package
|
// check refers implementation of Dir.Open() in http package
|
||||||
|
@ -176,6 +180,7 @@ func (fs *LocalFS) ReadAt(path string, b []byte, off int64) (int, error) {
|
||||||
return nil, ErrTooManyOpens
|
return nil, ErrTooManyOpens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// because the fd may be for other usage, its flag is not set as os.O_RDONLY
|
||||||
fd, err := os.OpenFile(fullpath, os.O_RDWR|os.O_APPEND, fs.defaultPerm)
|
fd, err := os.OpenFile(fullpath, os.O_RDWR|os.O_APPEND, fs.defaultPerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -185,7 +190,7 @@ func (fs *LocalFS) ReadAt(path string, b []byte, off int64) (int, error) {
|
||||||
lastAccess: time.Now(),
|
lastAccess: time.Now(),
|
||||||
}
|
}
|
||||||
fs.opens[fullpath] = info
|
fs.opens[fullpath] = info
|
||||||
fs.closeOpens(false)
|
fs.closeOpens(false, map[string]bool{fullpath: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
return info, nil
|
return info, nil
|
||||||
|
@ -231,7 +236,7 @@ func (fs *LocalFS) WriteAt(path string, b []byte, off int64) (int, error) {
|
||||||
lastAccess: time.Now(),
|
lastAccess: time.Now(),
|
||||||
}
|
}
|
||||||
fs.opens[fullpath] = info
|
fs.opens[fullpath] = info
|
||||||
fs.closeOpens(false)
|
fs.closeOpens(false, map[string]bool{fullpath: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
return info, nil
|
return info, nil
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -270,7 +271,7 @@ func (h *FileHandlers) UploadChunk(c *gin.Context) {
|
||||||
fsFilePath := h.FsPath(req.Path)
|
fsFilePath := h.FsPath(req.Path)
|
||||||
err = h.deps.FS().Rename(tmpFilePath, fsFilePath)
|
err = h.deps.FS().Rename(tmpFilePath, fsFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(q.ErrResp(c, 500, err))
|
c.JSON(q.ErrResp(c, 500, fmt.Errorf("%s error: %w", req.Path, err)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = h.uploadMgr.DelInfo(tmpFilePath)
|
err = h.uploadMgr.DelInfo(tmpFilePath)
|
||||||
|
@ -328,43 +329,75 @@ func (h *FileHandlers) Download(c *gin.Context) {
|
||||||
filePath := c.Query(FilePathQuery)
|
filePath := c.Query(FilePathQuery)
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
c.JSON(q.ErrResp(c, 400, errors.New("invalid file name")))
|
c.JSON(q.ErrResp(c, 400, errors.New("invalid file name")))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// concurrency relies on os's mechanism
|
// concurrently file accessing is managed by os
|
||||||
filePath = h.FsPath(filePath)
|
filePath = h.FsPath(filePath)
|
||||||
info, err := h.deps.FS().Stat(filePath)
|
info, err := h.deps.FS().Stat(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(q.ErrResp(c, 400, err))
|
if os.IsNotExist(err) {
|
||||||
|
c.JSON(q.ErrResp(c, 400, os.ErrNotExist))
|
||||||
|
} else {
|
||||||
|
c.JSON(q.ErrResp(c, 500, err))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
} else if info.IsDir() {
|
} else if info.IsDir() {
|
||||||
c.JSON(q.ErrResp(c, 501, errors.New("downloading a folder is not supported")))
|
c.JSON(q.ErrResp(c, 400, errors.New("downloading a folder is not supported")))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://golang.google.cn/pkg/net/http/#DetectContentType
|
||||||
|
// DetectContentType considers at most the first 512 bytes of data.
|
||||||
|
fileHeadBuf := make([]byte, 512)
|
||||||
|
read, err := h.deps.FS().ReadAt(filePath, fileHeadBuf, 0)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
c.JSON(q.ErrResp(c, 500, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentType := http.DetectContentType(fileHeadBuf[:read])
|
||||||
|
|
||||||
r, err := h.deps.FS().GetFileReader(filePath)
|
r, err := h.deps.FS().GetFileReader(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(q.ErrResp(c, 500, err))
|
c.JSON(q.ErrResp(c, 500, err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
// defer r.Close()
|
||||||
|
// := r.(*os.File)
|
||||||
|
|
||||||
// respond to normal requests
|
// respond to normal requests
|
||||||
|
fmt.Println(ifRangeVal, rangeVal)
|
||||||
if ifRangeVal != "" || rangeVal == "" {
|
if ifRangeVal != "" || rangeVal == "" {
|
||||||
c.DataFromReader(200, info.Size(), "application/octet-stream", r, map[string]string{})
|
c.DataFromReader(200, info.Size(), contentType, r, map[string]string{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// respond to range requests
|
// respond to range requests
|
||||||
parts, err := multipart.RangeToParts(rangeVal, "application/octet-stream", fmt.Sprintf("%d", info.Size()))
|
parts, err := multipart.RangeToParts(rangeVal, contentType, fmt.Sprintf("%d", info.Size()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(q.ErrResp(c, 400, err))
|
c.JSON(q.ErrResp(c, 401, err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
pr, pw := io.Pipe()
|
|
||||||
err = multipart.WriteResponse(r, pw, filePath, parts)
|
// pr, pw := io.Pipe()
|
||||||
|
mw, contentLength, err := multipart.NewResponseWriter(r, parts, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(q.ErrResp(c, 500, err))
|
c.JSON(q.ErrResp(c, 500, err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go mw.Write()
|
||||||
|
// WriteResponse(r, pw, filePath, parts)
|
||||||
|
// if err != nil {
|
||||||
|
// c.JSON(q.ErrResp(c, 500, err))
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
extraHeaders := map[string]string{
|
extraHeaders := map[string]string{
|
||||||
"Content-Disposition": fmt.Sprintf(`attachment; filename="%s"`, filePath),
|
// "Content-Disposition": fmt.Sprintf(`attachment; filename="%s"`, filePath),
|
||||||
}
|
}
|
||||||
c.DataFromReader(206, info.Size(), "application/octet-stream", pr, extraHeaders)
|
// it takes the \r\n before body into account, so contentLength+2
|
||||||
|
c.DataFromReader(206, contentLength+2, contentType, mw, extraHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListResp struct {
|
type ListResp struct {
|
||||||
|
@ -374,7 +407,7 @@ type ListResp struct {
|
||||||
func (h *FileHandlers) List(c *gin.Context) {
|
func (h *FileHandlers) List(c *gin.Context) {
|
||||||
dirPath := c.Query(ListDirQuery)
|
dirPath := c.Query(ListDirQuery)
|
||||||
if dirPath == "" {
|
if dirPath == "" {
|
||||||
c.JSON(q.ErrResp(c, 400, errors.New("incorrect path name")))
|
c.JSON(q.ErrResp(c, 402, errors.New("incorrect path name")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
src/handlers/settings/handler.go
Normal file
26
src/handlers/settings/handler.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/ihexxa/gocfg"
|
||||||
|
|
||||||
|
"github.com/ihexxa/quickshare/src/depidx"
|
||||||
|
q "github.com/ihexxa/quickshare/src/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SettingsSvc struct {
|
||||||
|
cfg gocfg.ICfg
|
||||||
|
deps *depidx.Deps
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSettingsSvc(cfg gocfg.ICfg, deps *depidx.Deps) (*SettingsSvc, error) {
|
||||||
|
return &SettingsSvc{
|
||||||
|
cfg: cfg,
|
||||||
|
deps: deps,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SettingsSvc) Health(c *gin.Context) {
|
||||||
|
// TODO: currently it checks nothing
|
||||||
|
c.JSON(q.Resp(200))
|
||||||
|
}
|
|
@ -10,6 +10,11 @@ import (
|
||||||
q "github.com/ihexxa/quickshare/src/handlers"
|
q "github.com/ihexxa/quickshare/src/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var exposedAPIs = map[string]bool{
|
||||||
|
"Login-fm": true,
|
||||||
|
"Health-fm": true,
|
||||||
|
}
|
||||||
|
|
||||||
func GetHandlerName(fullname string) (string, error) {
|
func GetHandlerName(fullname string) (string, error) {
|
||||||
parts := strings.Split(fullname, ".")
|
parts := strings.Split(fullname, ".")
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
|
@ -28,7 +33,7 @@ func (h *SimpleUserHandlers) Auth() gin.HandlerFunc {
|
||||||
|
|
||||||
// TODO: may also check the path
|
// TODO: may also check the path
|
||||||
enableAuth := h.cfg.GrabBool("Users.EnableAuth")
|
enableAuth := h.cfg.GrabBool("Users.EnableAuth")
|
||||||
if enableAuth && handlerName != "Login-fm" {
|
if enableAuth && !exposedAPIs[handlerName] {
|
||||||
token, err := c.Cookie(TokenCookie)
|
token, err := c.Cookie(TokenCookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(q.ErrResp(c, 401, err))
|
c.JSON(q.ErrResp(c, 401, err))
|
||||||
|
|
|
@ -49,7 +49,7 @@ func DefaultConfig() (string, error) {
|
||||||
OpenTTL: 60, // 1 min
|
OpenTTL: 60, // 1 min
|
||||||
},
|
},
|
||||||
Users: &UsersCfg{
|
Users: &UsersCfg{
|
||||||
EnableAuth: true,
|
EnableAuth: false,
|
||||||
DefaultAdmin: "",
|
DefaultAdmin: "",
|
||||||
DefaultAdminPwd: "",
|
DefaultAdminPwd: "",
|
||||||
CookieTTL: 3600 * 24 * 7, // 1 week
|
CookieTTL: 3600 * 24 * 7, // 1 week
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/static"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/ihexxa/gocfg"
|
"github.com/ihexxa/gocfg"
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ import (
|
||||||
"github.com/ihexxa/quickshare/src/fs"
|
"github.com/ihexxa/quickshare/src/fs"
|
||||||
"github.com/ihexxa/quickshare/src/fs/local"
|
"github.com/ihexxa/quickshare/src/fs/local"
|
||||||
"github.com/ihexxa/quickshare/src/handlers/fileshdr"
|
"github.com/ihexxa/quickshare/src/handlers/fileshdr"
|
||||||
|
"github.com/ihexxa/quickshare/src/handlers/settings"
|
||||||
"github.com/ihexxa/quickshare/src/handlers/singleuserhdr"
|
"github.com/ihexxa/quickshare/src/handlers/singleuserhdr"
|
||||||
"github.com/ihexxa/quickshare/src/idgen/simpleidgen"
|
"github.com/ihexxa/quickshare/src/idgen/simpleidgen"
|
||||||
"github.com/ihexxa/quickshare/src/kvstore"
|
"github.com/ihexxa/quickshare/src/kvstore"
|
||||||
|
@ -114,31 +116,41 @@ func initHandlers(router *gin.Engine, cfg gocfg.ICfg, deps *depidx.Deps) (*gin.E
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settingsSvc, err := settings.NewSettingsSvc(cfg, deps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// middleware
|
// middleware
|
||||||
router.Use(userHdrs.Auth())
|
router.Use(userHdrs.Auth())
|
||||||
|
// tmp static server
|
||||||
|
router.Use(static.Serve("/", static.LocalFile("../static", false)))
|
||||||
|
|
||||||
// handler
|
// handler
|
||||||
v1 := router.Group("/v1")
|
v1 := router.Group("/v1")
|
||||||
|
|
||||||
users := v1.Group("/users")
|
usersAPI := v1.Group("/users")
|
||||||
users.POST("/login", userHdrs.Login)
|
usersAPI.POST("/login", userHdrs.Login)
|
||||||
users.POST("/logout", userHdrs.Logout)
|
usersAPI.POST("/logout", userHdrs.Logout)
|
||||||
users.PATCH("/pwd", userHdrs.SetPwd)
|
usersAPI.PATCH("/pwd", userHdrs.SetPwd)
|
||||||
|
|
||||||
filesSvc := v1.Group("/fs")
|
filesAPI := v1.Group("/fs")
|
||||||
filesSvc.POST("/files", fileHdrs.Create)
|
filesAPI.POST("/files", fileHdrs.Create)
|
||||||
filesSvc.DELETE("/files", fileHdrs.Delete)
|
filesAPI.DELETE("/files", fileHdrs.Delete)
|
||||||
filesSvc.GET("/files", fileHdrs.Download)
|
filesAPI.GET("/files", fileHdrs.Download)
|
||||||
filesSvc.PATCH("/files/chunks", fileHdrs.UploadChunk)
|
filesAPI.PATCH("/files/chunks", fileHdrs.UploadChunk)
|
||||||
filesSvc.GET("/files/chunks", fileHdrs.UploadStatus)
|
filesAPI.GET("/files/chunks", fileHdrs.UploadStatus)
|
||||||
filesSvc.PATCH("/files/copy", fileHdrs.Copy)
|
filesAPI.PATCH("/files/copy", fileHdrs.Copy)
|
||||||
filesSvc.PATCH("/files/move", fileHdrs.Move)
|
filesAPI.PATCH("/files/move", fileHdrs.Move)
|
||||||
|
|
||||||
filesSvc.GET("/dirs", fileHdrs.List)
|
filesAPI.GET("/dirs", fileHdrs.List)
|
||||||
filesSvc.POST("/dirs", fileHdrs.Mkdir)
|
filesAPI.POST("/dirs", fileHdrs.Mkdir)
|
||||||
// files.POST("/dirs/copy", fileHdrs.CopyDir)
|
// files.POST("/dirs/copy", fileHdrs.CopyDir)
|
||||||
|
|
||||||
filesSvc.GET("/metadata", fileHdrs.Metadata)
|
filesAPI.GET("/metadata", fileHdrs.Metadata)
|
||||||
|
|
||||||
|
settingsAPI := v1.Group("/settings")
|
||||||
|
settingsAPI.OPTIONS("/health", settingsSvc.Health)
|
||||||
|
|
||||||
return router, nil
|
return router, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,14 @@ package server
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/client"
|
"github.com/ihexxa/quickshare/src/client"
|
||||||
"github.com/ihexxa/quickshare/src/handlers/fileshdr"
|
"github.com/ihexxa/quickshare/src/handlers/fileshdr"
|
||||||
|
@ -16,7 +19,6 @@ import (
|
||||||
func TestFileHandlers(t *testing.T) {
|
func TestFileHandlers(t *testing.T) {
|
||||||
addr := "http://127.0.0.1:8888"
|
addr := "http://127.0.0.1:8888"
|
||||||
root := "testData"
|
root := "testData"
|
||||||
chunkSize := 2
|
|
||||||
config := `{
|
config := `{
|
||||||
"users": {
|
"users": {
|
||||||
"enableAuth": false
|
"enableAuth": false
|
||||||
|
@ -29,6 +31,7 @@ func TestFileHandlers(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
os.RemoveAll(root)
|
||||||
err := os.MkdirAll(root, 0700)
|
err := os.MkdirAll(root, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -37,17 +40,110 @@ func TestFileHandlers(t *testing.T) {
|
||||||
|
|
||||||
srv := startTestServer(config)
|
srv := startTestServer(config)
|
||||||
defer srv.Shutdown()
|
defer srv.Shutdown()
|
||||||
// kv := srv.depsKVStore()
|
|
||||||
fs := srv.depsFS()
|
fs := srv.depsFS()
|
||||||
cl := client.NewFilesClient(addr)
|
cl := client.NewFilesClient(addr)
|
||||||
|
|
||||||
// TODO: remove this
|
if !waitForReady(addr) {
|
||||||
time.Sleep(500)
|
t.Fatal("fail to start server")
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("test file APIs: Create-UploadChunk-UploadStatus-Metadata-Delete", func(t *testing.T) {
|
assertUploadOK := func(t *testing.T, filePath, content string) bool {
|
||||||
|
cl := client.NewFilesClient(addr)
|
||||||
|
|
||||||
|
fileSize := int64(len([]byte(content)))
|
||||||
|
res, _, errs := cl.Create(filePath, fileSize)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Error(errs)
|
||||||
|
return false
|
||||||
|
} else if res.StatusCode != 200 {
|
||||||
|
t.Error(res.StatusCode)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _, errs = cl.UploadChunk(filePath, content, 0)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Error(errs)
|
||||||
|
return false
|
||||||
|
} else if res.StatusCode != 200 {
|
||||||
|
t.Error(res.StatusCode)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
assetDownloadOK := func(t *testing.T, filePath, content string) bool {
|
||||||
|
var (
|
||||||
|
res *http.Response
|
||||||
|
body string
|
||||||
|
errs []error
|
||||||
|
fileSize = int64(len([]byte(content)))
|
||||||
|
)
|
||||||
|
|
||||||
|
cl := client.NewFilesClient(addr)
|
||||||
|
|
||||||
|
rd := rand.Intn(3)
|
||||||
|
switch rd {
|
||||||
|
case 0:
|
||||||
|
res, body, errs = cl.Download(filePath, map[string]string{})
|
||||||
|
case 1:
|
||||||
|
res, body, errs = cl.Download(filePath, map[string]string{
|
||||||
|
"Range": fmt.Sprintf("bytes=0-%d", fileSize-1),
|
||||||
|
})
|
||||||
|
case 2:
|
||||||
|
res, body, errs = cl.Download(filePath, map[string]string{
|
||||||
|
"Range": fmt.Sprintf("bytes=0-%d, %d-%d", (fileSize-1)/2, (fileSize-1)/2+1, fileSize-1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Error(errs)
|
||||||
|
return false
|
||||||
|
} else if res.StatusCode != 200 && res.StatusCode != 206 {
|
||||||
|
t.Error(res.StatusCode)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch rd {
|
||||||
|
case 0:
|
||||||
|
if body != content {
|
||||||
|
t.Errorf("body not equal got(%s) expect(%s)\n", body, content)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
|
||||||
|
if body[2:] != content { // body returned by gorequest contains the first CRLF
|
||||||
|
t.Errorf("body not equal got(%s) expect(%s)\n", body[2:], content)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
body = body[2:] // body returned by gorequest contains the first CRLF
|
||||||
|
realBody := ""
|
||||||
|
boundaryEnd := strings.Index(body, "\r\n")
|
||||||
|
boundary := body[0:boundaryEnd]
|
||||||
|
bodyParts := strings.Split(body, boundary)
|
||||||
|
|
||||||
|
for i, bodyPart := range bodyParts {
|
||||||
|
if i == 0 || i == len(bodyParts)-1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
start := strings.Index(bodyPart, "\r\n\r\n")
|
||||||
|
|
||||||
|
fmt.Printf("<%s>", bodyPart[start+4:len(bodyPart)-2]) // ignore the last CRLF
|
||||||
|
realBody += bodyPart[start+4 : len(bodyPart)-2]
|
||||||
|
}
|
||||||
|
if realBody != content {
|
||||||
|
t.Errorf("multi body not equal got(%s) expect(%s)\n", realBody, content)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("test files APIs: Create-UploadChunk-UploadStatus-Metadata-Delete", func(t *testing.T) {
|
||||||
for filePath, content := range map[string]string{
|
for filePath, content := range map[string]string{
|
||||||
"path1/f1.md": "11111",
|
"path1/f1.md": "1111 1111 1111 1111",
|
||||||
"path1/path2/f2.md": "101010",
|
"path1/path2/f2.md": "1010 1010 1111 0000 0010",
|
||||||
} {
|
} {
|
||||||
fileSize := int64(len([]byte(content)))
|
fileSize := int64(len([]byte(content)))
|
||||||
// create a file
|
// create a file
|
||||||
|
@ -71,7 +167,7 @@ func TestFileHandlers(t *testing.T) {
|
||||||
i := 0
|
i := 0
|
||||||
contentBytes := []byte(content)
|
contentBytes := []byte(content)
|
||||||
for i < len(contentBytes) {
|
for i < len(contentBytes) {
|
||||||
right := i + chunkSize
|
right := i + rand.Intn(3) + 1
|
||||||
if right > len(contentBytes) {
|
if right > len(contentBytes) {
|
||||||
right = len(contentBytes)
|
right = len(contentBytes)
|
||||||
}
|
}
|
||||||
|
@ -127,7 +223,7 @@ func TestFileHandlers(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test file APIs: Mkdir-Create-UploadChunk-List", func(t *testing.T) {
|
t.Run("test dirs APIs: Mkdir-Create-UploadChunk-List", func(t *testing.T) {
|
||||||
for dirPath, files := range map[string]map[string]string{
|
for dirPath, files := range map[string]map[string]string{
|
||||||
"dir/path1/": map[string]string{
|
"dir/path1/": map[string]string{
|
||||||
"f1.md": "11111",
|
"f1.md": "11111",
|
||||||
|
@ -146,22 +242,7 @@ func TestFileHandlers(t *testing.T) {
|
||||||
|
|
||||||
for fileName, content := range files {
|
for fileName, content := range files {
|
||||||
filePath := filepath.Join(dirPath, fileName)
|
filePath := filepath.Join(dirPath, fileName)
|
||||||
|
assertUploadOK(t, filePath, content)
|
||||||
fileSize := int64(len([]byte(content)))
|
|
||||||
// create a file
|
|
||||||
res, _, errs := cl.Create(filePath, fileSize)
|
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatal(errs)
|
|
||||||
} else if res.StatusCode != 200 {
|
|
||||||
t.Fatal(res.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, errs = cl.UploadChunk(filePath, content, 0)
|
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatal(errs)
|
|
||||||
} else if res.StatusCode != 200 {
|
|
||||||
t.Fatal(res.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, lResp, errs := cl.List(dirPath)
|
_, lResp, errs := cl.List(dirPath)
|
||||||
|
@ -179,7 +260,7 @@ func TestFileHandlers(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test file APIs: Mkdir-Create-UploadChunk-Move-List", func(t *testing.T) {
|
t.Run("test operation APIs: Mkdir-Create-UploadChunk-Move-List", func(t *testing.T) {
|
||||||
srcDir := "move/src"
|
srcDir := "move/src"
|
||||||
dstDir := "move/dst"
|
dstDir := "move/dst"
|
||||||
|
|
||||||
|
@ -200,24 +281,10 @@ func TestFileHandlers(t *testing.T) {
|
||||||
for fileName, content := range files {
|
for fileName, content := range files {
|
||||||
oldPath := filepath.Join(srcDir, fileName)
|
oldPath := filepath.Join(srcDir, fileName)
|
||||||
newPath := filepath.Join(dstDir, fileName)
|
newPath := filepath.Join(dstDir, fileName)
|
||||||
fileSize := int64(len([]byte(content)))
|
// fileSize := int64(len([]byte(content)))
|
||||||
|
assertUploadOK(t, oldPath, content)
|
||||||
|
|
||||||
// create a file
|
res, _, errs := cl.Move(oldPath, newPath)
|
||||||
res, _, errs := cl.Create(oldPath, fileSize)
|
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatal(errs)
|
|
||||||
} else if res.StatusCode != 200 {
|
|
||||||
t.Fatal(res.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, errs = cl.UploadChunk(oldPath, content, 0)
|
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatal(errs)
|
|
||||||
} else if res.StatusCode != 200 {
|
|
||||||
t.Fatal(res.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, errs = cl.Move(oldPath, newPath)
|
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
t.Fatal(errs)
|
t.Fatal(errs)
|
||||||
} else if res.StatusCode != 200 {
|
} else if res.StatusCode != 200 {
|
||||||
|
@ -238,4 +305,69 @@ func TestFileHandlers(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("test download APIs: Download(normal, ranges)", func(t *testing.T) {
|
||||||
|
for filePath, content := range map[string]string{
|
||||||
|
"download/path1/f1": "123456",
|
||||||
|
"download/path1/path2": "12345678",
|
||||||
|
} {
|
||||||
|
assertUploadOK(t, filePath, content)
|
||||||
|
|
||||||
|
err = fs.Sync()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assetDownloadOK(t, filePath, content)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test concurrently uploading & downloading", func(t *testing.T) {
|
||||||
|
type mockFile struct {
|
||||||
|
FilePath string
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
|
startClient := func(files []*mockFile) {
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
for _, file := range files {
|
||||||
|
if !assertUploadOK(t, fmt.Sprintf("%s_%d", file.FilePath, i), file.Content) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fs.Sync()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assetDownloadOK(t, fmt.Sprintf("%s_%d", file.FilePath, i), file.Content) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, clientFiles := range [][]*mockFile{
|
||||||
|
[]*mockFile{
|
||||||
|
&mockFile{"concurrent/p0/f0", "00"},
|
||||||
|
&mockFile{"concurrent/f0.md", "0000 0000 0000 0"},
|
||||||
|
},
|
||||||
|
[]*mockFile{
|
||||||
|
&mockFile{"concurrent/p1/f1", "11"},
|
||||||
|
&mockFile{"concurrent/f1.md", "1111 1111 1"},
|
||||||
|
},
|
||||||
|
[]*mockFile{
|
||||||
|
&mockFile{"concurrent/p2/f2", "22"},
|
||||||
|
&mockFile{"concurrent/f2.md", "222"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
wg.Add(1)
|
||||||
|
go startClient(clientFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package server
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ihexxa/quickshare/src/client"
|
"github.com/ihexxa/quickshare/src/client"
|
||||||
su "github.com/ihexxa/quickshare/src/handlers/singleuserhdr"
|
su "github.com/ihexxa/quickshare/src/handlers/singleuserhdr"
|
||||||
|
@ -29,6 +28,7 @@ func TestSingleUserHandlers(t *testing.T) {
|
||||||
os.Setenv("DEFAULTADMIN", adminName)
|
os.Setenv("DEFAULTADMIN", adminName)
|
||||||
os.Setenv("DEFAULTADMINPWD", adminPwd)
|
os.Setenv("DEFAULTADMINPWD", adminPwd)
|
||||||
|
|
||||||
|
os.RemoveAll(root)
|
||||||
err := os.MkdirAll(root, 0700)
|
err := os.MkdirAll(root, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -39,10 +39,10 @@ func TestSingleUserHandlers(t *testing.T) {
|
||||||
defer srv.Shutdown()
|
defer srv.Shutdown()
|
||||||
|
|
||||||
suCl := client.NewSingleUserClient(addr)
|
suCl := client.NewSingleUserClient(addr)
|
||||||
// fCl := client.NewFilesClient(addr)
|
|
||||||
|
|
||||||
// TODO: remove this
|
if !waitForReady(addr) {
|
||||||
time.Sleep(1000)
|
t.Fatal("fail to start server")
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("test single user APIs: Login-SetPwd-Logout-Login", func(t *testing.T) {
|
t.Run("test single user APIs: Login-SetPwd-Logout-Login", func(t *testing.T) {
|
||||||
resp, _, errs := suCl.Login(adminName, adminPwd)
|
resp, _, errs := suCl.Login(adminName, adminPwd)
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import "github.com/ihexxa/gocfg"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ihexxa/gocfg"
|
||||||
|
"github.com/ihexxa/quickshare/src/client"
|
||||||
|
)
|
||||||
|
|
||||||
func startTestServer(config string) *Server {
|
func startTestServer(config string) *Server {
|
||||||
defaultCfg, err := DefaultConfig()
|
defaultCfg, err := DefaultConfig()
|
||||||
|
@ -25,3 +30,20 @@ func startTestServer(config string) *Server {
|
||||||
go srv.Start()
|
go srv.Start()
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func waitForReady(addr string) bool {
|
||||||
|
retry := 10
|
||||||
|
setCl := client.NewSettingsClient(addr)
|
||||||
|
|
||||||
|
for retry > 0 {
|
||||||
|
_, _, errs := setCl.Health()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
time.Sleep(100)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
retry--
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue