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 @@
-
+
{{log.type}} - {{log.message}}
diff --git a/ui/v1/src/app/settings/settings/settings.component.ts b/ui/v1/src/app/settings/settings/settings.component.ts index 24de0d54e..f78a7cb18 100644 --- a/ui/v1/src/app/settings/settings/settings.component.ts +++ b/ui/v1/src/app/settings/settings/settings.component.ts @@ -17,13 +17,13 @@ export class SettingsComponent implements OnInit, OnDestroy { constructor(private stashService: StashService) {} ngOnInit() { - // this.statusObservable = this.stashService.metadataUpdate().subscribe(response => { - // const result = JSON.parse(response.data.metadataUpdate); + this.statusObservable = this.stashService.metadataUpdate().subscribe(response => { + const result = JSON.parse(response.data.metadataUpdate); - // this.progress = result.progress; - // this.message = result.message; - // this.logs = result.logs; - // }); + this.progress = result.progress; + this.message = result.message; + this.logs = result.logs; + }); } ngOnDestroy() { diff --git a/ui/v1/src/styles.scss b/ui/v1/src/styles.scss index bb4c6e8f4..8dd232c1f 100644 --- a/ui/v1/src/styles.scss +++ b/ui/v1/src/styles.scss @@ -1,5 +1,10 @@ /* You can add global styles to this file, and also import other style files */ +/* TODO */ +.ui.progress .bar { + display: none !important; +} + body { background-color: #000000; } @@ -420,4 +425,8 @@ sui-select.ui.dropdown { .ui.dark.card > .extra { border-top: 1px solid #555 !important; color: inherit; +} + +sui-progress .label { + color: white !important; } \ No newline at end of file diff --git a/ui/v1/yarn.lock b/ui/v1/yarn.lock index 21a67024c..685123b2f 100644 --- a/ui/v1/yarn.lock +++ b/ui/v1/yarn.lock @@ -587,11 +587,6 @@ acorn@^5.0.0, acorn@^5.6.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== -actioncable@5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/actioncable/-/actioncable-5.2.2.tgz#0ec83195fd363c4d91a2fc19f78e77350aa5caac" - integrity sha512-uZveOQ0OL1z8VwYqA84gMMfLMPHpS/GsnodS8oVh1xTUYeMf1L80gAbTVVVVVj84zUf7lOiIZMaiY0IlXNsgiw== - agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -791,7 +786,14 @@ apollo-link-http-common@^0.2.8: dependencies: apollo-link "^1.2.6" -apollo-link@1.2.6, apollo-link@^1.0.0, apollo-link@^1.0.3, apollo-link@^1.2.6: +apollo-link-ws@1.0.14: + version "1.0.14" + resolved "https://registry.yarnpkg.com/apollo-link-ws/-/apollo-link-ws-1.0.14.tgz#588f898b7f953930a27e283941614d89907adf13" + integrity sha512-KwHVnhKKDUA5PmmzpiqkyahjBcwGdf2eFlTZg4DIwgH1R0KcBmn/A6rkZnmClBbUNgV6t+kR46dW2fyx64Jm3A== + dependencies: + apollo-link "^1.2.8" + +apollo-link@1.2.6, apollo-link@^1.0.0, apollo-link@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.6.tgz#d9b5676d79c01eb4e424b95c7171697f6ad2b8da" integrity sha512-sUNlA20nqIF3gG3F8eyMD+mO80fmf3dPZX+GUOs3MI9oZR8ug09H3F0UsWJMcpEg6h55Yy5wZ+BMmAjrbenF/Q== @@ -799,7 +801,7 @@ apollo-link@1.2.6, apollo-link@^1.0.0, apollo-link@^1.0.3, apollo-link@^1.2.6: apollo-utilities "^1.0.0" zen-observable-ts "^0.8.13" -apollo-link@^1.2.3: +apollo-link@^1.2.3, apollo-link@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.8.tgz#0f252adefd5047ac1a9f35ba9439d216587dcd84" integrity sha512-lfzGRxhK9RmiH3HPFi7TIEBhhDY9M5a2ZDnllcfy5QDk7cCQHQ1WQArcw1FK0g1B+mV4Kl72DSrlvZHZJEolrA== @@ -951,6 +953,11 @@ async-foreach@^0.1.3: resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== + async@1.x, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -1088,6 +1095,11 @@ babylon@^6.18.0: resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== +backo2@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -2455,7 +2467,7 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -eventemitter3@^3.0.0: +eventemitter3@^3.0.0, eventemitter3@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== @@ -3225,16 +3237,6 @@ graphql-request@^1.5.0: dependencies: cross-fetch "2.2.2" -graphql-ruby-client@1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/graphql-ruby-client/-/graphql-ruby-client-1.6.3.tgz#c9ee277c7a17d443b48cd23bf9357e294bdcddca" - integrity sha512-EW4SBDCYefZ3BfxEkbv20T69xdceDx+yTrx4KlwpBe726RSuiGcwIQylCA9S9YlmPF+JabrgOaqS08GboFpuhg== - dependencies: - apollo-link "^1.0.3" - glob "^7.1.2" - graphql "^0.10.5" - minimist "^1.2.0" - graphql-tag-pluck@0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/graphql-tag-pluck/-/graphql-tag-pluck-0.4.4.tgz#4494c0e13d10aa9a36cea2704218940a1af47170" @@ -3274,13 +3276,6 @@ graphql@14.1.1: dependencies: iterall "^1.2.2" -graphql@^0.10.5: - version "0.10.5" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.10.5.tgz#c9be17ca2bdfdbd134077ffd9bbaa48b8becd298" - integrity sha512-Q7cx22DiLhwHsEfUnUip1Ww/Vfx7FS0w6+iHItNuN61+XpegHSa3k5U0+6M5BcpavQImBwFiy0z3uYwY7cXMLQ== - dependencies: - iterall "^1.1.0" - handle-thing@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" @@ -4038,7 +4033,7 @@ istanbul@0.4.5: which "^1.1.1" wordwrap "^1.0.0" -iterall@^1.1.0, iterall@^1.1.3, iterall@^1.2.2: +iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== @@ -6957,6 +6952,17 @@ stylus@0.54.5: sax "0.5.x" source-map "0.1.x" +subscriptions-transport-ws@0.9.15: + version "0.9.15" + resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.15.tgz#68a8b7ba0037d8c489fb2f5a102d1494db297d0d" + integrity sha512-f9eBfWdHsePQV67QIX+VRhf++dn1adyC/PZHP6XI5AfKnZ4n0FW+v5omxwdHVpd4xq2ZijaHEcmlQrhBY79ZWQ== + dependencies: + backo2 "^1.0.2" + eventemitter3 "^3.1.0" + iterall "^1.2.1" + symbol-observable "^1.0.4" + ws "^5.2.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -6996,7 +7002,7 @@ symbol-observable@1.0.1: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= -symbol-observable@1.2.0, symbol-observable@^1.0.2, symbol-observable@^1.1.0: +symbol-observable@1.2.0, symbol-observable@^1.0.2, symbol-observable@^1.0.4, symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -7683,6 +7689,13 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + xregexp@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020"