Merge branch 'master' of github.com:viktorstrate/photoview into component-class-to-function-migration
This commit is contained in:
commit
24ea00d9ec
|
@ -6,7 +6,7 @@ updates:
|
|||
- package-ecosystem: "npm"
|
||||
directory: "/ui"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "ui (npm)"
|
||||
labels:
|
||||
|
@ -17,7 +17,7 @@ updates:
|
|||
- package-ecosystem: "gomod"
|
||||
directory: "/api"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "api (gomod)"
|
||||
labels:
|
||||
|
|
|
@ -4,7 +4,7 @@ go 1.13
|
|||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.13.0
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.15 // indirect
|
||||
github.com/agnivade/levenshtein v1.1.0 // indirect
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
|
@ -25,9 +25,10 @@ require (
|
|||
github.com/vektah/gqlparser/v2 v2.1.0
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
||||
github.com/xor-gate/goexif2 v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d // indirect
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd // indirect
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.0.2
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
)
|
||||
|
|
10
api/go.sum
10
api/go.sum
|
@ -3,6 +3,8 @@ github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyy
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.15 h1:qkLXKzb1QoVatRyd/YlXZ/Kg0m5K3SPuoD82jjSOaBc=
|
||||
github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
|
||||
|
@ -120,6 +122,8 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rB
|
|||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
|
@ -135,17 +139,23 @@ golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOL
|
|||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d h1:dOiJ2n2cMwGLce/74I/QHMbnpk5GfY7InR8rczoMqRM=
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190222072716-a9d3bda3a223/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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
|
@ -2,8 +2,6 @@ package routes
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -49,7 +47,6 @@ func RegisterPhotoRoutes(db *sql.DB, router *mux.Router) {
|
|||
}
|
||||
|
||||
var cachedPath string
|
||||
var file *os.File = nil
|
||||
|
||||
if mediaUrl.Purpose == models.PhotoThumbnail || mediaUrl.Purpose == models.PhotoHighRes || mediaUrl.Purpose == models.VideoThumbnail {
|
||||
cachedPath = path.Join(scanner.PhotoCache(), strconv.Itoa(media.AlbumId), strconv.Itoa(mediaUrl.MediaId), mediaUrl.MediaName)
|
||||
|
@ -62,48 +59,40 @@ func RegisterPhotoRoutes(db *sql.DB, router *mux.Router) {
|
|||
return
|
||||
}
|
||||
|
||||
file, err = os.Open(cachedPath)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = scanner.ProcessMedia(tx, media)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: processing image not found in cache: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
file, err = os.Open(cachedPath)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: after reprocessing image not found in cache: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
_, err = os.Stat(cachedPath)
|
||||
if os.IsNotExist((err)) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", mediaUrl.ContentType)
|
||||
if stats, err := file.Stat(); err == nil {
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", stats.Size()))
|
||||
_, err = scanner.ProcessMedia(tx, media)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: processing image not found in cache: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
_, err = os.Stat(cachedPath)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: after reprocessing image not found in cache: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
}
|
||||
|
||||
// Allow caching the resource for 1 day
|
||||
w.Header().Set("Cache-Control", "private, max-age=86400, immutable")
|
||||
|
||||
io.Copy(w, file)
|
||||
http.ServeFile(w, r, cachedPath)
|
||||
})
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,22 +5,21 @@
|
|||
"description": "UI app for Photoview",
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.12.1",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react": "^16.9.56",
|
||||
"babel-plugin-styled-components": "^1.11.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"downloadjs": "^1.4.7",
|
||||
"graphql": "^14.7.0",
|
||||
"graphql": "^15.4.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-lazyload": "^3.1.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-spring": "^8.0.27",
|
||||
"semantic-ui-css": "^2.4.1",
|
||||
"semantic-ui-react": "^2.0.1",
|
||||
"styled-components": "^5.2.0",
|
||||
"styled-components": "^5.2.1",
|
||||
"subscriptions-transport-ws": "^0.9.18",
|
||||
"url-join": "^4.0.1"
|
||||
},
|
||||
|
@ -36,21 +35,21 @@
|
|||
"@babel/core": "^7.12.3",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.12.1",
|
||||
"@babel/plugin-transform-runtime": "^7.12.1",
|
||||
"@babel/preset-react": "^7.12.1",
|
||||
"@babel/preset-react": "^7.12.5",
|
||||
"@testing-library/jest-dom": "^5.11.5",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/react": "^11.1.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.6.1",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-plugin-graphql-tag": "^3.1.0",
|
||||
"babel-plugin-transform-semantic-ui-react-imports": "^1.4.1",
|
||||
"eslint": "^7.12.0",
|
||||
"eslint-plugin-jest": "^24.1.0",
|
||||
"eslint": "^7.13.0",
|
||||
"eslint-plugin-jest": "^24.1.3",
|
||||
"eslint-plugin-jest-dom": "^3.2.4",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"husky": "^4.3.0",
|
||||
"jest": "^26.6.1",
|
||||
"lint-staged": "^10.5.0",
|
||||
"jest": "^26.6.3",
|
||||
"lint-staged": "^10.5.1",
|
||||
"mapbox-gl": "^1.12.0",
|
||||
"parcel-plugin-sw-cache": "^0.3.1",
|
||||
"prettier": "^2.1.2",
|
||||
|
|
|
@ -7,11 +7,13 @@ import { MemoryRouter, Route } from 'react-router-dom'
|
|||
|
||||
import * as authentication from '../../authentication'
|
||||
|
||||
jest.mock('../../authentication.js')
|
||||
|
||||
describe('AuthorizedRoute component', () => {
|
||||
const AuthorizedComponent = () => <div>authorized content</div>
|
||||
|
||||
test('not logged in', async () => {
|
||||
authentication.authToken = jest.fn(() => null)
|
||||
authentication.authToken.mockImplementation(() => null)
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/']}>
|
||||
|
@ -24,7 +26,7 @@ describe('AuthorizedRoute component', () => {
|
|||
})
|
||||
|
||||
test('logged in', async () => {
|
||||
authentication.authToken = jest.fn(() => 'token-here')
|
||||
authentication.authToken.mockImplementation(() => 'token-here')
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/']}>
|
||||
|
@ -34,5 +36,6 @@ describe('AuthorizedRoute component', () => {
|
|||
)
|
||||
|
||||
expect(screen.getByText('authorized content')).toBeInTheDocument()
|
||||
expect(screen.queryByText('login redirect')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -4,7 +4,6 @@ import { Table } from 'semantic-ui-react'
|
|||
import styled from 'styled-components'
|
||||
import { MessageState } from '../messages/Messages'
|
||||
import { useLazyQuery, gql } from '@apollo/client'
|
||||
import download from 'downloadjs'
|
||||
import { authToken } from '../../authentication'
|
||||
|
||||
export const SIDEBAR_DOWNLOAD_QUERY = gql`
|
||||
|
@ -31,11 +30,8 @@ function formatBytes(bytes) {
|
|||
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
const downloadPhoto = async url => {
|
||||
const downloadMedia = async url => {
|
||||
const imgUrl = new URL(url)
|
||||
// let headers = {
|
||||
// Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
// }
|
||||
|
||||
if (authToken() == null) {
|
||||
// Get share token if not authorized
|
||||
|
@ -43,29 +39,26 @@ const downloadPhoto = async url => {
|
|||
if (token) {
|
||||
imgUrl.searchParams.set('token', token[1])
|
||||
}
|
||||
|
||||
// headers = {}
|
||||
}
|
||||
|
||||
const response = await fetch(imgUrl.href, {
|
||||
// headers,
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
const totalBytes = Number(response.headers.get('content-length'))
|
||||
|
||||
if (totalBytes == 0) {
|
||||
MessageState.add({
|
||||
key: Math.random().toString(26),
|
||||
type: 'message',
|
||||
props: {
|
||||
header: 'Error downloading photo',
|
||||
content: `Could not get size of photo from server`,
|
||||
negative: true,
|
||||
},
|
||||
})
|
||||
return
|
||||
let blob = null
|
||||
if (response.headers.has('content-length')) {
|
||||
blob = await downloadMediaShowProgress(response)
|
||||
} else {
|
||||
blob = await response.blob()
|
||||
}
|
||||
|
||||
const filename = url.match(/[^/]*$/)[0]
|
||||
|
||||
downloadBlob(blob, filename)
|
||||
}
|
||||
|
||||
const downloadMediaShowProgress = async response => {
|
||||
const totalBytes = Number(response.headers.get('content-length'))
|
||||
const reader = response.body.getReader()
|
||||
let data = new Uint8Array(totalBytes)
|
||||
|
||||
|
@ -134,9 +127,23 @@ const downloadPhoto = async url => {
|
|||
const content = new Blob([data.buffer], {
|
||||
type: response.headers.get('content-type'),
|
||||
})
|
||||
const filename = url.match(/[^/]*$/)[0]
|
||||
|
||||
download(content, filename)
|
||||
return content
|
||||
}
|
||||
|
||||
const downloadBlob = async (blob, filename) => {
|
||||
let objectUrl = window.URL.createObjectURL(blob)
|
||||
|
||||
let anchor = document.createElement('a')
|
||||
document.body.appendChild(anchor)
|
||||
|
||||
anchor.href = objectUrl
|
||||
anchor.download = filename
|
||||
anchor.click()
|
||||
|
||||
anchor.remove()
|
||||
|
||||
window.URL.revokeObjectURL(objectUrl)
|
||||
}
|
||||
|
||||
const DownloadTableRow = styled(Table.Row)`
|
||||
|
@ -172,7 +179,7 @@ const SidebarDownload = ({ photo }) => {
|
|||
let downloadRows = downloads.map(x => (
|
||||
<DownloadTableRow
|
||||
key={x.mediaUrl.url}
|
||||
onClick={() => downloadPhoto(x.mediaUrl.url)}
|
||||
onClick={() => downloadMedia(x.mediaUrl.url, photo.title)}
|
||||
>
|
||||
<Table.Cell>{`${x.title}`}</Table.Cell>
|
||||
<Table.Cell>{`${x.mediaUrl.width} x ${x.mediaUrl.height}`}</Table.Cell>
|
||||
|
|
Loading…
Reference in New Issue