diff --git a/api/resolver_subscription_metadata.go b/api/resolver_subscription_metadata.go index 72365800f..2b736009c 100644 --- a/api/resolver_subscription_metadata.go +++ b/api/resolver_subscription_metadata.go @@ -1,7 +1,30 @@ package api -import "context" +import ( + "context" + "github.com/stashapp/stash/logger" + "github.com/stashapp/stash/manager" + "time" +) func (r *subscriptionResolver) MetadataUpdate(ctx context.Context) (<-chan string, error) { - panic("not implemented") + msg := make(chan string, 1) + + ticker := time.NewTicker(5 * time.Second) + + go func() { + for { + select { + case t := <-ticker.C: + logger.Trace("metadata subscription tick at %s", t) + manager.GetInstance().HandleMetadataUpdateSubscriptionTick(msg) + case <-ctx.Done(): + ticker.Stop() + close(msg) + return + } + } + }() + + return msg, nil } \ No newline at end of file diff --git a/api/server.go b/api/server.go index bbedcba69..3556f3777 100644 --- a/api/server.go +++ b/api/server.go @@ -9,6 +9,7 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/gobuffalo/packr/v2" + "github.com/gorilla/websocket" "github.com/rs/cors" "github.com/stashapp/stash/logger" "github.com/stashapp/stash/manager" @@ -61,7 +62,12 @@ func Start() { //api.GetRequestContext(ctx).Variables[] return next(ctx) }) - gqlHandler := handler.GraphQL(models.NewExecutableSchema(models.Config{Resolvers: &Resolver{}}), recoverFunc, requestMiddleware) + websocketUpgrader := handler.WebsocketUpgrader(websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + }) + gqlHandler := handler.GraphQL(models.NewExecutableSchema(models.Config{Resolvers: &Resolver{}}), recoverFunc, requestMiddleware, websocketUpgrader) // https://stash.server:9999/certs/server.crt r.Handle("/certs/*", http.FileServer(certsBox)) diff --git a/go.mod b/go.mod index cf5a0ba38..797bd2038 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/go-chi/chi v4.0.1+incompatible github.com/gobuffalo/packr/v2 v2.0.0-rc.15 github.com/golang-migrate/migrate/v4 v4.2.2 + github.com/gorilla/websocket v1.2.0 github.com/h2non/filetype v1.0.6 github.com/jmoiron/sqlx v1.2.0 github.com/mattn/go-sqlite3 v1.9.0 diff --git a/logger/logger.go b/logger/logger.go index 99754d0b2..de9a822b1 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -1,50 +1,119 @@ package logger import ( + "fmt" "github.com/sirupsen/logrus" + "sync" ) +type LogItem struct { + Type string `json:"type"` + Message string `json:"message"` +} + var logger = logrus.New() var progressLogger = logrus.New() +var LogCache []LogItem +var mutex = &sync.Mutex{} + +func addLogItem(l *LogItem) { + mutex.Lock() + LogCache = append([]LogItem{*l}, LogCache...) + if len(LogCache) > 30 { + LogCache = LogCache[:len(LogCache)-1] + } + mutex.Unlock() +} + func init() { progressLogger.SetFormatter(new(ProgressFormatter)) } func Progressf(format string, args ...interface{}) { progressLogger.Infof(format, args...) + l := &LogItem{ + Type: "progress", + Message: fmt.Sprintf(format, args...), + } + addLogItem(l) + +} + +func Trace(args ...interface{}) { + logger.Trace(args...) } func Debug(args ...interface{}) { logger.Debug(args...) + l := &LogItem{ + Type: "debug", + Message: fmt.Sprint(args), + } + addLogItem(l) } func Debugf(format string, args ...interface{}) { logger.Debugf(format, args...) + l := &LogItem{ + Type: "debug", + Message: fmt.Sprintf(format, args...), + } + addLogItem(l) } func Info(args ...interface{}) { logger.Info(args...) + l := &LogItem{ + Type: "info", + Message: fmt.Sprint(args), + } + addLogItem(l) } func Infof(format string, args ...interface{}) { logger.Infof(format, args...) + l := &LogItem{ + Type: "info", + Message: fmt.Sprintf(format, args...), + } + addLogItem(l) } func Warn(args ...interface{}) { logger.Warn(args...) + l := &LogItem{ + Type: "warn", + Message: fmt.Sprint(args), + } + addLogItem(l) } func Warnf(format string, args ...interface{}) { logger.Warnf(format, args...) + l := &LogItem{ + Type: "warn", + Message: fmt.Sprintf(format, args...), + } + addLogItem(l) } func Error(args ...interface{}) { logger.Error(args...) + l := &LogItem{ + Type: "error", + Message: fmt.Sprint(args), + } + addLogItem(l) } func Errorf(format string, args ...interface{}) { logger.Errorf(format, args...) + l := &LogItem{ + Type: "error", + Message: fmt.Sprintf(format, args...), + } + addLogItem(l) } func Fatal(args ...interface{}) { diff --git a/manager/manager_subscription_handler.go b/manager/manager_subscription_handler.go new file mode 100644 index 000000000..6a9718657 --- /dev/null +++ b/manager/manager_subscription_handler.go @@ -0,0 +1,36 @@ +package manager + +import ( + "encoding/json" + "github.com/stashapp/stash/logger" +) + +type metadataUpdatePayload struct { + Progress float64 `json:"progress"` + Message string `json:"message"` + Logs []logger.LogItem `json:"logs"` +} + +func (s *singleton) HandleMetadataUpdateSubscriptionTick(msg chan string) { + var statusMessage string + switch instance.Status { + case Idle: + statusMessage = "Idle" + case Import: + statusMessage = "Import" + case Export: + statusMessage = "Export" + case Scan: + statusMessage = "Scan" + case Generate: + statusMessage = "Generate" + } + payload := &metadataUpdatePayload{ + Progress: 0, // TODO + Message: statusMessage, + Logs: logger.LogCache, + } + payloadJSON, _ := json.Marshal(payload) + + msg <- string(payloadJSON) +} diff --git a/ui/v1/package.json b/ui/v1/package.json index 0c569e6b7..b1b7e8c61 100644 --- a/ui/v1/package.json +++ b/ui/v1/package.json @@ -23,13 +23,13 @@ "@angular/platform-browser": "7.2.1", "@angular/platform-browser-dynamic": "7.2.1", "@angular/router": "7.2.1", - "actioncable": "5.2.2", "apollo-angular": "1.5.0", "apollo-angular-link-http": "1.4.0", "apollo-cache-inmemory": "1.4.2", "apollo-client": "2.4.12", "apollo-link": "1.2.6", "apollo-link-error": "1.1.5", + "apollo-link-ws": "1.0.14", "core-js": "2.6.2", "graphql": "14.1.1", "graphql-code-generator": "0.15.2", @@ -41,7 +41,6 @@ "graphql-codegen-typescript-common": "0.15.2", "graphql-codegen-typescript-resolvers": "0.15.2", "graphql-codegen-typescript-server": "0.15.2", - "graphql-ruby-client": "1.6.3", "graphql-tag": "2.10.1", "ng-lazyload-image": "5.0.0", "ng2-semantic-ui": "0.9.7", @@ -49,6 +48,7 @@ "ngx-pagination": "3.2.1", "rxjs": "6.3.3", "rxjs-compat": "6.3.3", + "subscriptions-transport-ws": "0.9.15", "zone.js": "0.8.28" }, "devDependencies": { diff --git a/ui/v1/src/app/core/stash.service.ts b/ui/v1/src/app/core/stash.service.ts index 3c0d149a6..9e3a10d48 100644 --- a/ui/v1/src/app/core/stash.service.ts +++ b/ui/v1/src/app/core/stash.service.ts @@ -10,10 +10,8 @@ import { onError } from 'apollo-link-error'; import { ApolloLink } from 'apollo-link'; import { getMainDefinition } from 'apollo-utilities'; -import * as ActionCable from 'actioncable'; -import * as ActionCableLink from 'graphql-ruby-client/subscriptions/ActionCableLink'; - import * as GQL from './graphql-generated'; +import {WebSocketLink} from "apollo-link-ws"; @Injectable() export class StashService { @@ -53,6 +51,7 @@ export class StashService { private metadataScanGQL = new GQL.MetadataScanGQL(this.apollo); private metadataGenerateGQL = new GQL.MetadataGenerateGQL(this.apollo); private metadataCleanGQL = new GQL.MetadataCleanGQL(this.apollo); + private metadataUpdateGQL = new GQL.MetadataUpdateGQL(this.apollo); constructor(private apollo: Apollo, private platformLocation: PlatformLocation, private httpLink: HttpLink) { const platform: any = platformLocation; @@ -60,9 +59,12 @@ export class StashService { platformUrl.port = platformUrl.protocol === 'https:' ? '9999' : '9998'; const url = platformUrl.toString().slice(0, -1); - // http://graphql-ruby.org/javascript_client/apollo_subscriptions - const cable = ActionCable.createConsumer(`ws://${platform.location.hostname}:3000/subscriptions`); - const actionCableLink = new ActionCableLink({cable}); + const wsLink = new WebSocketLink({ + uri: `ws://${platform.location.hostname}:${platformUrl.port}/graphql`, + options: { + reconnect: true + } + }); const errorLink = onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) { @@ -86,7 +88,7 @@ export class StashService { const definition = getMainDefinition(query); return definition.kind === 'OperationDefinition' && definition.operation === 'subscription'; }, - actionCableLink, + wsLink, httpLinkHandler ); @@ -499,9 +501,7 @@ export class StashService { } metadataUpdate() { - // return this.apollo.subscribe({ - // query: METADATA_UPDATE_SUBSCRIPTION - // }); + return this.metadataUpdateGQL.subscribe() } } diff --git a/ui/v1/src/app/settings/settings/settings.component.html b/ui/v1/src/app/settings/settings/settings.component.html index 45690c2c5..bf850f466 100644 --- a/ui/v1/src/app/settings/settings/settings.component.html +++ b/ui/v1/src/app/settings/settings/settings.component.html @@ -13,7 +13,7 @@
-