diff --git a/api/graphql/generated.go b/api/graphql/generated.go index e4cb6341..902ea537 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -114,19 +114,20 @@ type ComplexityRoot struct { } Mutation struct { - AuthorizeUser func(childComplexity int, username string, password string) int - CreateUser func(childComplexity int, username string, rootPath string, password *string, admin bool) int - DeleteShareToken func(childComplexity int, token string) int - DeleteUser func(childComplexity int, id int) int - FavoriteMedia func(childComplexity int, mediaID int, favorite bool) int - InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int - ProtectShareToken func(childComplexity int, token string, password *string) int - RegisterUser func(childComplexity int, username string, password string, rootPath string) int - ScanAll func(childComplexity int) int - ScanUser func(childComplexity int, userID int) int - ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int - ShareMedia func(childComplexity int, mediaID int, expire *time.Time, password *string) int - UpdateUser func(childComplexity int, id int, username *string, rootPath *string, password *string, admin *bool) int + AuthorizeUser func(childComplexity int, username string, password string) int + CreateUser func(childComplexity int, username string, rootPath string, password *string, admin bool) int + DeleteShareToken func(childComplexity int, token string) int + DeleteUser func(childComplexity int, id int) int + FavoriteMedia func(childComplexity int, mediaID int, favorite bool) int + InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int + ProtectShareToken func(childComplexity int, token string, password *string) int + RegisterUser func(childComplexity int, username string, password string, rootPath string) int + ScanAll func(childComplexity int) int + ScanUser func(childComplexity int, userID int) int + SetPeriodicScanInterval func(childComplexity int, interval int) int + ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int + ShareMedia func(childComplexity int, mediaID int, expire *time.Time, password *string) int + UpdateUser func(childComplexity int, id int, username *string, rootPath *string, password *string, admin *bool) int } Notification struct { @@ -241,6 +242,7 @@ type MutationResolver interface { UpdateUser(ctx context.Context, id int, username *string, rootPath *string, password *string, admin *bool) (*models.User, error) CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error) DeleteUser(ctx context.Context, id int) (*models.User, error) + SetPeriodicScanInterval(ctx context.Context, interval int) (int, error) } type QueryResolver interface { SiteInfo(ctx context.Context) (*models.SiteInfo, error) @@ -713,6 +715,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.ScanUser(childComplexity, args["userId"].(int)), true + case "Mutation.setPeriodicScanInterval": + if e.complexity.Mutation.SetPeriodicScanInterval == nil { + break + } + + args, err := ec.field_Mutation_setPeriodicScanInterval_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.SetPeriodicScanInterval(childComplexity, args["interval"].(int)), true + case "Mutation.shareAlbum": if e.complexity.Mutation.ShareAlbum == nil { break @@ -1307,6 +1321,12 @@ type Mutation { admin: Boolean! ): User @isAdmin deleteUser(id: Int!): User @isAdmin + + """ + Set how often, in seconds, the server should automatically scan for new media, + a value of 0 will disable periodic scans + """ + setPeriodicScanInterval(interval: Int!): Int! } type Subscription { @@ -1748,6 +1768,21 @@ func (ec *executionContext) field_Mutation_scanUser_args(ctx context.Context, ra return args, nil } +func (ec *executionContext) field_Mutation_setPeriodicScanInterval_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 int + if tmp, ok := rawArgs["interval"]; ok { + ctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithField("interval")) + arg0, err = ec.unmarshalNInt2int(ctx, tmp) + if err != nil { + return nil, err + } + } + args["interval"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_shareAlbum_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -4127,6 +4162,47 @@ func (ec *executionContext) _Mutation_deleteUser(ctx context.Context, field grap return ec.marshalOUser2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_setPeriodicScanInterval(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_setPeriodicScanInterval_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().SetPeriodicScanInterval(rctx, args["interval"].(int)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int) + fc.Result = res + return ec.marshalNInt2int(ctx, field.Selections, res) +} + func (ec *executionContext) _Notification_key(ctx context.Context, field graphql.CollectedField, obj *models.Notification) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -7545,6 +7621,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) out.Values[i] = ec._Mutation_createUser(ctx, field) case "deleteUser": out.Values[i] = ec._Mutation_deleteUser(ctx, field) + case "setPeriodicScanInterval": + out.Values[i] = ec._Mutation_setPeriodicScanInterval(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/api/graphql/resolvers/scanner.go b/api/graphql/resolvers/scanner.go index e57105e1..ee905ece 100644 --- a/api/graphql/resolvers/scanner.go +++ b/api/graphql/resolvers/scanner.go @@ -39,3 +39,23 @@ func (r *mutationResolver) ScanUser(ctx context.Context, userID int) (*models.Sc Message: &startMessage, }, nil } + +func (r *mutationResolver) SetPeriodicScanInterval(ctx context.Context, interval int) (int, error) { + if interval < 0 { + return 0, errors.New("interval must be 0 or above") + } + + _, err := r.Database.Exec("UPDATE site_info SET periodic_scan_interval = ?", interval) + if err != nil { + return 0, err + } + + var dbInterval int + + row := r.Database.QueryRow("SELECT periodic_scan_interval FROM site_info") + if err = row.Scan(&dbInterval); err != nil { + return 0, err + } + + return dbInterval, nil +} diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index b343efa8..d3932e66 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -92,6 +92,12 @@ type Mutation { admin: Boolean! ): User @isAdmin deleteUser(id: Int!): User @isAdmin + + """ + Set how often, in seconds, the server should automatically scan for new media, + a value of 0 will disable periodic scans + """ + setPeriodicScanInterval(interval: Int!): Int! } type Subscription { diff --git a/ui/src/Pages/SettingsPage/ScannerSection.js b/ui/src/Pages/SettingsPage/ScannerSection.js index 6b32bc01..834b731f 100644 --- a/ui/src/Pages/SettingsPage/ScannerSection.js +++ b/ui/src/Pages/SettingsPage/ScannerSection.js @@ -1,6 +1,13 @@ import React, { useState } from 'react' -import { Button, Checkbox, Dropdown, Icon, Input } from 'semantic-ui-react' +import { + Button, + Checkbox, + Dropdown, + Icon, + Input, + Loader, +} from 'semantic-ui-react' import { useMutation, useQuery } from 'react-apollo' import gql from 'graphql-tag' import styled from 'styled-components' @@ -23,6 +30,12 @@ const SCAN_INTERVAL_QUERY = gql` } ` +const SCAN_INTERVAL_MUTATION = gql` + mutation changeScanIntervalMutation($interval: Int!) { + setPeriodicScanInterval(interval: $interval) + } +` + const timeUnits = [ { value: 'second', @@ -46,6 +59,10 @@ const timeUnits = [ }, ] +const convertToSeconds = ({ value, unit }) => { + return parseInt(value * timeUnits.find(x => x.value == unit).multiplier) +} + const convertToAppropriateUnit = ({ value, unit }) => { if (value == 0) { return { @@ -54,7 +71,7 @@ const convertToAppropriateUnit = ({ value, unit }) => { } } - const seconds = value * timeUnits.find(x => x.value == unit).multiplier + const seconds = convertToSeconds({ value, unit }) let resultingUnit = timeUnits.first for (const unit of timeUnits) { @@ -113,6 +130,29 @@ const ScannerSection = () => { }, }) + const [ + setScanIntervalMutation, + { loading: scanIntervalMutationLoading }, + ] = useMutation(SCAN_INTERVAL_MUTATION) + + const onScanIntervalCheckboxChange = checked => { + setEnablePeriodicScanner(checked) + + setScanIntervalMutation({ + variables: { + interval: checked ? convertToSeconds(scanInterval) : 0, + }, + }) + } + + const onScanIntervalUpdate = scanInterval => { + setScanIntervalMutation({ + variables: { + interval: convertToSeconds(scanInterval), + }, + }) + } + const scanIntervalUnits = [ { key: 'second', @@ -163,7 +203,7 @@ const ScannerSection = () => { label="Enable periodic scanner" disabled={scanIntervalQuery.loading} checked={enablePeriodicScanner} - onChange={(_, { checked }) => setEnablePeriodicScanner(checked)} + onChange={(_, { checked }) => onScanIntervalCheckboxChange(checked)} /> @@ -178,25 +218,43 @@ const ScannerSection = () => { - setScanInterval(x => ({ - ...x, + onChange={(_, { value }) => { + const newScanInterval = { + ...scanInterval, unit: value, - })) - } + } + + setScanInterval(newScanInterval) + onScanIntervalUpdate(newScanInterval) + }} value={scanInterval.unit} options={scanIntervalUnits} /> } + onBlur={() => onScanIntervalUpdate(scanInterval)} + onKeyDown={({ key }) => + key == 'Enter' && onScanIntervalUpdate(scanInterval) + } loading={scanIntervalQuery.loading} labelPosition="right" style={{ maxWidth: 300 }} id="periodic_scan_field" value={scanInterval.value} - onChange={e => setScanInterval(e.target.value)} + onChange={(_, { value }) => { + setScanInterval(x => ({ + ...x, + value, + })) + }} /> )} + ) }