diff --git a/api/_routers/01-install_metadata.go b/api/_routers/01-install_metadata.go index 2ab5b29b9d347ddebc2740399966cef5cc0615c1..8a9102e8c8de083eb1e52b28462a943ed5e05687 100644 --- a/api/_routers/01-install_metadata.go +++ b/api/_routers/01-install_metadata.go @@ -67,14 +67,6 @@ func (i *InstallMetadataRouter) ServeHTTP(w http.ResponseWriter, r *http.Request } } -func GetRequestId(r *http.Request) string { - x, ok := r.Context().Value(requestIdCtxKey).(string) - if !ok { - return "REQ-ID-UNKNOWN" - } - return x -} - func GetActionName(r *http.Request) string { x, ok := r.Context().Value(actionNameCtxKey).(string) if !ok { diff --git a/api/_routers/97-optional-access-token.go b/api/_routers/97-optional-access-token.go index 517ab346cd6e1d3901e34296f1786c0c12661be8..bbddbfdd70339b8eca58d806e160d8c019c90410 100644 --- a/api/_routers/97-optional-access-token.go +++ b/api/_routers/97-optional-access-token.go @@ -1,6 +1,7 @@ package _routers import ( + "errors" "net/http" "github.com/getsentry/sentry-go" @@ -35,7 +36,7 @@ func OptionalAccessToken(generator GeneratorWithUserFn) GeneratorFn { appserviceUserId := util.GetAppserviceUserIdFromRequest(r) userId, err := _auth_cache.GetUserId(ctx, accessToken, appserviceUserId) if err != nil { - if err != matrix.ErrInvalidToken { + if !errors.Is(err, matrix.ErrInvalidToken) { sentry.CaptureException(err) ctx.Log.Error("Error verifying token: ", err) return _responses.InternalServerError("unexpected error validating access token") diff --git a/api/_routers/97-require-access-token.go b/api/_routers/97-require-access-token.go index 8d4b1fecf3c1baa89603896c41734c3e15e7dd81..00880271dc96b66c1ac73758e87e32475ddde5dd 100644 --- a/api/_routers/97-require-access-token.go +++ b/api/_routers/97-require-access-token.go @@ -1,6 +1,7 @@ package _routers import ( + "errors" "net/http" "github.com/getsentry/sentry-go" @@ -38,10 +39,10 @@ func RequireAccessToken(generator GeneratorWithUserFn) GeneratorFn { appserviceUserId := util.GetAppserviceUserIdFromRequest(r) userId, err := _auth_cache.GetUserId(ctx, accessToken, appserviceUserId) if err != nil || userId == "" { - if err == matrix.ErrGuestToken { + if errors.Is(err, matrix.ErrGuestToken) { return _responses.GuestAuthFailed() } - if err != nil && err != matrix.ErrInvalidToken { + if err != nil && !errors.Is(err, matrix.ErrInvalidToken) { sentry.CaptureException(err) ctx.Log.Error("Error verifying token: ", err) return _responses.InternalServerError("unexpected error validating access token") diff --git a/api/custom/imports.go b/api/custom/imports.go index 20846616bdd323d300c273fb63a5ca0b12ff25d5..7faec339b767c5e2d60be0c3fa547ef8764f4e2c 100644 --- a/api/custom/imports.go +++ b/api/custom/imports.go @@ -1,6 +1,7 @@ package custom import ( + "errors" "net/http" "github.com/getsentry/sentry-go" @@ -64,7 +65,7 @@ func AppendToImport(r *http.Request, rctx rcontext.RequestContext, user _apimeta err := task_runner.AppendImportFile(rctx, importId, r.Body) if err != nil { - if err == common.ErrMediaNotFound { + if errors.Is(err, common.ErrMediaNotFound) { return _responses.NotFoundError() } rctx.Log.Error(err) @@ -91,7 +92,7 @@ func StopImport(r *http.Request, rctx rcontext.RequestContext, user _apimeta.Use err := task_runner.FinishImport(rctx, importId) if err != nil { - if err == common.ErrMediaNotFound { + if errors.Is(err, common.ErrMediaNotFound) { return _responses.NotFoundError() } rctx.Log.Error(err) diff --git a/api/custom/purge.go b/api/custom/purge.go index 4d4ff3b90da29f71a07c11d7f286aaac617155d7..319401ce6f1a58823f7206e3bd3f084076535637 100644 --- a/api/custom/purge.go +++ b/api/custom/purge.go @@ -1,6 +1,7 @@ package custom import ( + "errors" "net/http" "strconv" @@ -69,7 +70,7 @@ func PurgeIndividualRecord(r *http.Request, rctx rcontext.RequestContext, user _ }, }) if err != nil { - if err == common.ErrWrongUser { + if errors.Is(err, common.ErrWrongUser) { return _responses.AuthFailed() } rctx.Log.Error(err) @@ -104,7 +105,7 @@ func PurgeQuarantined(r *http.Request, rctx rcontext.RequestContext, user _apime DbMedia: affected, }) if err != nil { - if err == common.ErrWrongUser { + if errors.Is(err, common.ErrWrongUser) { return _responses.AuthFailed() } rctx.Log.Error(err) @@ -157,7 +158,7 @@ func PurgeOldMedia(r *http.Request, rctx rcontext.RequestContext, user _apimeta. DbMedia: records, }) if err != nil { - if err == common.ErrWrongUser { + if errors.Is(err, common.ErrWrongUser) { return _responses.AuthFailed() } rctx.Log.Error(err) @@ -214,7 +215,7 @@ func PurgeUserMedia(r *http.Request, rctx rcontext.RequestContext, user _apimeta DbMedia: records, }) if err != nil { - if err == common.ErrWrongUser { + if errors.Is(err, common.ErrWrongUser) { return _responses.AuthFailed() } rctx.Log.Error(err) @@ -287,7 +288,7 @@ func PurgeRoomMedia(r *http.Request, rctx rcontext.RequestContext, user _apimeta MxcUris: mxcs, }) if err != nil { - if err == common.ErrWrongUser { + if errors.Is(err, common.ErrWrongUser) { return _responses.AuthFailed() } rctx.Log.Error(err) @@ -341,7 +342,7 @@ func PurgeDomainMedia(r *http.Request, rctx rcontext.RequestContext, user _apime DbMedia: records, }) if err != nil { - if err == common.ErrWrongUser { + if errors.Is(err, common.ErrWrongUser) { return _responses.AuthFailed() } rctx.Log.Error(err) diff --git a/api/r0/download.go b/api/r0/download.go index af4a23e158cd49e6ab89194e0b3f002148feab42..a948f22a8b7576cc35b659183c49ed42bdad528e 100644 --- a/api/r0/download.go +++ b/api/r0/download.go @@ -1,6 +1,7 @@ package r0 import ( + "errors" "net/http" "strconv" @@ -69,18 +70,18 @@ func DownloadMedia(r *http.Request, rctx rcontext.RequestContext, user _apimeta. BlockForReadUntil: blockFor, }) if err != nil { - if err == common.ErrMediaNotFound { + if errors.Is(err, common.ErrMediaNotFound) { return _responses.NotFoundError() - } else if err == common.ErrMediaTooLarge { + } else if errors.Is(err, common.ErrMediaTooLarge) { return _responses.RequestTooLarge() - } else if err == common.ErrMediaQuarantined { + } else if errors.Is(err, common.ErrMediaQuarantined) { rctx.Log.Debug("Quarantined media accessed. Has stream? ", stream != nil) if stream != nil { return _responses.MakeQuarantinedImageResponse(stream) } else { return _responses.NotFoundError() // We lie for security } - } else if err == common.ErrMediaNotYetUploaded { + } else if errors.Is(err, common.ErrMediaNotYetUploaded) { return _responses.NotYetUploaded() } rctx.Log.Error("Unexpected error locating media: ", err) diff --git a/api/r0/identicon.go b/api/r0/identicon.go index 07d307becfeeeea80d295a90ac4c6dcaf8dec044..751947d61d556c9ff10ebb6a68122be4ba444cf0 100644 --- a/api/r0/identicon.go +++ b/api/r0/identicon.go @@ -80,7 +80,7 @@ func Identicon(r *http.Request, rctx rcontext.RequestContext, user _apimeta.User } rctx.Log.Info("Generating identicon") - img := sig.Make(width, false, []byte(hashed)) + img := sig.Make(width, false, hashed) if width != height { // Resize to the desired height rctx.Log.Info("Resizing image to fit height") diff --git a/api/r0/preview_url.go b/api/r0/preview_url.go index 0d7963e2a52e15600dd443156028c3db904a03f2..ba2503fdb74d4500f7c6726eef0e36aabb20be71 100644 --- a/api/r0/preview_url.go +++ b/api/r0/preview_url.go @@ -53,6 +53,7 @@ func PreviewUrl(r *http.Request, rctx rcontext.RequestContext, user _apimeta.Use if urlStr == "" { return _responses.BadRequest("No url provided") } + //goland:noinspection HttpUrlsUsage if strings.Index(urlStr, "http://") != 0 && strings.Index(urlStr, "https://") != 0 { return _responses.BadRequest("Scheme not accepted") } @@ -76,9 +77,9 @@ func PreviewUrl(r *http.Request, rctx rcontext.RequestContext, user _apimeta.Use } } if err != nil { - if err == common.ErrMediaNotFound || err == common.ErrHostNotFound { + if errors.Is(err, common.ErrMediaNotFound) || errors.Is(err, common.ErrHostNotFound) { return _responses.NotFoundError() - } else if err == common.ErrInvalidHost || err == common.ErrHostNotAllowed { + } else if errors.Is(err, common.ErrInvalidHost) || errors.Is(err, common.ErrHostNotAllowed) { return _responses.BadRequest(err.Error()) } else { sentry.CaptureException(err) diff --git a/api/r0/thumbnail.go b/api/r0/thumbnail.go index eee357cc7785347ee00f61b7ecec81bdc0c7be7e..2b4bb43c3e42020328b28387f82f3d6d69637fca 100644 --- a/api/r0/thumbnail.go +++ b/api/r0/thumbnail.go @@ -1,6 +1,7 @@ package r0 import ( + "errors" "net/http" "strconv" @@ -118,18 +119,18 @@ func ThumbnailMedia(r *http.Request, rctx rcontext.RequestContext, user _apimeta Animated: animated, }) if err != nil { - if err == common.ErrMediaNotFound { + if errors.Is(err, common.ErrMediaNotFound) { return _responses.NotFoundError() - } else if err == common.ErrMediaTooLarge { + } else if errors.Is(err, common.ErrMediaTooLarge) { return _responses.RequestTooLarge() - } else if err == common.ErrMediaQuarantined { + } else if errors.Is(err, common.ErrMediaQuarantined) { rctx.Log.Debug("Quarantined media accessed. Has stream? ", stream != nil) if stream != nil { return _responses.MakeQuarantinedImageResponse(stream) } else { return _responses.NotFoundError() // We lie for security } - } else if err == common.ErrMediaNotYetUploaded { + } else if errors.Is(err, common.ErrMediaNotYetUploaded) { return _responses.NotYetUploaded() } rctx.Log.Error("Unexpected error locating media: ", err) diff --git a/api/r0/upload_async.go b/api/r0/upload_async.go index 8d0bd6f31f967568177bf1c92422ad6154d61f3c..1e0e11ab3f3fae45372d6a61e0c90af436a5c633 100644 --- a/api/r0/upload_async.go +++ b/api/r0/upload_async.go @@ -1,6 +1,7 @@ package r0 import ( + "errors" "net/http" "path/filepath" @@ -47,21 +48,21 @@ func UploadMediaAsync(r *http.Request, rctx rcontext.RequestContext, user _apime // Actually upload media, err := pipeline_upload.ExecutePut(rctx, server, mediaId, r.Body, contentType, filename, user.UserId) if err != nil { - if err == common.ErrQuotaExceeded { + if errors.Is(err, common.ErrQuotaExceeded) { return _responses.QuotaExceeded() - } else if err == common.ErrAlreadyUploaded { + } else if errors.Is(err, common.ErrAlreadyUploaded) { return _responses.ErrorResponse{ Code: common.ErrCodeCannotOverwrite, Message: "This media has already been uploaded.", InternalCode: common.ErrCodeCannotOverwrite, } - } else if err == common.ErrWrongUser { + } else if errors.Is(err, common.ErrWrongUser) { return _responses.ErrorResponse{ Code: common.ErrCodeForbidden, Message: "You do not have permission to upload this media.", InternalCode: common.ErrCodeForbidden, } - } else if err == common.ErrExpired { + } else if errors.Is(err, common.ErrExpired) { return _responses.ErrorResponse{ Code: common.ErrCodeNotFound, Message: "Media expired or not found.", diff --git a/api/r0/upload_sync.go b/api/r0/upload_sync.go index b8cb7f326436a7b16ee9e9511f496d1d18d84c8d..e0ca2629d54a4a0f50a600de92c3ea501a4eb662 100644 --- a/api/r0/upload_sync.go +++ b/api/r0/upload_sync.go @@ -1,6 +1,7 @@ package r0 import ( + "errors" "net/http" "path/filepath" "strconv" @@ -42,7 +43,7 @@ func UploadMediaSync(r *http.Request, rctx rcontext.RequestContext, user _apimet // Actually upload media, err := pipeline_upload.Execute(rctx, r.Host, "", r.Body, contentType, filename, user.UserId, datastores.LocalMediaKind) if err != nil { - if err == common.ErrQuotaExceeded { + if errors.Is(err, common.ErrQuotaExceeded) { return _responses.QuotaExceeded() } rctx.Log.Error("Unexpected error uploading media: ", err) diff --git a/api/router.go b/api/router.go index 79430516b02c7bc0ebcbe45cc9ae1ade9dc8ba43..ebc6c8ac1916e7a5e8ddabf975c36e7d1478bf4d 100644 --- a/api/router.go +++ b/api/router.go @@ -57,6 +57,7 @@ func finishCorsFn(w http.ResponseWriter, r *http.Request) { func panicFn(w http.ResponseWriter, r *http.Request, i interface{}) { logrus.Errorf("Panic received on %s %s: %s", r.Method, util.GetLogSafeUrl(r), i) + //goland:noinspection GoTypeAssertionOnErrors if e, ok := i.(error); ok { sentry.CaptureException(e) } else { diff --git a/api/unstable/info.go b/api/unstable/info.go index b14171c268a075718d22d778a366a4fa85575a8f..7335c8ed9f71788767b361db1ec3bf814634f5b5 100644 --- a/api/unstable/info.go +++ b/api/unstable/info.go @@ -1,6 +1,7 @@ package unstable import ( + "errors" "net/http" "strconv" "strings" @@ -85,18 +86,18 @@ func MediaInfo(r *http.Request, rctx rcontext.RequestContext, user _apimeta.User }) // Error handling copied from download endpoint if err != nil { - if err == common.ErrMediaNotFound { + if errors.Is(err, common.ErrMediaNotFound) { return _responses.NotFoundError() - } else if err == common.ErrMediaTooLarge { + } else if errors.Is(err, common.ErrMediaTooLarge) { return _responses.RequestTooLarge() - } else if err == common.ErrMediaQuarantined { + } else if errors.Is(err, common.ErrMediaQuarantined) { rctx.Log.Debug("Quarantined media accessed. Has stream? ", stream != nil) if stream != nil { return _responses.MakeQuarantinedImageResponse(stream) } else { return _responses.NotFoundError() // We lie for security } - } else if err == common.ErrMediaNotYetUploaded { + } else if errors.Is(err, common.ErrMediaNotYetUploaded) { return _responses.NotYetUploaded() } rctx.Log.Error("Unexpected error locating media: ", err) diff --git a/api/unstable/local_copy.go b/api/unstable/local_copy.go index c22f34deab488eac369eeeda6b185248443d4655..55f28c357199bd5edde95b5ee5508873e8344304 100644 --- a/api/unstable/local_copy.go +++ b/api/unstable/local_copy.go @@ -1,6 +1,7 @@ package unstable import ( + "errors" "net/http" "strconv" "time" @@ -66,18 +67,18 @@ func LocalCopy(r *http.Request, rctx rcontext.RequestContext, user _apimeta.User }) // Error handling copied from download endpoint if err != nil { - if err == common.ErrMediaNotFound { + if errors.Is(err, common.ErrMediaNotFound) { return _responses.NotFoundError() - } else if err == common.ErrMediaTooLarge { + } else if errors.Is(err, common.ErrMediaTooLarge) { return _responses.RequestTooLarge() - } else if err == common.ErrMediaQuarantined { + } else if errors.Is(err, common.ErrMediaQuarantined) { rctx.Log.Debug("Quarantined media accessed. Has stream? ", stream != nil) if stream != nil { return _responses.MakeQuarantinedImageResponse(stream) } else { return _responses.NotFoundError() // We lie for security } - } else if err == common.ErrMediaNotYetUploaded { + } else if errors.Is(err, common.ErrMediaNotYetUploaded) { return _responses.NotYetUploaded() } rctx.Log.Error("Unexpected error locating media: ", err) @@ -88,7 +89,7 @@ func LocalCopy(r *http.Request, rctx rcontext.RequestContext, user _apimeta.User record, err = pipeline_upload.Execute(rctx, server, mediaId, stream, record.ContentType, record.UploadName, user.UserId, datastores.LocalMediaKind) // Error handling copied from upload(sync) endpoint if err != nil { - if err == common.ErrQuotaExceeded { + if errors.Is(err, common.ErrQuotaExceeded) { return _responses.QuotaExceeded() } rctx.Log.Error("Unexpected error uploading media: ", err) diff --git a/api/webserver.go b/api/webserver.go index ed9ae05c4ee4602fe468acc785dae4083662b21a..a2c7fd59f666214b3ab12e04fbcba9e2017b059b 100644 --- a/api/webserver.go +++ b/api/webserver.go @@ -3,6 +3,7 @@ package api import ( "context" "encoding/json" + "errors" "net" "net/http" "strconv" @@ -53,8 +54,9 @@ func Init() *sync.WaitGroup { reload = false go func() { + //goland:noinspection HttpUrlsUsage logrus.WithField("address", address).Info("Started up. Listening at http://" + address) - if err := srv.ListenAndServe(); err != http.ErrServerClosed { + if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { sentry.CaptureException(err) logrus.Fatal(err) } diff --git a/archival/entity_export.go b/archival/entity_export.go index 262c28d4759647215933774125347a193f7e493b..0d8330b3f67bcb496d0b7579d81a2c013cc15d31 100644 --- a/archival/entity_export.go +++ b/archival/entity_export.go @@ -1,6 +1,7 @@ package archival import ( + "errors" "time" "github.com/turt2live/matrix-media-repo/archival/v2archive" @@ -46,10 +47,10 @@ func ExportEntityData(ctx rcontext.RequestContext, exportId string, entityId str BlockForReadUntil: 1 * time.Minute, RecordOnly: false, }) - if err == common.ErrMediaQuarantined { + if errors.Is(err, common.ErrMediaQuarantined) { ctx.Log.Warnf("%s is quarantined and will not be included in the export", mxc) continue - } else if err == common.ErrMediaNotYetUploaded { + } else if errors.Is(err, common.ErrMediaNotYetUploaded) { ctx.Log.Debug("Media not uploaded yet - skipping") continue } else if err != nil { diff --git a/common/LEGACY_media_kinds.go b/common/LEGACY_media_kinds.go deleted file mode 100644 index a439543469637d3079037e35153238fced6d143e..0000000000000000000000000000000000000000 --- a/common/LEGACY_media_kinds.go +++ /dev/null @@ -1,22 +0,0 @@ -package common - -type Kind string - -const KindLocalMedia Kind = "local_media" -const KindRemoteMedia Kind = "remote_media" -const KindThumbnails Kind = "thumbnails" -const KindArchives Kind = "archives" -const KindAll Kind = "all" - -func IsKind(have Kind, want Kind) bool { - return have == want || have == KindAll -} - -func HasKind(have []string, want Kind) bool { - for _, k := range have { - if IsKind(Kind(k), want) { - return true - } - } - return false -} diff --git a/common/errorcodes.go b/common/errorcodes.go index 5d4d7b10035c82c2005f0db6d42072c2c755e2a0..2ba7fca2a5fff03d74a286e6175539fd9cb02cdd 100644 --- a/common/errorcodes.go +++ b/common/errorcodes.go @@ -1,8 +1,6 @@ package common const ErrCodeInvalidHost = "M_INVALID_HOST" -const ErrCodeHostNotFound = "M_HOST_NOT_FOUND" -const ErrCodeHostNotAllowed = "M_HOST_NOT_ALLOWED" const ErrCodeNotFound = "M_NOT_FOUND" const ErrCodeUnknownToken = "M_UNKNOWN_TOKEN" const ErrCodeNoGuests = "M_GUEST_ACCESS_FORBIDDEN" diff --git a/common/globals/LEGACY_singleflight_groups.go b/common/globals/LEGACY_singleflight_groups.go deleted file mode 100644 index 4f5fd266f599d11dd9a17cc8bf715572b01bc9be..0000000000000000000000000000000000000000 --- a/common/globals/LEGACY_singleflight_groups.go +++ /dev/null @@ -1,7 +0,0 @@ -package globals - -import ( - "github.com/turt2live/matrix-media-repo/util/singleflight-counter" -) - -var DefaultRequestGroup singleflight_counter.Group diff --git a/controllers/LEGACY.txt b/controllers/LEGACY.txt deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/controllers/data_controller/export_controller.go b/controllers/data_controller/export_controller.go deleted file mode 100644 index 95e2053d6e856f28a260df46921a6f22614c7f74..0000000000000000000000000000000000000000 --- a/controllers/data_controller/export_controller.go +++ /dev/null @@ -1,397 +0,0 @@ -package data_controller - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "encoding/json" - "fmt" - "io" - "time" - - "github.com/getsentry/sentry-go" - "github.com/turt2live/matrix-media-repo/util/ids" - "github.com/turt2live/matrix-media-repo/util/stream_util" - - "github.com/dustin/go-humanize" - "github.com/turt2live/matrix-media-repo/common" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/storage" - "github.com/turt2live/matrix-media-repo/storage/datastore" - "github.com/turt2live/matrix-media-repo/storage/datastore/ds_s3" - "github.com/turt2live/matrix-media-repo/templating" - "github.com/turt2live/matrix-media-repo/types" - "github.com/turt2live/matrix-media-repo/util" -) - -type ManifestRecord struct { - FileName string `json:"name"` - ArchivedName string `json:"file_name"` - SizeBytes int64 `json:"size_bytes"` - ContentType string `json:"content_type"` - S3Url string `json:"s3_url"` - Sha256 string `json:"sha256"` - Origin string `json:"origin"` - MediaId string `json:"media_id"` - CreatedTs int64 `json:"created_ts"` - Uploader string `json:"uploader"` -} - -type Manifest struct { - Version int `json:"version"` - EntityId string `json:"entity_id"` - CreatedTs int64 `json:"created_ts"` - Media map[string]*ManifestRecord `json:"media"` - - // Deprecated: for v1 manifests - UserId string `json:"user_id,omitempty"` -} - -func StartServerExport(serverName string, s3urls bool, includeData bool, ctx rcontext.RequestContext) (*types.BackgroundTask, string, error) { - exportId, err := ids.NewUniqueId() - if err != nil { - return nil, "", err - } - - db := storage.GetDatabase().GetMetadataStore(ctx) - task, err := db.CreateBackgroundTask("export_data", map[string]interface{}{ - "server_name": serverName, - "include_s3_urls": s3urls, - "include_data": includeData, - "export_id": exportId, - }) - - if err != nil { - return nil, "", err - } - - go func() { - // Use a new context in the goroutine - ctx.Context = context.Background() - db := storage.GetDatabase().GetMetadataStore(ctx) - - ds, err := datastore.PickDatastore(string(common.KindArchives), ctx) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - mediaDb := storage.GetDatabase().GetMediaStore(ctx) - media, err := mediaDb.GetAllMediaForServer(serverName) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - compileArchive(exportId, serverName, ds, media, s3urls, includeData, ctx) - - ctx.Log.Info("Finishing export task") - err = db.FinishedBackgroundTask(task.ID) - if err != nil { - ctx.Log.Error(err) - ctx.Log.Error("Failed to flag task as finished") - sentry.CaptureException(err) - } - ctx.Log.Info("Finished export") - }() - - return task, exportId, nil -} - -func StartUserExport(userId string, s3urls bool, includeData bool, ctx rcontext.RequestContext) (*types.BackgroundTask, string, error) { - exportId, err := ids.NewUniqueId() - if err != nil { - return nil, "", err - } - - db := storage.GetDatabase().GetMetadataStore(ctx) - task, err := db.CreateBackgroundTask("export_data", map[string]interface{}{ - "user_id": userId, - "include_s3_urls": s3urls, - "include_data": includeData, - "export_id": exportId, - }) - - if err != nil { - return nil, "", err - } - - go func() { - // Use a new context in the goroutine - ctx.Context = context.Background() - db := storage.GetDatabase().GetMetadataStore(ctx) - - ds, err := datastore.PickDatastore(string(common.KindArchives), ctx) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - mediaDb := storage.GetDatabase().GetMediaStore(ctx) - media, err := mediaDb.GetMediaByUser(userId) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - compileArchive(exportId, userId, ds, media, s3urls, includeData, ctx) - - ctx.Log.Info("Finishing export task") - err = db.FinishedBackgroundTask(task.ID) - if err != nil { - ctx.Log.Error(err) - ctx.Log.Error("Failed to flag task as finished") - sentry.CaptureException(err) - } - ctx.Log.Info("Finished export") - }() - - return task, exportId, nil -} - -func compileArchive(exportId string, entityId string, archiveDs *datastore.DatastoreRef, media []*types.Media, s3urls bool, includeData bool, ctx rcontext.RequestContext) { - exportDb := storage.GetDatabase().GetExportStore(ctx) - err := exportDb.InsertExport(exportId, entityId) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - var currentTar *tar.Writer - var currentTarBytes bytes.Buffer - part := 0 - parts := make([]*types.ObjectInfo, 0) - currentSize := int64(0) - - persistTar := func() error { - _ = currentTar.Close() - - // compress - ctx.Log.Info("Compressing tar file") - gzipBytes := bytes.Buffer{} - archiver := gzip.NewWriter(&gzipBytes) - archiver.Name = fmt.Sprintf("export-part-%d.tar", part) - _, err := io.Copy(archiver, stream_util.BufferToStream(bytes.NewBuffer(currentTarBytes.Bytes()))) - if err != nil { - return err - } - _ = archiver.Close() - - ctx.Log.Info("Uploading compressed tar file") - buf := bytes.NewBuffer(gzipBytes.Bytes()) - size := int64(buf.Len()) - obj, err := archiveDs.UploadFile(stream_util.BufferToStream(buf), size, ctx) - if err != nil { - return err - } - parts = append(parts, obj) - - fname := fmt.Sprintf("export-part-%d.tgz", part) - err = exportDb.InsertExportPart(exportId, part, size, fname, archiveDs.DatastoreId, obj.Location) - if err != nil { - return err - } - - return nil - } - - newTar := func() error { - if part > 0 { - ctx.Log.Info("Persisting complete tar file") - err := persistTar() - if err != nil { - return err - } - } - - ctx.Log.Info("Starting new tar file") - currentTarBytes = bytes.Buffer{} - currentTar = tar.NewWriter(¤tTarBytes) - part = part + 1 - currentSize = 0 - - return nil - } - - // Start the first tar file - ctx.Log.Info("Creating first tar file") - err = newTar() - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - putFile := func(name string, size int64, creationTime time.Time, file io.Reader) error { - header := &tar.Header{ - Name: name, - Size: size, - Mode: int64(0644), - ModTime: creationTime, - } - err := currentTar.WriteHeader(header) - if err != nil { - ctx.Log.Error("error writing header") - return err - } - - i, err := io.Copy(currentTar, file) - if err != nil { - ctx.Log.Error("error writing file") - return err - } - - currentSize += i - - return nil - } - - archivedName := func(m *types.Media) string { - // TODO: Pick the right extension for the file type - return fmt.Sprintf("%s__%s.obj", m.Origin, m.MediaId) - } - - // Build a manifest first (JSON) - ctx.Log.Info("Building manifest") - indexModel := &templating.ExportIndexModel{ - Entity: entityId, - ExportID: exportId, - Media: make([]*templating.ExportIndexMediaModel, 0), - } - mediaManifest := make(map[string]*ManifestRecord) - for _, m := range media { - var s3url string - if s3urls { - s3url, err = ds_s3.GetS3URL(m.DatastoreId, m.Location) - if err != nil { - ctx.Log.Warn(err) - } - } - mediaManifest[m.MxcUri()] = &ManifestRecord{ - ArchivedName: archivedName(m), - FileName: m.UploadName, - SizeBytes: m.SizeBytes, - ContentType: m.ContentType, - S3Url: s3url, - Sha256: m.Sha256Hash, - Origin: m.Origin, - MediaId: m.MediaId, - CreatedTs: m.CreationTs, - Uploader: m.UserId, - } - indexModel.Media = append(indexModel.Media, &templating.ExportIndexMediaModel{ - ExportID: exportId, - ArchivedName: archivedName(m), - FileName: m.UploadName, - SizeBytes: m.SizeBytes, - SizeBytesHuman: humanize.Bytes(uint64(m.SizeBytes)), - Origin: m.Origin, - MediaID: m.MediaId, - Sha256Hash: m.Sha256Hash, - ContentType: m.ContentType, - UploadTs: m.CreationTs, - UploadDateHuman: util.FromMillis(m.CreationTs).Format(time.UnixDate), - Uploader: m.UserId, - }) - } - manifest := &Manifest{ - Version: 2, - EntityId: entityId, - CreatedTs: util.NowMillis(), - Media: mediaManifest, - } - b, err := json.Marshal(manifest) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - ctx.Log.Info("Writing manifest") - err = putFile("manifest.json", int64(len(b)), time.Now(), stream_util.BufferToStream(bytes.NewBuffer(b))) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - if includeData { - ctx.Log.Info("Building and writing index") - t, err := templating.GetTemplate("export_index") - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - html := bytes.Buffer{} - err = t.Execute(&html, indexModel) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - err = putFile("index.html", int64(html.Len()), time.Now(), stream_util.BufferToStream(bytes.NewBuffer(html.Bytes()))) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - ctx.Log.Info("Including data in the archive") - for _, m := range media { - ctx.Log.Info("Downloading ", m.MxcUri()) - s, err := datastore.DownloadStream(ctx, m.DatastoreId, m.Location) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - continue - } - - ctx.Log.Infof("Copying %s to memory", m.MxcUri()) - b := bytes.Buffer{} - _, err = io.Copy(&b, s) - if err != nil { - ctx.Log.Error(err) - stream_util.DumpAndCloseStream(s) - sentry.CaptureException(err) - continue - } - stream_util.DumpAndCloseStream(s) - s = stream_util.BufferToStream(bytes.NewBuffer(b.Bytes())) - - ctx.Log.Info("Archiving ", m.MxcUri()) - err = putFile(archivedName(m), m.SizeBytes, time.Unix(0, m.CreationTs*int64(time.Millisecond)), s) - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - - if currentSize >= ctx.Config.Archiving.TargetBytesPerPart { - ctx.Log.Info("Rotating tar") - err = newTar() - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - } - } - } - - if currentSize > 0 { - ctx.Log.Info("Persisting last tar") - err = persistTar() - if err != nil { - ctx.Log.Error(err) - sentry.CaptureException(err) - return - } - } -} diff --git a/controllers/data_controller/import_controller.go b/controllers/data_controller/import_controller.go deleted file mode 100644 index a8bd419a511ebe6f2af8330b243fece81a09a6bc..0000000000000000000000000000000000000000 --- a/controllers/data_controller/import_controller.go +++ /dev/null @@ -1,414 +0,0 @@ -package data_controller - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "database/sql" - "encoding/json" - "errors" - "io" - "net/http" - "sync" - - "github.com/getsentry/sentry-go" - "github.com/turt2live/matrix-media-repo/util/ids" - "github.com/turt2live/matrix-media-repo/util/stream_util" - - "github.com/turt2live/matrix-media-repo/common" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/controllers/upload_controller" - "github.com/turt2live/matrix-media-repo/storage" - "github.com/turt2live/matrix-media-repo/storage/datastore" - "github.com/turt2live/matrix-media-repo/storage/datastore/ds_s3" - "github.com/turt2live/matrix-media-repo/types" - "github.com/turt2live/matrix-media-repo/util" -) - -type importUpdate struct { - stop bool - fileMap map[string]*bytes.Buffer - onDoneChan chan bool -} - -var openImports = &sync.Map{} // importId => updateChan - -func StartImport(data io.Reader, ctx rcontext.RequestContext) (*types.BackgroundTask, string, error) { - // Prepare the first update for the import (sync, so we can error) - // We do this before anything else because if the archive is invalid then we shouldn't - // even bother with an import. - results, err := processArchive(data) - if err != nil { - return nil, "", err - } - - importId, err := ids.NewUniqueId() - if err != nil { - return nil, "", err - } - - db := storage.GetDatabase().GetMetadataStore(ctx) - task, err := db.CreateBackgroundTask("import_data", map[string]interface{}{ - "import_id": importId, - }) - - if err != nil { - return nil, "", err - } - - // Start the import and send it its first update - updateChan := make(chan *importUpdate) - go doImport(updateChan, task.ID, importId, ctx) - openImports.Store(importId, updateChan) - updateChan <- &importUpdate{stop: false, fileMap: results} - - return task, importId, nil -} - -func AppendToImport(importId string, data io.Reader, withReturnChan bool) (chan bool, error) { - runningImport, ok := openImports.Load(importId) - if !ok || runningImport == nil { - return nil, errors.New("import not found or it has been closed") - } - - results, err := processArchive(data) - if err != nil { - return nil, err - } - - // Repeat the safety check - the archive processing can take a bit - runningImport, ok = openImports.Load(importId) - if !ok || runningImport == nil { - return nil, nil - } - - var doneChan chan bool - if withReturnChan { - doneChan = make(chan bool) - } - updateChan := runningImport.(chan *importUpdate) - updateChan <- &importUpdate{stop: false, fileMap: results, onDoneChan: doneChan} - - return doneChan, nil -} - -func StopImport(importId string) error { - runningImport, ok := openImports.Load(importId) - if !ok || runningImport == nil { - return errors.New("import not found or it has been closed") - } - - updateChan := runningImport.(chan *importUpdate) - updateChan <- &importUpdate{stop: true, fileMap: make(map[string]*bytes.Buffer)} - - return nil -} - -func processArchive(data io.Reader) (map[string]*bytes.Buffer, error) { - archiver, err := gzip.NewReader(data) - if err != nil { - return nil, err - } - - defer archiver.Close() - - tarFile := tar.NewReader(archiver) - index := make(map[string]*bytes.Buffer) - for { - header, err := tarFile.Next() - if err == io.EOF { - break // we're done - } - if err != nil { - return nil, err - } - - if header == nil { - continue // skip this weird file - } - if header.Typeflag != tar.TypeReg { - continue // skip directories and other stuff - } - - // Copy the file into our index - buf := &bytes.Buffer{} - _, err = io.Copy(buf, tarFile) - if err != nil { - return nil, err - } - buf = bytes.NewBuffer(buf.Bytes()) // clone to reset reader position - index[header.Name] = buf - } - - return index, nil -} - -func doImport(updateChannel chan *importUpdate, taskId int, importId string, ctx rcontext.RequestContext) { - defer close(updateChannel) - - // Use a new context in the goroutine - ctx.Context = context.Background() - - ctx.Log.Info("Preparing for import...") - fileMap := make(map[string]*bytes.Buffer) - stopImport := false - archiveManifest := &Manifest{} - haveManifest := false - imported := make(map[string]bool) - db := storage.GetDatabase().GetMediaStore(ctx) - var update *importUpdate - - for !stopImport { - if update != nil && update.onDoneChan != nil { - ctx.Log.Info("Flagging tar as completed") - update.onDoneChan <- true - } - update = <-updateChannel - if update.stop { - ctx.Log.Info("Close requested") - stopImport = true - } - - // Populate files - for name, fileBytes := range update.fileMap { - if _, ok := fileMap[name]; ok { - ctx.Log.Warnf("Duplicate file name, skipping: %s", name) - continue // file already known to us - } - ctx.Log.Infof("Tracking file: %s", name) - fileMap[name] = fileBytes - } - - var manifestBuf *bytes.Buffer - var ok bool - if manifestBuf, ok = fileMap["manifest.json"]; !ok { - ctx.Log.Info("No manifest found - waiting for more files") - continue - } - - if !haveManifest { - haveManifest = true - err := json.Unmarshal(manifestBuf.Bytes(), archiveManifest) - if err != nil { - ctx.Log.Error("Failed to parse manifest - giving up on import") - ctx.Log.Error(err) - sentry.CaptureException(err) - break - } - if archiveManifest.Version != 1 && archiveManifest.Version != 2 { - ctx.Log.Error("Unsupported archive version") - break - } - if archiveManifest.Version == 1 { - archiveManifest.EntityId = archiveManifest.UserId - } - if archiveManifest.EntityId == "" { - ctx.Log.Error("Invalid manifest: no entity") - break - } - if archiveManifest.Media == nil { - ctx.Log.Error("Invalid manifest: no media") - break - } - ctx.Log.Infof("Using manifest for %s (v%d) created %d", archiveManifest.EntityId, archiveManifest.Version, archiveManifest.CreatedTs) - } - - if !haveManifest { - // Without a manifest we can't import anything - continue - } - - toClear := make([]string, 0) - doClear := true - for mxc, record := range archiveManifest.Media { - _, found := imported[mxc] - if found { - continue // already imported - } - - _, err := db.Get(record.Origin, record.MediaId) - if err == nil { - ctx.Log.Info("Media already imported: " + record.Origin + "/" + record.MediaId) - - // flag as imported and move on - imported[mxc] = true - continue - } - - userId := archiveManifest.EntityId - if userId[0] != '@' { - userId = "" // assume none for now - } - - kind := common.KindLocalMedia - serverName := archiveManifest.EntityId - if userId != "" { - _, s, err := util.SplitUserId(userId) - if err != nil { - ctx.Log.Errorf("Invalid user ID: %s", userId) - serverName = "" - } else { - serverName = s - } - } - if !util.IsServerOurs(serverName) { - kind = common.KindRemoteMedia - } - - ctx.Log.Infof("Attempting to import %s for %s", mxc, archiveManifest.EntityId) - buf, found := fileMap[record.ArchivedName] - if found { - ctx.Log.Info("Using file from memory") - closer := stream_util.BufferToStream(buf) - _, err := upload_controller.StoreDirect(nil, closer, record.SizeBytes, record.ContentType, record.FileName, userId, record.Origin, record.MediaId, kind, ctx, true) - if err != nil { - ctx.Log.Errorf("Error importing file: %s", err.Error()) - doClear = false // don't clear things on error - sentry.CaptureException(err) - continue - } - toClear = append(toClear, record.ArchivedName) - } else if record.S3Url != "" { - ctx.Log.Info("Using S3 URL") - endpoint, bucket, location, err := ds_s3.ParseS3URL(record.S3Url) - if err != nil { - ctx.Log.Errorf("Error importing file: %s", err.Error()) - sentry.CaptureException(err) - continue - } - - ctx.Log.Infof("Seeing if a datastore for %s/%s exists", endpoint, bucket) - datastores, err := datastore.GetAvailableDatastores(ctx) - if err != nil { - ctx.Log.Errorf("Error locating datastore: %s", err.Error()) - sentry.CaptureException(err) - continue - } - imported := false - for _, ds := range datastores { - if ds.Type != "s3" { - continue - } - - tmplUrl, err := ds_s3.GetS3URL(ds.DatastoreId, location) - if err != nil { - ctx.Log.Errorf("Error investigating s3 datastore: %s", err.Error()) - sentry.CaptureException(err) - continue - } - if tmplUrl == record.S3Url { - ctx.Log.Infof("File matches! Assuming the file has been uploaded already") - - existingRecord, err := db.Get(record.Origin, record.MediaId) - if err != nil && err != sql.ErrNoRows { - ctx.Log.Errorf("Error testing file in database: %s", err.Error()) - sentry.CaptureException(err) - break - } - if err != sql.ErrNoRows && existingRecord != nil { - ctx.Log.Warnf("Media %s already exists - skipping without altering record", existingRecord.MxcUri()) - imported = true - break - } - - // Use the user ID (if any) as the uploader as a default. If this is an import - // for a server then we use the recorded one, if any is available. - uploader := userId - if userId == "" { - uploader = record.Uploader - } - media := &types.Media{ - Origin: record.Origin, - MediaId: record.MediaId, - UploadName: record.FileName, - ContentType: record.ContentType, - UserId: uploader, - Sha256Hash: record.Sha256, - SizeBytes: record.SizeBytes, - DatastoreId: ds.DatastoreId, - Location: location, - CreationTs: record.CreatedTs, - } - - err = db.Insert(media) - if err != nil { - ctx.Log.Errorf("Error creating media record: %s", err.Error()) - sentry.CaptureException(err) - break - } - - ctx.Log.Infof("Media %s has been imported", media.MxcUri()) - imported = true - break - } - } - - if !imported { - ctx.Log.Info("No datastore found - trying to upload by downloading first") - r, err := http.DefaultClient.Get(record.S3Url) - if err != nil { - ctx.Log.Errorf("Error trying to download file from S3 via HTTP: ", err.Error()) - sentry.CaptureException(err) - continue - } - - _, err = upload_controller.StoreDirect(nil, r.Body, r.ContentLength, record.ContentType, record.FileName, userId, record.Origin, record.MediaId, kind, ctx, true) - if err != nil { - ctx.Log.Errorf("Error importing file: %s", err.Error()) - sentry.CaptureException(err) - continue - } - } - } else { - ctx.Log.Warn("Missing usable file for import - assuming it will show up in a future upload") - continue - } - - ctx.Log.Info("Counting file as imported") - imported[mxc] = true - } - - if doClear { - ctx.Log.Info("Clearing up memory for imported files...") - for _, f := range toClear { - ctx.Log.Infof("Removing %s from memory", f) - delete(fileMap, f) - } - } - - ctx.Log.Info("Checking for any unimported files...") - missingAny := false - for mxc, _ := range archiveManifest.Media { - _, found := imported[mxc] - if found { - continue // already imported - } - missingAny = true - break - } - - if !missingAny { - ctx.Log.Info("No more files to import - closing import") - stopImport = true - } - } - - // Clean up the last tar file - if update != nil && update.onDoneChan != nil { - ctx.Log.Info("Flagging tar as completed") - update.onDoneChan <- true - } - - openImports.Delete(importId) - - ctx.Log.Info("Finishing import task") - dbMeta := storage.GetDatabase().GetMetadataStore(ctx) - err := dbMeta.FinishedBackgroundTask(taskId) - if err != nil { - ctx.Log.Error(err) - ctx.Log.Error("Failed to flag task as finished") - sentry.CaptureException(err) - } - ctx.Log.Info("Finished import") -} diff --git a/controllers/upload_controller/upload_controller.go b/controllers/upload_controller/upload_controller.go deleted file mode 100644 index 2f684c92d7ddd21ecf1d629c72a665169b4d6b20..0000000000000000000000000000000000000000 --- a/controllers/upload_controller/upload_controller.go +++ /dev/null @@ -1,266 +0,0 @@ -package upload_controller - -import ( - "bytes" - "errors" - "io" - - "github.com/getsentry/sentry-go" - "github.com/turt2live/matrix-media-repo/util/stream_util" - - "github.com/sirupsen/logrus" - "github.com/turt2live/matrix-media-repo/common" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/plugins" - "github.com/turt2live/matrix-media-repo/storage" - "github.com/turt2live/matrix-media-repo/storage/datastore" - "github.com/turt2live/matrix-media-repo/types" - "github.com/turt2live/matrix-media-repo/util" -) - -const NoApplicableUploadUser = "" - -type AlreadyUploadedFile struct { - DS *datastore.DatastoreRef - ObjectInfo *types.ObjectInfo -} - -func trackUploadAsLastAccess(ctx rcontext.RequestContext, media *types.Media) { - err := storage.GetDatabase().GetMetadataStore(ctx).UpsertLastAccess(media.Sha256Hash, util.NowMillis()) - if err != nil { - logrus.Warn("Failed to upsert the last access time: ", err) - } -} - -func checkSpam(contents []byte, filename string, contentType string, userId string, origin string, mediaId string) error { - spam, err := plugins.CheckForSpam(bytes.NewBuffer(contents), filename, contentType, userId, origin, mediaId) - if err != nil { - logrus.Warn("Error checking spam - assuming not spam: " + err.Error()) - sentry.CaptureException(err) - return nil - } - if spam { - return common.ErrMediaQuarantined - } - return nil -} - -func StoreDirect(f *AlreadyUploadedFile, contents io.ReadCloser, expectedSize int64, contentType string, filename string, userId string, origin string, mediaId string, kind common.Kind, ctx rcontext.RequestContext, filterUserDuplicates bool) (*types.Media, error) { - var err error - var ds *datastore.DatastoreRef - var info *types.ObjectInfo - var contentBytes []byte - if f == nil { - dsPicked, err := datastore.PickDatastore(string(kind), ctx) - if err != nil { - return nil, err - } - ds = dsPicked - - contentBytes, err = io.ReadAll(contents) - if err != nil { - return nil, err - } - - fInfo, err := ds.UploadFile(stream_util.BytesToStream(contentBytes), expectedSize, ctx) - if err != nil { - return nil, err - } - info = fInfo - } else { - ds = f.DS - info = f.ObjectInfo - - // download the contents for antispam - contents, err = ds.DownloadFile(info.Location) - if err != nil { - return nil, err - } - contentBytes, err = io.ReadAll(contents) - if err != nil { - return nil, err - } - } - - db := storage.GetDatabase().GetMediaStore(ctx) - records, err := db.GetByHash(info.Sha256Hash) - if err != nil { - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - return nil, err - } - - if len(records) > 0 { - ctx.Log.Info("Duplicate media for hash ", info.Sha256Hash) - - // If the user is a real user (ie: actually uploaded media), then we'll see if there's - // an exact duplicate that we can return. Otherwise we'll just pick the first record and - // clone that. - if filterUserDuplicates && userId != NoApplicableUploadUser { - for _, record := range records { - if record.Quarantined { - ctx.Log.Warn("User attempted to upload quarantined content - rejecting") - return nil, common.ErrMediaQuarantined - } - if record.UserId == userId && record.Origin == origin && record.ContentType == contentType { - ctx.Log.Info("User has already uploaded this media before - returning unaltered media record") - - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - - trackUploadAsLastAccess(ctx, record) - return record, nil - } - } - } - - err = checkSpam(contentBytes, filename, contentType, userId, origin, mediaId) - if err != nil { - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - return nil, err - } - - // We'll use the location from the first record - record := records[0] - if record.Quarantined { - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - ctx.Log.Warn("User attempted to upload quarantined content - rejecting") - return nil, common.ErrMediaQuarantined - } - - // Double check that we're not about to try and store a record we know about - for _, knownRecord := range records { - if knownRecord.Origin == origin && knownRecord.MediaId == mediaId { - ctx.Log.Info("Duplicate media record found - returning unaltered record") - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - trackUploadAsLastAccess(ctx, knownRecord) - return knownRecord, nil - } - } - - media := record - media.Origin = origin - media.MediaId = mediaId - media.UserId = userId - media.UploadName = filename - media.ContentType = contentType - media.CreationTs = util.NowMillis() - - err = db.Insert(media) - if err != nil { - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - return nil, err - } - - // If the media's file exists, we'll delete the temp file - // If the media's file doesn't exist, we'll move the temp file to where the media expects it to be - if media.DatastoreId != ds.DatastoreId && media.Location != info.Location { - ds2, err := datastore.LocateDatastore(ctx, media.DatastoreId) - if err != nil { - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - return nil, err - } - if !ds2.ObjectExists(media.Location) { - stream, err := ds.DownloadFile(info.Location) - if err != nil { - return nil, err - } - - err2 := ds2.OverwriteObject(media.Location, stream, ctx) - if err2 != nil { - ctx.Log.Warn("Error overwriting object", err2) - sentry.CaptureException(err2) - } - err2 = ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - } else { - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - } - } - - trackUploadAsLastAccess(ctx, media) - return media, nil - } - - // The media doesn't already exist - save it as new - - if info.SizeBytes <= 0 { - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - return nil, errors.New("file has no contents") - } - - err = checkSpam(contentBytes, filename, contentType, userId, origin, mediaId) - if err != nil { - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - return nil, err - } - - ctx.Log.Info("Persisting new media record") - - media := &types.Media{ - Origin: origin, - MediaId: mediaId, - UploadName: filename, - ContentType: contentType, - UserId: userId, - Sha256Hash: info.Sha256Hash, - SizeBytes: info.SizeBytes, - DatastoreId: ds.DatastoreId, - Location: info.Location, - CreationTs: util.NowMillis(), - } - - err = db.Insert(media) - if err != nil { - err2 := ds.DeleteObject(info.Location) // delete temp object - if err2 != nil { - ctx.Log.Warn("Error deleting temporary upload", err2) - sentry.CaptureException(err2) - } - return nil, err - } - - trackUploadAsLastAccess(ctx, media) - return media, nil -} diff --git a/database/table_blurhashes.go b/database/table_blurhashes.go index 8bf0339dba9c9a276266a90a82fb0c24fa2967fd..2f4bfbde1010457af3e1238ac692bd1c2006d46f 100644 --- a/database/table_blurhashes.go +++ b/database/table_blurhashes.go @@ -55,7 +55,7 @@ func (s *blurhashesTableWithContext) Get(hash string) (string, error) { row := s.statements.selectBlurhash.QueryRowContext(s.ctx, hash) val := "" err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = "" } diff --git a/database/table_expiring_media.go b/database/table_expiring_media.go index 416d1c37df07e0f6432916dddca4da117c1fd56d..5c3fac2bed4fa76e0007810797c9b049090f18b5 100644 --- a/database/table_expiring_media.go +++ b/database/table_expiring_media.go @@ -72,7 +72,7 @@ func (s *expiringMediaTableWithContext) ByUserCount(userId string) (int64, error row := s.statements.selectExpiringMediaByUserCount.QueryRowContext(s.ctx, userId, util.NowMillis()) val := int64(0) err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = 0 } @@ -83,7 +83,7 @@ func (s *expiringMediaTableWithContext) Get(origin string, mediaId string) (*DbE row := s.statements.selectExpiringMediaById.QueryRowContext(s.ctx, origin, mediaId) val := &DbExpiringMedia{} err := row.Scan(&val.Origin, &val.MediaId, &val.UserId, &val.ExpiresTs) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = nil } diff --git a/database/table_export_parts.go b/database/table_export_parts.go index a6ecf447717aaa32e099c9f979316a66ae57f657..b82ad9b93cf1ad8ff579d10fa5f8470de6483ab4 100644 --- a/database/table_export_parts.go +++ b/database/table_export_parts.go @@ -64,7 +64,7 @@ func (s *exportPartsTableWithContext) GetForExport(exportId string) ([]*DbExport results := make([]*DbExportPart, 0) rows, err := s.statements.selectExportPartsById.QueryContext(s.ctx, exportId) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return results, nil } return nil, err @@ -83,7 +83,7 @@ func (s *exportPartsTableWithContext) Get(exportId string, partNum int) (*DbExpo row := s.statements.selectExportPartById.QueryRowContext(s.ctx, exportId, partNum) val := &DbExportPart{} err := row.Scan(&val.ExportId, &val.PartNum, &val.SizeBytes, &val.FileName, &val.DatastoreId, &val.Location) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = nil } diff --git a/database/table_exports.go b/database/table_exports.go index ea306f305d1e6dbb91232268b2c9c4e57f4d8808..bf200df19df49806559e9ece50c0143bd880f164 100644 --- a/database/table_exports.go +++ b/database/table_exports.go @@ -60,7 +60,7 @@ func (s *exportsTableWithContext) GetEntity(exportId string) (string, error) { row := s.statements.selectExportEntity.QueryRowContext(s.ctx, exportId) val := "" err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = "" } diff --git a/database/table_media.go b/database/table_media.go index edb811ec937ee78d76e3eae5cd3bb7c78e3fa5a4..5e3fcd2095c71281cc92d43ca27c28be4f254fc5 100644 --- a/database/table_media.go +++ b/database/table_media.go @@ -157,7 +157,7 @@ func (s *mediaTableWithContext) GetDistinctDatastoreIds() ([]string, error) { results := make([]string, 0) rows, err := s.statements.selectDistinctMediaDatastoreIds.QueryContext(s.ctx) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return results, nil } return nil, err @@ -179,7 +179,7 @@ func (s *mediaTableWithContext) IsHashQuarantined(sha256hash string) (bool, erro row := s.statements.selectMediaIsQuarantinedByHash.QueryRowContext(s.ctx, sha256hash) val := false err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = false } @@ -189,7 +189,7 @@ func (s *mediaTableWithContext) IsHashQuarantined(sha256hash string) (bool, erro func (s *mediaTableWithContext) scanRows(rows *sql.Rows, err error) ([]*DbMedia, error) { results := make([]*DbMedia, 0) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return results, nil } return nil, err @@ -253,7 +253,7 @@ func (s *mediaTableWithContext) GetById(origin string, mediaId string) (*DbMedia row := s.statements.selectMediaById.QueryRowContext(s.ctx, origin, mediaId) val := &DbMedia{Locatable: &Locatable{}} err := row.Scan(&val.Origin, &val.MediaId, &val.UploadName, &val.ContentType, &val.UserId, &val.Sha256Hash, &val.SizeBytes, &val.CreationTs, &val.Quarantined, &val.DatastoreId, &val.Location) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = nil } @@ -264,7 +264,7 @@ func (s *mediaTableWithContext) ByUserCount(userId string) (int64, error) { row := s.statements.selectMediaByUserCount.QueryRowContext(s.ctx, userId) val := int64(0) err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = 0 } @@ -275,7 +275,7 @@ func (s *mediaTableWithContext) IdExists(origin string, mediaId string) (bool, e row := s.statements.selectMediaExists.QueryRowContext(s.ctx, origin, mediaId) val := false err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = false } @@ -286,7 +286,7 @@ func (s *mediaTableWithContext) LocationExists(datastoreId string, location stri row := s.statements.selectMediaByLocationExists.QueryRowContext(s.ctx, datastoreId, location) val := false err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = false } diff --git a/database/table_media_attributes.go b/database/table_media_attributes.go index 2513fc55fa6ca8faf8a21cdc27dda98f3f9ab525..de50010f11486dfc4c332eada0cbc4c8f163c7a5 100644 --- a/database/table_media_attributes.go +++ b/database/table_media_attributes.go @@ -62,7 +62,7 @@ func (s *mediaAttributesTableWithContext) Get(origin string, mediaId string) (*D row := s.statements.selectMediaAttributes.QueryRowContext(s.ctx, origin, mediaId) val := &DbMediaAttributes{} err := row.Scan(&val.Origin, &val.MediaId, &val.Purpose) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = nil } diff --git a/database/table_reserved_media.go b/database/table_reserved_media.go index 58047c68e65c62a0c3795ed448390cccc213d09d..d862644b2f5efdeb304f7a826906e07860718f54 100644 --- a/database/table_reserved_media.go +++ b/database/table_reserved_media.go @@ -56,7 +56,7 @@ func (s *reservedMediaTableWithContext) IdExists(origin string, mediaId string) row := s.statements.selectReservedMediaExists.QueryRowContext(s.ctx, origin, mediaId) val := false err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = false } diff --git a/database/table_tasks.go b/database/table_tasks.go index 511094d8555cc462e91789711afe8ea0453e846e..eb82414da9e776e5f9231610f81e644056ef3926 100644 --- a/database/table_tasks.go +++ b/database/table_tasks.go @@ -83,7 +83,7 @@ func (s *tasksTableWithContext) Get(id int) (*DbTask, error) { row := s.statements.selectTask.QueryRowContext(s.ctx, id) val := &DbTask{} err := row.Scan(&val.TaskId, &val.Name, &val.Params, &val.StartTs, &val.EndTs) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = nil } @@ -98,7 +98,7 @@ func (s *tasksTableWithContext) GetAll(includingFinished bool) ([]*DbTask, error } rows, err := q.QueryContext(s.ctx) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return results, nil } return nil, err diff --git a/database/table_thumbnails.go b/database/table_thumbnails.go index 67de1760f2bdd6152641a030ed99f5689f2ff556..280fa0d6ae3354e4e8b2fc05baf73f571b3d3012 100644 --- a/database/table_thumbnails.go +++ b/database/table_thumbnails.go @@ -93,7 +93,7 @@ func (s *thumbnailsTableWithContext) GetByParams(origin string, mediaId string, row := s.statements.selectThumbnailByParams.QueryRowContext(s.ctx, origin, mediaId, width, height, method, animated) val := &DbThumbnail{Locatable: &Locatable{}} err := row.Scan(&val.Origin, &val.MediaId, &val.ContentType, &val.Width, &val.Height, &val.Method, &val.Animated, &val.Sha256Hash, &val.SizeBytes, &val.CreationTs, &val.DatastoreId, &val.Location) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = nil } @@ -103,7 +103,7 @@ func (s *thumbnailsTableWithContext) GetByParams(origin string, mediaId string, func (s *thumbnailsTableWithContext) scanRows(rows *sql.Rows, err error) ([]*DbThumbnail, error) { results := make([]*DbThumbnail, 0) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return results, nil } return nil, err @@ -140,7 +140,7 @@ func (s *thumbnailsTableWithContext) LocationExists(datastoreId string, location row := s.statements.selectThumbnailByLocationExists.QueryRowContext(s.ctx, datastoreId, location) val := false err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = false } diff --git a/database/table_url_previews.go b/database/table_url_previews.go index e4a28ce97e86ce7230a5ae0182dc33f29ced3f24..633fa1bf9798f01c38820270435686fdc12a40ac 100644 --- a/database/table_url_previews.go +++ b/database/table_url_previews.go @@ -68,7 +68,7 @@ func (s *urlPreviewsTableWithContext) Get(url string, ts int64, languageHeader s row := s.statements.selectUrlPreview.QueryRowContext(s.ctx, url, ts, languageHeader) val := &DbUrlPreview{} err := row.Scan(&val.Url, &val.ErrorCode, &val.BucketTs, &val.SiteUrl, &val.SiteName, &val.ResourceType, &val.Description, &val.Title, &val.ImageMxc, &val.ImageType, &val.ImageSize, &val.ImageWidth, &val.ImageHeight, &val.LanguageHeader) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return val, err diff --git a/database/table_user_stats.go b/database/table_user_stats.go index 3840ef25823aaedc077605fb05147a439020ec36..d94b49ecf1c84925c327e2d5f1afb27c656910a8 100644 --- a/database/table_user_stats.go +++ b/database/table_user_stats.go @@ -45,7 +45,7 @@ func (s *userStatsTableWithContext) UserUploadedBytes(userId string) (int64, err row := s.statements.selectUserStatsUploadedBytes.QueryRowContext(s.ctx, userId) val := int64(0) err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = 0 } diff --git a/database/virtualtable_metadata.go b/database/virtualtable_metadata.go index fac2c0ea9ae7de311d8af9547eddb6ecd5d68ca2..cf583fbfd051e430c15520c85090050da3b0b32d 100644 --- a/database/virtualtable_metadata.go +++ b/database/virtualtable_metadata.go @@ -104,7 +104,7 @@ func (s *metadataVirtualTableWithContext) EstimateDatastoreSize(datastoreId stri row := s.statements.selectEstimatedDatastoreSize.QueryRowContext(s.ctx, datastoreId) val := int64(0) err := row.Scan(&val) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil val = 0 } @@ -116,7 +116,7 @@ func (s *metadataVirtualTableWithContext) ByteUsageForServer(serverName string) media := int64(0) thumbs := int64(0) err := row.Scan(&media, &thumbs) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil media = int64(0) thumbs = int64(0) @@ -129,7 +129,7 @@ func (s *metadataVirtualTableWithContext) CountUsageForServer(serverName string) media := int64(0) thumbs := int64(0) err := row.Scan(&media, &thumbs) - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { err = nil media = int64(0) thumbs = int64(0) @@ -183,7 +183,7 @@ func (s *metadataVirtualTableWithContext) UnoptimizedSynapseUserStatsPage(server results := make([]*DbSynUserStat, 0) rows, err := s.statements.db.QueryContext(s.ctx, sqlPageQ, sqlParams...) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return results, 0, nil } return nil, 0, err @@ -203,7 +203,7 @@ func (s *metadataVirtualTableWithContext) UnoptimizedSynapseUserStatsPage(server total := int64(0) err = row.Scan(&total) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return make([]*DbSynUserStat, 0), 0, nil } return nil, 0, err @@ -215,7 +215,7 @@ func (s *metadataVirtualTableWithContext) UnoptimizedSynapseUserStatsPage(server func (s *metadataVirtualTableWithContext) scanLastAccess(rows *sql.Rows, err error) ([]*VirtLastAccess, error) { results := make([]*VirtLastAccess, 0) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return results, nil } return nil, err diff --git a/datastores/upload.go b/datastores/upload.go index a64b96206429616d2c5c4d90c2215f068d1a467c..813300b305502fd48c179c1f7f1beccf2fedfc99 100644 --- a/datastores/upload.go +++ b/datastores/upload.go @@ -49,12 +49,11 @@ func Upload(ctx rcontext.RequestContext, ds config.DatastoreConfig, data io.Read metrics.S3Operations.With(prometheus.Labels{"operation": "StatObject"}).Inc() _, err = s3c.client.StatObject(ctx.Context, s3c.bucket, objectName, minio.StatObjectOptions{}) if err != nil { - if merr, ok := err.(minio.ErrorResponse); ok { + var merr minio.ErrorResponse + if errors.As(err, &merr) { if merr.Code == "NoSuchKey" || merr.StatusCode == http.StatusNotFound { exists = false } - } else { - return "", err } } } diff --git a/docs/contrib/delegation.md b/docs/contrib/delegation.md index 7e646a88f4469438b960d77383d29c4f0142b9bb..7359093829536c8ae12497a0f6a497426392cf2a 100644 --- a/docs/contrib/delegation.md +++ b/docs/contrib/delegation.md @@ -17,12 +17,12 @@ media-repo.yaml: ```yaml repo: useForwaredHost: true # See notes below - ... + #... homeservers: - name: example.com csApi: https://matrix.example.com # The base URL to where the homeserver can actually be reached - ... + #... ``` A full sample config can be found [here](https://github.com/turt2live/matrix-media-repo/blob/master/config.sample.yaml). diff --git a/go.mod b/go.mod index b35c37496cbde25fd71788032e634ccb13b33e85..9e5eb8330d5d58a45d8236f1dec47d53f4c4cb8c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.19 require ( github.com/DavidHuie/gomigrate v0.0.0-20190826182718-4adc4b3de142 - github.com/Jeffail/tunny v0.1.4 github.com/PuerkitoBio/goquery v1.8.1 github.com/adrium/goheif v0.0.0-20230113233934-ca402e77a786 github.com/alioygur/is v1.0.3 @@ -31,8 +30,6 @@ require ( github.com/kettek/apng v0.0.0-20220823221153-ff692776a607 github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f github.com/lib/pq v1.10.9 - github.com/minio/minio-go/v6 v6.0.57 - github.com/olebedev/emitter v0.0.0-20230411050614-349169dec2ba github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.16.0 @@ -86,7 +83,6 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mewkiz/flac v1.0.8 // indirect github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/prometheus/client_model v0.4.0 // indirect diff --git a/go.sum b/go.sum index 9b35ae6004c42801b540882a82637bc18109db22..0c0caa4891ab4e640be79a34470de6fdf25b0d1a 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ github.com/DavidHuie/gomigrate v0.0.0-20190826182718-4adc4b3de142 h1:pfeJevnIXt4 github.com/DavidHuie/gomigrate v0.0.0-20190826182718-4adc4b3de142/go.mod h1:F3GZLX+VN44AjFiyKD8++nq8sVE0Sw3bOhhQ3mUffnM= github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= -github.com/Jeffail/tunny v0.1.4 h1:chtpdz+nUtaYQeCKlNBg6GycFF/kGVHOr6A3cmzTJXs= -github.com/Jeffail/tunny v0.1.4/go.mod h1:P8xAx4XQl0xsuhjX1DtfaMDCSuavzdb2rwbd0lk+fvo= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/adrium/goheif v0.0.0-20230113233934-ca402e77a786 h1:zvgtcRb2B5gynWjm+Fc9oJZPHXwmcgyH0xCcNm6Rmo4= @@ -68,7 +66,6 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LV github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU= github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw= github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dyatlov/go-oembed v0.0.0-20191103150536-a57c85b3b37c h1:MEV1LrQtCBGacXajlT4CSuYWbZuLl/qaZVqwoOmwAbU= @@ -185,7 +182,6 @@ github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3T github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jszwec/csvutil v1.5.1/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4YcJjGBkg= @@ -200,11 +196,9 @@ github.com/kettek/apng v0.0.0-20220823221153-ff692776a607/go.mod h1:x78/VRQYKuCf github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -240,32 +234,23 @@ github.com/mewkiz/flac v1.0.8/go.mod h1:l7dt5uFY724eKVkHQtAJAQSkhpC3helU3RDxN0ES github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA= github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14 h1:tnAPMExbRERsyEYkmR1YjhTgDM0iqyiBYf8ojRXxdbA= github.com/mewkiz/pkg v0.0.0-20230226050401-4010bf0fec14/go.mod h1:QYCFBiH5q6XTHEbWhR0uhR3M9qNPoD2CSQzr0g75kE4= -github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v6 v6.0.57 h1:ixPkbKkyD7IhnluRgQpGSpHdpvNVaW6OD5R9IAO/9Tw= -github.com/minio/minio-go/v6 v6.0.57/go.mod h1:5+R/nM9Pwrh0vqF+HbYYDQ84wdUFPyXHkrdT4AIkifM= github.com/minio/minio-go/v7 v7.0.61 h1:87c+x8J3jxQ5VUGimV9oHdpjsAvy3fhneEBKuoKEVUI= github.com/minio/minio-go/v7 v7.0.61/go.mod h1:BTu8FcrEw+HidY0zd/0eny43QnVNkXRPXrLXFuQBHXg= -github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/olebedev/emitter v0.0.0-20230411050614-349169dec2ba h1:/Q5vvLs180BFH7u+Nakdrr1B9O9RAxVaIurFQy0c8QQ= -github.com/olebedev/emitter v0.0.0-20230411050614-349169dec2ba/go.mod h1:eT2/Pcsim3XBjbvldGiJBvvgiqZkAFyiOJJsDKXs/ts= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -312,19 +297,16 @@ github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7 github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM= github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0= -github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -342,7 +324,6 @@ github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIU github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -363,7 +344,6 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -395,7 +375,6 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -475,7 +454,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/homeserver_interop/synapse/db.go b/homeserver_interop/synapse/db.go index 57494cec71025242ace5a5b7399e104afd9dfe1a..503299abcc01ca144ef488a97f78f2aee5a0c9ef 100644 --- a/homeserver_interop/synapse/db.go +++ b/homeserver_interop/synapse/db.go @@ -2,6 +2,7 @@ package synapse import ( "database/sql" + "errors" _ "github.com/lib/pq" // postgres driver ) @@ -45,7 +46,7 @@ func OpenDatabase(connectionString string) (*SynDatabase, error) { func (d *SynDatabase) GetAllMedia() ([]*LocalMedia, error) { rows, err := d.statements.selectLocalMedia.Query() if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return []*LocalMedia{}, nil // no records } return nil, err diff --git a/matrix/federation.go b/matrix/federation.go index e76c58b3cdd3d4e7a070231d0792dc9dd5dd90c0..0ddda378dfd09d8fe28e5f8addabb00b204dddce 100644 --- a/matrix/federation.go +++ b/matrix/federation.go @@ -55,8 +55,9 @@ func getFederationBreaker(hostname string) *circuit.Breaker { return cb } -// Note: URL lookups are not covered by the breaker because otherwise it might never close. func GetServerApiUrl(hostname string) (string, string, error) { + // dev note: URL lookups are not covered by the breaker because otherwise it might never close. + logrus.Debug("Getting server API URL for " + hostname) // Check to see if we've cached this hostname at all diff --git a/metrics/webserver.go b/metrics/webserver.go index d3ddfd3e043d4ae0b59f7cc7107bcdd1a68e9563..67bebb47daa98bb0dc237ddda9a22fd976dc4b9b 100644 --- a/metrics/webserver.go +++ b/metrics/webserver.go @@ -2,6 +2,7 @@ package metrics import ( "context" + "errors" "net" "net/http" "strconv" @@ -32,8 +33,9 @@ func Init() { address := net.JoinHostPort(config.Get().Metrics.BindAddress, strconv.Itoa(config.Get().Metrics.Port)) srv = &http.Server{Addr: address, Handler: rtr} go func() { + //goland:noinspection HttpUrlsUsage logrus.WithField("address", address).Info("Started metrics listener. Listening at http://" + address) - if err := srv.ListenAndServe(); err != http.ErrServerClosed { + if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { sentry.CaptureException(err) logrus.Fatal(err) } diff --git a/pipelines/_steps/datastore_op/put_and_return_stream.go b/pipelines/_steps/datastore_op/put_and_return_stream.go index 975c1d8075368af7ffa52ea55b614afb043b92cb..f2e975a34148aa0f04d1ee44635b03dab8f529d9 100644 --- a/pipelines/_steps/datastore_op/put_and_return_stream.go +++ b/pipelines/_steps/datastore_op/put_and_return_stream.go @@ -32,7 +32,9 @@ func PutAndReturnStream(ctx rcontext.RequestContext, origin string, mediaId stri pr, pw := io.Pipe() tee := io.TeeReader(input, pw) - defer pw.CloseWithError(errors.New("failed to finish write")) + defer func(pw *io.PipeWriter, err error) { + _ = pw.CloseWithError(err) + }(pw, errors.New("failed to finish write")) wg := new(sync.WaitGroup) wg.Add(2) diff --git a/pipelines/_steps/upload/generate_media_id.go b/pipelines/_steps/upload/generate_media_id.go index 4fd86792ce22e60137b58b4bd53517f934b1cf99..01dd1b83d4f0497d4f79e2df22bf99cec04c16f3 100644 --- a/pipelines/_steps/upload/generate_media_id.go +++ b/pipelines/_steps/upload/generate_media_id.go @@ -20,7 +20,7 @@ func GenerateMediaId(ctx rcontext.RequestContext, origin string) (string, error) var err error var exists bool attempts := 0 - for true { + for { attempts += 1 if attempts > 10 { return "", errors.New("internal limit reached: unable to generate media ID") diff --git a/pipelines/_steps/url_preview/preview.go b/pipelines/_steps/url_preview/preview.go index 3fc75b943f972b74fa35057802c355a61d68ffcb..31bd7a7f47e04a7a796a14090ac8aa3855944db4 100644 --- a/pipelines/_steps/url_preview/preview.go +++ b/pipelines/_steps/url_preview/preview.go @@ -1,6 +1,8 @@ package url_preview import ( + "errors" + "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/pool" "github.com/turt2live/matrix-media-repo/url_previewing/m" @@ -26,13 +28,13 @@ func Preview(ctx rcontext.RequestContext, targetUrl *m.UrlPayload, languageHeade } // Try OpenGraph if that failed - if err == m.ErrPreviewUnsupported { + if errors.Is(err, m.ErrPreviewUnsupported) { ctx.Log.Debug("Trying OpenGraph previewer") preview, err = p.GenerateOpenGraphPreview(targetUrl, languageHeader, ctx) } // Try scraping if that failed - if err == m.ErrPreviewUnsupported { + if errors.Is(err, m.ErrPreviewUnsupported) { ctx.Log.Debug("Trying built-in previewer") preview, err = p.GenerateCalculatedPreview(targetUrl, languageHeader, ctx) } diff --git a/pipelines/_steps/url_preview/process.go b/pipelines/_steps/url_preview/process.go index ed30cf8a58ee8af78500c55675a34a630e5f84fd..dfc241b72d6396c28199af7c992243116fe24086 100644 --- a/pipelines/_steps/url_preview/process.go +++ b/pipelines/_steps/url_preview/process.go @@ -1,6 +1,8 @@ package url_preview import ( + "errors" + "github.com/getsentry/sentry-go" "github.com/turt2live/matrix-media-repo/common" "github.com/turt2live/matrix-media-repo/common/rcontext" @@ -12,11 +14,11 @@ func Process(ctx rcontext.RequestContext, previewUrl string, preview m.PreviewRe previewDb := database.GetInstance().UrlPreviews.Prepare(ctx) if err != nil { - if err == m.ErrPreviewUnsupported { + if errors.Is(err, m.ErrPreviewUnsupported) { err = common.ErrMediaNotFound } - if err == common.ErrMediaNotFound { + if errors.Is(err, common.ErrMediaNotFound) { previewDb.InsertError(previewUrl, common.ErrCodeNotFound) } else { previewDb.InsertError(previewUrl, common.ErrCodeUnknown) diff --git a/pipelines/pipeline_download/pipeline.go b/pipelines/pipeline_download/pipeline.go index 0c5ebef3cd732a8f8e91994538b1519b28957671..f57de2fd449c27cb51eb26e61be0a688b3d824b7 100644 --- a/pipelines/pipeline_download/pipeline.go +++ b/pipelines/pipeline_download/pipeline.go @@ -103,7 +103,7 @@ func Execute(ctx rcontext.RequestContext, origin string, mediaId string, opts Do return r, nil }) - if err == common.ErrMediaQuarantined { + if errors.Is(err, common.ErrMediaQuarantined) { cancel() return nil, r, err } diff --git a/pipelines/pipeline_thumbnail/pipeline.go b/pipelines/pipeline_thumbnail/pipeline.go index f8451caea6a5a069a68e1e8ae714dc9292aa67d3..9786eac96ea917be2caf93c53d5ff445372b42ad 100644 --- a/pipelines/pipeline_thumbnail/pipeline.go +++ b/pipelines/pipeline_thumbnail/pipeline.go @@ -75,7 +75,7 @@ func Execute(ctx rcontext.RequestContext, origin string, mediaId string, opts Th // Step 4: Get the associated media record (without stream) mediaRecord, dr, err := pipeline_download.Execute(ctx, origin, mediaId, opts.ImpliedDownloadOpts()) if err != nil { - if err == common.ErrMediaQuarantined { + if errors.Is(err, common.ErrMediaQuarantined) { go serveRecord(recordCh, nil) // async function to prevent deadlock if dr != nil { dr.Close() @@ -116,7 +116,7 @@ func Execute(ctx rcontext.RequestContext, origin string, mediaId string, opts Th // Step 7: Create a limited stream return download.CreateLimitedStream(ctx, r, opts.StartByte, opts.EndByte) }) - if err == common.ErrMediaQuarantined { + if errors.Is(err, common.ErrMediaQuarantined) { cancel() return nil, r, err } diff --git a/pipelines/pipeline_upload/pipeline.go b/pipelines/pipeline_upload/pipeline.go index 8dd30f51259c68a1b7f40e4e66e1fcbefb20b69e..dd534ef837a4cb34b5b49a041cd9c591b26ceacd 100644 --- a/pipelines/pipeline_upload/pipeline.go +++ b/pipelines/pipeline_upload/pipeline.go @@ -83,8 +83,12 @@ func Execute(ctx rcontext.RequestContext, origin string, mediaId string, r io.Re allWriters := io.MultiWriter(cacheW, bhW) tee := io.TeeReader(reader, allWriters) - defer bhW.CloseWithError(errors.New("failed to finish write")) - defer cacheW.CloseWithError(errors.New("failed to finish write")) + defer func(bhW *io.PipeWriter, err error) { + _ = bhW.CloseWithError(err) + }(bhW, errors.New("failed to finish write")) + defer func(cacheW *io.PipeWriter, err error) { + _ = cacheW.CloseWithError(err) + }(cacheW, errors.New("failed to finish write")) // Step 6: Check quarantine if err = upload.CheckQuarantineStatus(ctx, sha256hash); err != nil { diff --git a/plugins/mmr_plugin.go b/plugins/mmr_plugin.go index 24bc040ea880133d884f11f68b84478049962041..3ef00dc55a5dc694d7781602d3eeda316d216ec0 100644 --- a/plugins/mmr_plugin.go +++ b/plugins/mmr_plugin.go @@ -53,7 +53,7 @@ func (p *mmrPlugin) Antispam() (plugin_interfaces.Antispam, error) { } p.antispamPlugin = raw.(plugin_interfaces.Antispam) - p.antispamPlugin.HandleConfig(p.config) + _ = p.antispamPlugin.HandleConfig(p.config) return p.antispamPlugin, nil } diff --git a/plugins/plugin_common/handshake.go b/plugins/plugin_common/handshake.go index c0278fd7cddb40d3a50e4a858fdd20da4e3bf6b1..6a661ffc69a37d97d505c54084310328efb3ee33 100644 --- a/plugins/plugin_common/handshake.go +++ b/plugins/plugin_common/handshake.go @@ -4,9 +4,10 @@ import ( "github.com/hashicorp/go-plugin" ) -// UX, not security +// dev note: HandshakeConfig is for UX, not security + var HandshakeConfig = plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "MEDIA_REPO_PLUGIN", MagicCookieValue: "hello world - I am a media repo", -} \ No newline at end of file +} diff --git a/pool/queue.go b/pool/queue.go index 18cea5f6f0e63db811164ae0954238b5fd6fe321..9def5b7c86ff2549b7fbb221acfd8cd7edc88759 100644 --- a/pool/queue.go +++ b/pool/queue.go @@ -22,6 +22,7 @@ func NewQueue(workers int, name string) (*Queue, error) { PanicHandler: func(err interface{}) { logrus.Errorf("Panic from internal queue %s", name) logrus.Error(err) + //goland:noinspection GoTypeAssertionOnErrors if e, ok := err.(error); ok { sentry.CaptureException(e) } diff --git a/redis_cache/LEGACY_redis.go b/redis_cache/LEGACY_redis.go deleted file mode 100644 index d9d65fd216cb7773c5d4a6b6ed2c20e9bbb721b4..0000000000000000000000000000000000000000 --- a/redis_cache/LEGACY_redis.go +++ /dev/null @@ -1,98 +0,0 @@ -package redis_cache - -import ( - "bytes" - "context" - "errors" - "io" - "time" - - "github.com/redis/go-redis/v9" - "github.com/sirupsen/logrus" - "github.com/turt2live/matrix-media-repo/common/config" - "github.com/turt2live/matrix-media-repo/common/rcontext" -) - -var ErrCacheMiss = errors.New("missed cache") -var ErrCacheDown = errors.New("all shards appear to be down") - -type RedisCache struct { - ring *redis.Ring -} - -func NewCache(conf config.RedisConfig) *RedisCache { - addresses := make(map[string]string) - for _, c := range conf.Shards { - addresses[c.Name] = c.Address - } - ring := redis.NewRing(&redis.RingOptions{ - Addrs: addresses, - DialTimeout: 10 * time.Second, - DB: conf.DbNum, - }) - - logrus.Info("Contacting Redis shards...") - _ = ring.ForEachShard(context.Background(), func(ctx context.Context, client *redis.Client) error { - logrus.Infof("Pinging %s", client.String()) - r, err := client.Ping(ctx).Result() - if err != nil { - return err - } - logrus.Infof("%s replied with: %s", client.String(), r) - return nil - }) - - return &RedisCache{ring: ring} -} - -func (c *RedisCache) Close() error { - return c.ring.Close() -} - -func (c *RedisCache) SetStream(ctx rcontext.RequestContext, key string, s io.Reader) error { - b, err := io.ReadAll(s) - if err != nil { - return err - } - return c.SetBytes(ctx, key, b) -} - -func (c *RedisCache) GetStream(ctx rcontext.RequestContext, key string) (io.Reader, error) { - b, err := c.GetBytes(ctx, key) - if err != nil { - return nil, err - } - return bytes.NewBuffer(b), nil -} - -func (c *RedisCache) SetBytes(ctx rcontext.RequestContext, key string, b []byte) error { - if c.ring.PoolStats().TotalConns == 0 { - return ErrCacheDown - } - _, err := c.ring.Set(ctx.Context, key, b, time.Duration(0)).Result() // no expiration (zero) - if err != nil && c.ring.PoolStats().TotalConns == 0 { - ctx.Log.Error(err) - return ErrCacheDown - } - return err -} - -func (c *RedisCache) GetBytes(ctx rcontext.RequestContext, key string) ([]byte, error) { - if c.ring.PoolStats().TotalConns == 0 { - return nil, ErrCacheDown - } - r := c.ring.Get(ctx.Context, key) - if r.Err() != nil { - if r.Err() == redis.Nil { - return nil, ErrCacheMiss - } - if c.ring.PoolStats().TotalConns == 0 { - ctx.Log.Error(r.Err()) - return nil, ErrCacheDown - } - return nil, r.Err() - } - - b, err := r.Bytes() - return b, err -} diff --git a/redislib/cache.go b/redislib/cache.go index 3e3d25b5c7a9f28d0b9fddd54c897b87b99551c2..c95336db93ce6d0fd5a10c3fc06394536e66dec1 100644 --- a/redislib/cache.go +++ b/redislib/cache.go @@ -34,7 +34,7 @@ func StoreMedia(ctx rcontext.RequestContext, hash string, content io.Reader, siz } buf := make([]byte, appendBufferSize) - for true { + for { read, err := content.Read(buf) if err == io.EOF { break diff --git a/storage/LEGACY.txt b/storage/LEGACY.txt deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/storage/datastore/datastore.go b/storage/datastore/datastore.go deleted file mode 100644 index 719a37838505d5a597cb32a9eb3d2f70b2cbfc72..0000000000000000000000000000000000000000 --- a/storage/datastore/datastore.go +++ /dev/null @@ -1,153 +0,0 @@ -package datastore - -import ( - "errors" - "fmt" - "io" - - "github.com/getsentry/sentry-go" - "github.com/turt2live/matrix-media-repo/common" - - "github.com/sirupsen/logrus" - "github.com/turt2live/matrix-media-repo/common/config" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/storage" - "github.com/turt2live/matrix-media-repo/types" -) - -func GetAvailableDatastores(ctx rcontext.RequestContext) ([]*types.Datastore, error) { - datastores := make([]*types.Datastore, 0) - for _, ds := range ctx.Config.DataStores { - uri := GetUriForDatastore(ds) - - dsInstance, err := storage.GetOrCreateDatastoreOfType(rcontext.Initial(), ds.Type, uri) - if err != nil { - return nil, err - } - - datastores = append(datastores, dsInstance) - } - - return datastores, nil -} - -func LocateDatastore(ctx rcontext.RequestContext, datastoreId string) (*DatastoreRef, error) { - ds, err := storage.GetDatabase().GetMediaStore(ctx).GetDatastore(datastoreId) - if err != nil { - return nil, err - } - - conf, err := GetDatastoreConfig(ds) - if err != nil { - return nil, err - } - - return newDatastoreRef(ds, conf), nil -} - -func DownloadStream(ctx rcontext.RequestContext, datastoreId string, location string) (io.ReadCloser, error) { - ref, err := LocateDatastore(ctx, datastoreId) - if err != nil { - return nil, err - } - return ref.DownloadFile(location) -} - -func GetDatastoreConfig(ds *types.Datastore) (config.DatastoreConfig, error) { - for _, dsConf := range config.UniqueDatastores() { - if dsConf.Type == ds.Type && GetUriForDatastore(dsConf) == ds.Uri { - return dsConf, nil - } - } - - return config.DatastoreConfig{}, errors.New("datastore not found") -} - -func GetUriForDatastore(dsConf config.DatastoreConfig) string { - if dsConf.Type == "file" { - path, pathFound := dsConf.Options["path"] - if !pathFound { - sentry.CaptureException(errors.New("Missing 'path' on file datastore")) - logrus.Fatal("Missing 'path' on file datastore") - } - return path - } else if dsConf.Type == "s3" { - endpoint, epFound := dsConf.Options["endpoint"] - bucket, bucketFound := dsConf.Options["bucketName"] - region, regionFound := dsConf.Options["region"] - if !epFound || !bucketFound { - sentry.CaptureException(errors.New("Missing 'endpoint' or 'bucketName' on s3 datastore")) - logrus.Fatal("Missing 'endpoint' or 'bucketName' on s3 datastore") - } - if regionFound { - return fmt.Sprintf("s3://%s/%s?region=%s", endpoint, bucket, region) - } else { - return fmt.Sprintf("s3://%s/%s", endpoint, bucket) - } - } else { - sentry.CaptureException(errors.New("unknown datastore type: " + dsConf.Type)) - logrus.Fatal("Unknown datastore type: ", dsConf.Type) - } - - return "" -} - -func PickDatastore(forKind string, ctx rcontext.RequestContext) (*DatastoreRef, error) { - ctx.Log.Info("Finding a suitable datastore to pick for " + forKind) - confDatastores := ctx.Config.DataStores - mediaStore := storage.GetDatabase().GetMediaStore(ctx) - - // Figure out which datastores are likely to be useful for us to check against first. This - // helps speed up later checks which could require significant DB resources (estimating the - // size of the datastore). - var possibleDatastores = make([]config.DatastoreConfig, 0) - for _, dsConf := range confDatastores { - allowed := common.HasKind(dsConf.MediaKinds, common.Kind(forKind)) - if !allowed { - continue - } - - possibleDatastores = append(possibleDatastores, dsConf) - } - - var targetDs *types.Datastore - var targetDsConf config.DatastoreConfig - var dsSize int64 - for _, dsConf := range possibleDatastores { - - ds, err := mediaStore.GetDatastoreByUri(GetUriForDatastore(dsConf)) - if err != nil { - ctx.Log.Error("Error getting datastore: ", err.Error()) - sentry.CaptureException(err) - continue - } - - var size int64 - - if len(possibleDatastores) > 1 { - size, err = estimatedDatastoreSize(ds, ctx) - if err != nil { - ctx.Log.Error("Error estimating datastore size for ", ds.DatastoreId, ": ", err.Error()) - sentry.CaptureException(err) - continue - } - } - - if targetDs == nil || size < dsSize { - targetDs = ds - targetDsConf = dsConf - dsSize = size - } - } - - if targetDs != nil { - ctx.Log.Info("Using ", targetDs.Uri) - return newDatastoreRef(targetDs, targetDsConf), nil - } - - return nil, errors.New("failed to pick a datastore: none available") -} - -func estimatedDatastoreSize(ds *types.Datastore, ctx rcontext.RequestContext) (int64, error) { - return storage.GetDatabase().GetMetadataStore(ctx).GetEstimatedSizeOfDatastore(ds.DatastoreId) -} diff --git a/storage/datastore/datastore_ref.go b/storage/datastore/datastore_ref.go deleted file mode 100644 index 9d8e62dd2cb20e0c00d3e207c9632cc645676513..0000000000000000000000000000000000000000 --- a/storage/datastore/datastore_ref.go +++ /dev/null @@ -1,125 +0,0 @@ -package datastore - -import ( - "errors" - "io" - "os" - "path" - - "github.com/sirupsen/logrus" - config2 "github.com/turt2live/matrix-media-repo/common/config" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/storage/datastore/ds_file" - "github.com/turt2live/matrix-media-repo/storage/datastore/ds_s3" - "github.com/turt2live/matrix-media-repo/types" - "github.com/turt2live/matrix-media-repo/util" -) - -type DatastoreRef struct { - // TODO: Don't blindly copy properties from types.Datastore - DatastoreId string - Type string - Uri string - - datastore *types.Datastore - config config2.DatastoreConfig -} - -func newDatastoreRef(ds *types.Datastore, config config2.DatastoreConfig) *DatastoreRef { - return &DatastoreRef{ - DatastoreId: ds.DatastoreId, - Type: ds.Type, - Uri: ds.Uri, - datastore: ds, - config: config, - } -} - -func (d *DatastoreRef) UploadFile(file io.ReadCloser, expectedLength int64, ctx rcontext.RequestContext) (*types.ObjectInfo, error) { - ctx = ctx.LogWithFields(logrus.Fields{"datastoreId": d.DatastoreId, "datastoreUri": d.Uri}) - - if d.Type == "file" { - return ds_file.PersistFile(d.Uri, file, ctx) - } else if d.Type == "s3" { - s3, err := ds_s3.GetOrCreateS3Datastore(d.DatastoreId, d.config) - if err != nil { - return nil, err - } - return s3.UploadFile(file, expectedLength, ctx) - } else { - return nil, errors.New("unknown datastore type") - } -} - -func (d *DatastoreRef) DeleteObject(location string) error { - if d.Type == "file" { - return ds_file.DeletePersistedFile(d.Uri, location) - } else if d.Type == "s3" { - s3, err := ds_s3.GetOrCreateS3Datastore(d.DatastoreId, d.config) - if err != nil { - return err - } - return s3.DeleteObject(location) - } else { - return errors.New("unknown datastore type") - } -} - -func (d *DatastoreRef) DownloadFile(location string) (io.ReadCloser, error) { - if d.Type == "file" { - return os.Open(path.Join(d.Uri, location)) - } else if d.Type == "s3" { - s3, err := ds_s3.GetOrCreateS3Datastore(d.DatastoreId, d.config) - if err != nil { - return nil, err - } - return s3.DownloadObject(location) - } else { - return nil, errors.New("unknown datastore type") - } -} - -func (d *DatastoreRef) ObjectExists(location string) bool { - if d.Type == "file" { - ok, err := util.FileExists(path.Join(d.Uri, location)) - if err != nil { - return false - } - return ok - } else if d.Type == "s3" { - s3, err := ds_s3.GetOrCreateS3Datastore(d.DatastoreId, d.config) - if err != nil { - return false - } - return s3.ObjectExists(location) - } else { - panic("unknown datastore type") - } -} - -func (d *DatastoreRef) OverwriteObject(location string, stream io.ReadCloser, ctx rcontext.RequestContext) error { - if d.Type == "file" { - _, _, err := ds_file.PersistFileAtLocation(path.Join(d.Uri, location), stream, ctx) - return err - } else if d.Type == "s3" { - s3, err := ds_s3.GetOrCreateS3Datastore(d.DatastoreId, d.config) - if err != nil { - return err - } - return s3.OverwriteObject(location, stream) - } else { - return errors.New("unknown datastore type") - } -} - -func (d *DatastoreRef) ListObjectIds(ctx rcontext.RequestContext) ([]string, error) { - if d.Type == "s3" { - s3, err := ds_s3.GetOrCreateS3Datastore(d.DatastoreId, d.config) - if err != nil { - return nil, err - } - return s3.ListObjects() - } else { - return nil, errors.New("cannot list objects in this datastore type") - } -} diff --git a/storage/datastore/ds_file/file_store.go b/storage/datastore/ds_file/file_store.go deleted file mode 100644 index 182d2349ecd7c25534d26fb8057459ae2bcf7ee1..0000000000000000000000000000000000000000 --- a/storage/datastore/ds_file/file_store.go +++ /dev/null @@ -1,130 +0,0 @@ -package ds_file - -import ( - "errors" - "io" - "os" - "path" - - "github.com/turt2live/matrix-media-repo/util/ids" - "github.com/turt2live/matrix-media-repo/util/stream_util" - - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/types" - "github.com/turt2live/matrix-media-repo/util" -) - -func PersistFile(basePath string, file io.ReadCloser, ctx rcontext.RequestContext) (*types.ObjectInfo, error) { - defer stream_util.DumpAndCloseStream(file) - - exists := true - var primaryContainer string - var secondaryContainer string - var fileName string - var targetDir string - var targetFile string - attempts := 0 - for exists { - fileId, err := ids.NewUniqueId() - if err != nil { - return nil, err - } - - primaryContainer = fileId[0:2] - secondaryContainer = fileId[2:4] - fileName = fileId[4:] - targetDir = path.Join(basePath, primaryContainer, secondaryContainer) - targetFile = path.Join(targetDir, fileName) - - ctx.Log.Info("Checking if file exists: " + targetFile) - - exists, err = util.FileExists(targetFile) - attempts++ - - if err != nil { - ctx.Log.Error("Error checking if the file exists: ", err) - } - - // Infinite loop protection - if attempts > 5 { - return nil, errors.New("failed to find a suitable directory") - } - } - - err := os.MkdirAll(targetDir, 0755) - if err != nil { - return nil, err - } - - sizeBytes, hash, err := PersistFileAtLocation(targetFile, file, ctx) - if err != nil { - return nil, err - } - - locationPath := path.Join(primaryContainer, secondaryContainer, fileName) - return &types.ObjectInfo{ - Location: locationPath, - Sha256Hash: hash, - SizeBytes: sizeBytes, - }, nil -} - -func PersistFileAtLocation(targetFile string, file io.ReadCloser, ctx rcontext.RequestContext) (int64, string, error) { - defer stream_util.DumpAndCloseStream(file) - - f, err := os.OpenFile(targetFile, os.O_WRONLY|os.O_CREATE, 0644) - if err != nil { - return 0, "", err - } - defer stream_util.DumpAndCloseStream(f) - - rfile, wfile := io.Pipe() - tr := io.TeeReader(file, wfile) - - done := make(chan bool) - defer close(done) - - var hash string - var sizeBytes int64 - var hashErr error - var writeErr error - - go func() { - defer wfile.Close() - ctx.Log.Info("Calculating hash of stream...") - hash, hashErr = stream_util.GetSha256HashOfStream(io.NopCloser(tr)) - ctx.Log.Info("Hash of file is ", hash) - done <- true - }() - - go func() { - ctx.Log.Info("Writing file...") - sizeBytes, writeErr = io.Copy(f, rfile) - ctx.Log.Info("Wrote ", sizeBytes, " bytes to file") - done <- true - }() - - for c := 0; c < 2; c++ { - <-done - } - - if hashErr != nil { - defer os.Remove(targetFile) - return 0, "", hashErr - } - - if writeErr != nil { - return 0, "", writeErr - } - - return sizeBytes, hash, nil -} - -func DeletePersistedFile(basePath string, location string) error { - err := os.Remove(path.Join(basePath, location)) - if err != nil && os.IsNotExist(err) { - // It didn't exist, so pretend it was successful - return nil - } - return err -} diff --git a/storage/datastore/ds_s3/s3_store.go b/storage/datastore/ds_s3/s3_store.go deleted file mode 100644 index 3a3914e8b9a34cc620eb72cb38ad380e3a2edfa0..0000000000000000000000000000000000000000 --- a/storage/datastore/ds_s3/s3_store.go +++ /dev/null @@ -1,253 +0,0 @@ -package ds_s3 - -import ( - "errors" - "fmt" - "io" - "os" - "strconv" - "strings" - - "github.com/minio/minio-go/v6" - "github.com/turt2live/matrix-media-repo/util/ids" - "github.com/turt2live/matrix-media-repo/util/stream_util" - - "github.com/prometheus/client_golang/prometheus" - "github.com/sirupsen/logrus" - "github.com/turt2live/matrix-media-repo/common/config" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/metrics" - "github.com/turt2live/matrix-media-repo/types" -) - -var stores = make(map[string]*s3Datastore) - -type s3Datastore struct { - conf config.DatastoreConfig - dsId string - client *minio.Client - bucket string - region string - tempPath string - storageClass string -} - -func GetOrCreateS3Datastore(dsId string, conf config.DatastoreConfig) (*s3Datastore, error) { - if s, ok := stores[dsId]; ok { - return s, nil - } - - endpoint, epFound := conf.Options["endpoint"] - bucket, bucketFound := conf.Options["bucketName"] - accessKeyId, keyFound := conf.Options["accessKeyId"] - accessSecret, secretFound := conf.Options["accessSecret"] - region, regionFound := conf.Options["region"] - tempPath, tempPathFound := conf.Options["tempPath"] - storageClass, storageClassFound := conf.Options["storageClass"] - if !epFound || !bucketFound || !keyFound || !secretFound { - return nil, errors.New("invalid configuration: missing s3 options") - } - if !tempPathFound { - logrus.Warn("Datastore ", dsId, " (s3) does not have a tempPath set - this could lead to excessive memory usage by the media repo") - } - if !storageClassFound { - storageClass = "STANDARD" - } - - useSsl := true - useSslStr, sslFound := conf.Options["ssl"] - if sslFound && useSslStr != "" { - useSsl, _ = strconv.ParseBool(useSslStr) - } - - var s3client *minio.Client - var err error - - if regionFound { - s3client, err = minio.NewWithRegion(endpoint, accessKeyId, accessSecret, useSsl, region) - } else { - s3client, err = minio.New(endpoint, accessKeyId, accessSecret, useSsl) - } - if err != nil { - return nil, err - } - - s3ds := &s3Datastore{ - conf: conf, - dsId: dsId, - client: s3client, - bucket: bucket, - region: region, - tempPath: tempPath, - storageClass: storageClass, - } - stores[dsId] = s3ds - return s3ds, nil -} - -func GetS3URL(datastoreId string, location string) (string, error) { - var store *s3Datastore - var ok bool - if store, ok = stores[datastoreId]; !ok { - return "", errors.New("s3 datastore not found") - } - - // HACK: Surely there's a better way... - return fmt.Sprintf("https://%s/%s/%s", store.conf.Options["endpoint"], store.bucket, location), nil -} - -func ParseS3URL(s3url string) (string, string, string, error) { - trimmed := s3url[8:] // trim off https - parts := strings.Split(trimmed, "/") - if len(parts) < 3 { - return "", "", "", errors.New("invalid url") - } - - endpoint := parts[0] - location := parts[len(parts)-1] - bucket := strings.Join(parts[1:len(parts)-1], "/") - - return endpoint, bucket, location, nil -} - -func (s *s3Datastore) EnsureBucketExists() error { - found, err := s.client.BucketExists(s.bucket) - if err != nil { - return err - } - if !found { - return errors.New("bucket not found") - } - return nil -} - -func (s *s3Datastore) EnsureTempPathExists() error { - err := os.MkdirAll(s.tempPath, os.ModePerm) - if err != os.ErrExist && err != nil { - return err - } - return nil -} - -func (s *s3Datastore) UploadFile(file io.ReadCloser, expectedLength int64, ctx rcontext.RequestContext) (*types.ObjectInfo, error) { - defer stream_util.DumpAndCloseStream(file) - - objectName, err := ids.NewUniqueId() - if err != nil { - return nil, err - } - - var rs3 io.ReadCloser - var ws3 io.WriteCloser - rs3, ws3 = io.Pipe() - tr := io.TeeReader(file, ws3) - - done := make(chan bool) - defer close(done) - - var hash string - var sizeBytes int64 - var hashErr error - var uploadErr error - - go func() { - defer ws3.Close() - ctx.Log.Info("Calculating hash of stream...") - hash, hashErr = stream_util.GetSha256HashOfStream(io.NopCloser(tr)) - ctx.Log.Info("Hash of file is ", hash) - done <- true - }() - - go func() { - if expectedLength <= 0 { - if s.tempPath != "" { - ctx.Log.Info("Buffering file to temp path due to unknown file size") - var f *os.File - f, uploadErr = os.CreateTemp(s.tempPath, "mr*") - if uploadErr != nil { - io.Copy(io.Discard, rs3) - done <- true - return - } - defer os.Remove(f.Name()) - expectedLength, uploadErr = io.Copy(f, rs3) - stream_util.DumpAndCloseStream(f) - f, uploadErr = os.Open(f.Name()) - if uploadErr != nil { - done <- true - return - } - rs3 = f - defer stream_util.DumpAndCloseStream(f) - } else { - ctx.Log.Warn("Uploading content of unknown length to s3 - this could result in high memory usage") - expectedLength = -1 - } - } - ctx.Log.Info("Uploading file...") - metrics.S3Operations.With(prometheus.Labels{"operation": "PutObject"}).Inc() - sizeBytes, uploadErr = s.client.PutObjectWithContext(ctx, s.bucket, objectName, rs3, expectedLength, minio.PutObjectOptions{StorageClass: s.storageClass}) - ctx.Log.Info("Uploaded ", sizeBytes, " bytes to s3") - done <- true - }() - - for c := 0; c < 2; c++ { - <-done - } - - obj := &types.ObjectInfo{ - Location: objectName, - Sha256Hash: hash, - SizeBytes: sizeBytes, - } - - if hashErr != nil { - s.DeleteObject(obj.Location) - return nil, hashErr - } - - if uploadErr != nil { - return nil, uploadErr - } - - return obj, nil -} - -func (s *s3Datastore) DeleteObject(location string) error { - logrus.Info("Deleting object from bucket ", s.bucket, ": ", location) - metrics.S3Operations.With(prometheus.Labels{"operation": "RemoveObject"}).Inc() - return s.client.RemoveObject(s.bucket, location) -} - -func (s *s3Datastore) DownloadObject(location string) (io.ReadCloser, error) { - logrus.Info("Downloading object from bucket ", s.bucket, ": ", location) - metrics.S3Operations.With(prometheus.Labels{"operation": "GetObject"}).Inc() - return s.client.GetObject(s.bucket, location, minio.GetObjectOptions{}) -} - -func (s *s3Datastore) ObjectExists(location string) bool { - metrics.S3Operations.With(prometheus.Labels{"operation": "StatObject"}).Inc() - stat, err := s.client.StatObject(s.bucket, location, minio.StatObjectOptions{}) - if err != nil { - return false - } - return stat.Size > 0 -} - -func (s *s3Datastore) OverwriteObject(location string, stream io.ReadCloser) error { - defer stream_util.DumpAndCloseStream(stream) - metrics.S3Operations.With(prometheus.Labels{"operation": "PutObject"}).Inc() - _, err := s.client.PutObject(s.bucket, location, stream, -1, minio.PutObjectOptions{StorageClass: s.storageClass}) - return err -} - -func (s *s3Datastore) ListObjects() ([]string, error) { - doneCh := make(chan struct{}) - defer close(doneCh) - list := make([]string, 0) - metrics.S3Operations.With(prometheus.Labels{"operation": "ListObjectsV2"}).Inc() - for message := range s.client.ListObjectsV2(s.bucket, "", true, doneCh) { - list = append(list, message.Key) - } - return list, nil -} diff --git a/storage/ds_utils.go b/storage/ds_utils.go deleted file mode 100644 index d925348f5ec754df4864c6f7ce89b9fc5223ac5d..0000000000000000000000000000000000000000 --- a/storage/ds_utils.go +++ /dev/null @@ -1,60 +0,0 @@ -package storage - -import ( - "database/sql" - "github.com/turt2live/matrix-media-repo/util/ids" - - "github.com/sirupsen/logrus" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/storage/stores" - "github.com/turt2live/matrix-media-repo/types" -) - -func GetOrCreateDatastoreOfType(ctx rcontext.RequestContext, dsType string, dsUri string) (*types.Datastore, error) { - mediaService := GetDatabase().GetMediaStore(ctx) - datastore, err := mediaService.GetDatastoreByUri(dsUri) - if err != nil && err == sql.ErrNoRows { - id, err2 := ids.NewUniqueId() - if err2 != nil { - ctx.Log.Error("Error generating datastore ID for URI ", dsUri, ": ", err) - return nil, err2 - } - datastore = &types.Datastore{ - DatastoreId: id, - Type: dsType, - Uri: dsUri, - } - err2 = mediaService.InsertDatastore(datastore) - if err2 != nil { - ctx.Log.Error("Error creating datastore for URI ", dsUri, ": ", err) - return nil, err2 - } - } - return datastore, nil -} - -func getOrCreateDatastoreWithMediaService(mediaService *stores.MediaStore, basePath string) (*types.Datastore, error) { - datastore, err := mediaService.GetDatastoreByUri(basePath) - if err != nil && err == sql.ErrNoRows { - id, err2 := ids.NewUniqueId() - if err2 != nil { - logrus.Error("Error generating datastore ID for base path ", basePath, ": ", err) - return nil, err2 - } - datastore = &types.Datastore{ - DatastoreId: id, - Type: "file", - Uri: basePath, - } - err2 = mediaService.InsertDatastore(datastore) - if err2 != nil { - logrus.Error("Error creating datastore for base path ", basePath, ": ", err) - return nil, err2 - } - } else if err != nil { - logrus.Error("Error getting datastore for base path ", basePath, ": ", err) - return nil, err - } - - return datastore, nil -} diff --git a/storage/startup_migrations.go b/storage/startup_migrations.go deleted file mode 100644 index f1dcb930a8a50efac1c8a383698bb0c05468381b..0000000000000000000000000000000000000000 --- a/storage/startup_migrations.go +++ /dev/null @@ -1,120 +0,0 @@ -package storage - -import ( - "github.com/getsentry/sentry-go" - "path" - - "github.com/sirupsen/logrus" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/util" -) - -func populateThumbnailHashes(db *Database) error { - svc := db.GetThumbnailStore(rcontext.Initial()) - mediaSvc := db.GetMediaStore(rcontext.Initial()) - - thumbs, err := svc.GetAllWithoutHash() - if err != nil { - logrus.Error("Failed to get thumbnails that don't have hashes: ", err) - return err - } - - for _, thumb := range thumbs { - datastore, err := mediaSvc.GetDatastore(thumb.DatastoreId) - if err != nil { - logrus.Error("Error getting datastore for thumbnail ", thumb.Origin, " ", thumb.MediaId, ": ", err) - sentry.CaptureException(err) - continue - } - if datastore.Type != "file" { - logrus.Error("Unrecognized datastore type for thumbnail ", thumb.Origin, " ", thumb.MediaId) - sentry.CaptureException(err) - continue - } - location := path.Join(datastore.Uri, thumb.Location) - - hash, err := util.GetFileHash(location) - if err != nil { - logrus.Error("Failed to generate hash for location '", location, "': ", err) - return err - } - - thumb.Sha256Hash = hash - err = svc.UpdateHash(thumb) - if err != nil { - logrus.Error("Failed to update hash for '", location, "': ", err) - return err - } - - logrus.Info("Updated hash for thumbnail at '", location, "' as ", hash) - } - - return nil -} - -func populateDatastores(db *Database) error { - logrus.Info("Starting to populate datastores...") - - thumbService := db.GetThumbnailStore(rcontext.Initial()) - mediaService := db.GetMediaStore(rcontext.Initial()) - - logrus.Info("Fetching thumbnails...") - thumbs, err := thumbService.GetAllWithoutDatastore() - if err != nil { - logrus.Error("Failed to get thumbnails that don't have a datastore: ", err) - return err - } - - for _, thumb := range thumbs { - basePath := path.Clean(path.Join(thumb.Location, "..", "..", "..")) - datastore, err := getOrCreateDatastoreWithMediaService(mediaService, basePath) - if err != nil { - logrus.Error("Error getting datastore for thumbnail path ", basePath, ": ", err) - sentry.CaptureException(err) - continue - } - - thumb.DatastoreId = datastore.DatastoreId - thumb.Location = util.GetLastSegmentsOfPath(thumb.Location, 3) - - err = thumbService.UpdateDatastoreAndLocation(thumb) - if err != nil { - logrus.Error("Failed to update datastore for thumbnail ", thumb.Origin, " ", thumb.MediaId, ": ", err) - sentry.CaptureException(err) - continue - } - - logrus.Info("Updated datastore for thumbnail ", thumb.Origin, " ", thumb.MediaId) - } - - logrus.Info("Fetching media...") - mediaRecords, err := mediaService.GetAllWithoutDatastore() - if err != nil { - logrus.Error("Failed to get media that doesn't have a datastore: ", err) - return err - } - - for _, media := range mediaRecords { - basePath := path.Clean(path.Join(media.Location, "..", "..", "..")) - datastore, err := getOrCreateDatastoreWithMediaService(mediaService, basePath) - if err != nil { - logrus.Error("Error getting datastore for media path ", basePath, ": ", err) - sentry.CaptureException(err) - continue - } - - media.DatastoreId = datastore.DatastoreId - media.Location = util.GetLastSegmentsOfPath(media.Location, 3) - - err = mediaService.UpdateDatastoreAndLocation(media) - if err != nil { - logrus.Error("Failed to update datastore for media ", media.Origin, " ", media.MediaId, ": ", err) - sentry.CaptureException(err) - continue - } - - logrus.Info("Updated datastore for media ", media.Origin, " ", media.MediaId) - } - - return nil -} diff --git a/storage/storage.go b/storage/storage.go deleted file mode 100644 index 6a84272375c309758662ab49457939beee1e1feb..0000000000000000000000000000000000000000 --- a/storage/storage.go +++ /dev/null @@ -1,127 +0,0 @@ -package storage - -import ( - "database/sql" - "sync" - - "github.com/DavidHuie/gomigrate" - _ "github.com/lib/pq" // postgres driver - "github.com/sirupsen/logrus" - "github.com/turt2live/matrix-media-repo/common/config" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/storage/stores" -) - -type Database struct { - db *sql.DB - repos repos -} - -type repos struct { - mediaStore *stores.MediaStoreFactory - thumbnailStore *stores.ThumbnailStoreFactory - urlStore *stores.UrlStoreFactory - metadataStore *stores.MetadataStoreFactory - exportStore *stores.ExportStoreFactory - mediaAttributesStore *stores.MediaAttributesStoreFactory -} - -var dbInstance *Database -var singletonDbLock = &sync.Once{} - -func GetDatabase() *Database { - if dbInstance == nil { - singletonDbLock.Do(func() { - err := OpenDatabase( - config.Get().Database.Postgres, - config.Get().Database.Pool.MaxConnections, - config.Get().Database.Pool.MaxIdle) - if err != nil { - panic(err) - } - }) - } - return dbInstance -} - -func OpenDatabase(connectionString string, maxConns int, maxIdleConns int) error { - d := &Database{} - var err error - - if d.db, err = sql.Open("postgres", connectionString); err != nil { - return err - } - d.db.SetMaxOpenConns(maxConns) - d.db.SetMaxIdleConns(maxIdleConns) - - // Make sure the database is how we want it - migrator, err := gomigrate.NewMigratorWithLogger(d.db, gomigrate.Postgres{}, config.Runtime.MigrationsPath, logrus.StandardLogger()) - if err != nil { - return err - } - err = migrator.Migrate() - if err != nil { - return err - } - - // New the repo factories - logrus.Info("Setting up media DB store...") - if d.repos.mediaStore, err = stores.InitMediaStore(d.db); err != nil { - return err - } - logrus.Info("Setting up thumbnails DB store...") - if d.repos.thumbnailStore, err = stores.InitThumbnailStore(d.db); err != nil { - return err - } - logrus.Info("Setting up URL previews DB store...") - if d.repos.urlStore, err = stores.InitUrlStore(d.db); err != nil { - return err - } - logrus.Info("Setting up metadata DB store...") - if d.repos.metadataStore, err = stores.InitMetadataStore(d.db); err != nil { - return err - } - logrus.Info("Setting up export DB store...") - if d.repos.exportStore, err = stores.InitExportStore(d.db); err != nil { - return err - } - logrus.Info("Setting up media attributes DB store...") - if d.repos.mediaAttributesStore, err = stores.InitMediaAttributesStore(d.db); err != nil { - return err - } - - // Run some tasks that should always be done on startup - if err = populateDatastores(d); err != nil { - return err - } - if err = populateThumbnailHashes(d); err != nil { - return err - } - - dbInstance = d - return nil -} - -func (d *Database) GetMediaStore(ctx rcontext.RequestContext) *stores.MediaStore { - return d.repos.mediaStore.Create(ctx) -} - -func (d *Database) GetThumbnailStore(ctx rcontext.RequestContext) *stores.ThumbnailStore { - return d.repos.thumbnailStore.New(ctx) -} - -func (d *Database) GetUrlStore(ctx rcontext.RequestContext) *stores.UrlStore { - return d.repos.urlStore.Create(ctx) -} - -func (d *Database) GetMetadataStore(ctx rcontext.RequestContext) *stores.MetadataStore { - return d.repos.metadataStore.Create(ctx) -} - -func (d *Database) GetExportStore(ctx rcontext.RequestContext) *stores.ExportStore { - return d.repos.exportStore.Create(ctx) -} - -func (d *Database) GetMediaAttributesStore(ctx rcontext.RequestContext) *stores.MediaAttributesStore { - return d.repos.mediaAttributesStore.Create(ctx) -} diff --git a/storage/stores/export_store.go b/storage/stores/export_store.go deleted file mode 100644 index 9fc233ae954049de41ea55cf9acc04f6bcfc4907..0000000000000000000000000000000000000000 --- a/storage/stores/export_store.go +++ /dev/null @@ -1,148 +0,0 @@ -package stores - -import ( - "database/sql" - - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/types" -) - -const insertExportMetadata = "INSERT INTO exports (export_id, entity) VALUES ($1, $2);" -const insertExportPart = "INSERT INTO export_parts (export_id, index, size_bytes, file_name, datastore_id, location) VALUES ($1, $2, $3, $4, $5, $6);" -const selectExportMetadata = "SELECT export_id, entity FROM exports WHERE export_id = $1;" -const selectExportParts = "SELECT export_id, index, size_bytes, file_name, datastore_id, location FROM export_parts WHERE export_id = $1;" -const selectExportPart = "SELECT export_id, index, size_bytes, file_name, datastore_id, location FROM export_parts WHERE export_id = $1 AND index = $2;" -const deleteExportParts = "DELETE FROM export_parts WHERE export_id = $1;" -const deleteExport = "DELETE FROM exports WHERE export_id = $1;" - -type exportStoreStatements struct { - insertExportMetadata *sql.Stmt - insertExportPart *sql.Stmt - selectExportMetadata *sql.Stmt - selectExportParts *sql.Stmt - selectExportPart *sql.Stmt - deleteExportParts *sql.Stmt - deleteExport *sql.Stmt -} - -type ExportStoreFactory struct { - sqlDb *sql.DB - stmts *exportStoreStatements -} - -type ExportStore struct { - factory *ExportStoreFactory // just for reference - ctx rcontext.RequestContext - statements *exportStoreStatements // copied from factory -} - -func InitExportStore(sqlDb *sql.DB) (*ExportStoreFactory, error) { - store := ExportStoreFactory{stmts: &exportStoreStatements{}} - var err error - - store.sqlDb = sqlDb - - if store.stmts.insertExportMetadata, err = store.sqlDb.Prepare(insertExportMetadata); err != nil { - return nil, err - } - if store.stmts.insertExportPart, err = store.sqlDb.Prepare(insertExportPart); err != nil { - return nil, err - } - if store.stmts.selectExportMetadata, err = store.sqlDb.Prepare(selectExportMetadata); err != nil { - return nil, err - } - if store.stmts.selectExportParts, err = store.sqlDb.Prepare(selectExportParts); err != nil { - return nil, err - } - if store.stmts.selectExportPart, err = store.sqlDb.Prepare(selectExportPart); err != nil { - return nil, err - } - if store.stmts.deleteExportParts, err = store.sqlDb.Prepare(deleteExportParts); err != nil { - return nil, err - } - if store.stmts.deleteExport, err = store.sqlDb.Prepare(deleteExport); err != nil { - return nil, err - } - - return &store, nil -} - -func (f *ExportStoreFactory) Create(ctx rcontext.RequestContext) *ExportStore { - return &ExportStore{ - factory: f, - ctx: ctx, - statements: f.stmts, // we copy this intentionally - } -} - -func (s *ExportStore) InsertExport(exportId string, entity string) error { - _, err := s.statements.insertExportMetadata.ExecContext(s.ctx, exportId, entity) - return err -} - -func (s *ExportStore) InsertExportPart(exportId string, index int, size int64, name string, datastoreId string, location string) error { - _, err := s.statements.insertExportPart.ExecContext(s.ctx, exportId, index, size, name, datastoreId, location) - return err -} - -func (s *ExportStore) GetExportMetadata(exportId string) (*types.ExportMetadata, error) { - m := &types.ExportMetadata{} - err := s.statements.selectExportMetadata.QueryRowContext(s.ctx, exportId).Scan( - &m.ExportID, - &m.Entity, - ) - return m, err -} - -func (s *ExportStore) GetExportParts(exportId string) ([]*types.ExportPart, error) { - rows, err := s.statements.selectExportParts.QueryContext(s.ctx, exportId) - if err != nil { - return nil, err - } - - var results []*types.ExportPart - for rows.Next() { - obj := &types.ExportPart{} - err = rows.Scan( - &obj.ExportID, - &obj.Index, - &obj.SizeBytes, - &obj.FileName, - &obj.DatastoreID, - &obj.Location, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *ExportStore) GetExportPart(exportId string, index int) (*types.ExportPart, error) { - m := &types.ExportPart{} - err := s.statements.selectExportPart.QueryRowContext(s.ctx, exportId, index).Scan( - &m.ExportID, - &m.Index, - &m.SizeBytes, - &m.FileName, - &m.DatastoreID, - &m.Location, - ) - return m, err -} - -func (s *ExportStore) DeleteExportAndParts(exportId string) error { - _, err := s.statements.deleteExportParts.ExecContext(s.ctx, exportId) - if err != nil { - return err - } - - _, err = s.statements.deleteExport.ExecContext(s.ctx, exportId) - if err != nil { - return err - } - - return nil -} diff --git a/storage/stores/media_attributes_store.go b/storage/stores/media_attributes_store.go deleted file mode 100644 index ef4827a0bf86bae8f50b84489da21d19ab95968b..0000000000000000000000000000000000000000 --- a/storage/stores/media_attributes_store.go +++ /dev/null @@ -1,79 +0,0 @@ -package stores - -import ( - "database/sql" - - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/types" -) - -const selectMediaAttributes = "SELECT origin, media_id, purpose FROM media_attributes WHERE origin = $1 AND media_id = $2;" -const upsertMediaPurpose = "INSERT INTO media_attributes (origin, media_id, purpose) VALUES ($1, $2, $3) ON CONFLICT (origin, media_id) DO UPDATE SET purpose = $3;" - -type mediaAttributesStoreStatements struct { - selectMediaAttributes *sql.Stmt - upsertMediaPurpose *sql.Stmt -} - -type MediaAttributesStoreFactory struct { - sqlDb *sql.DB - stmts *mediaAttributesStoreStatements -} - -type MediaAttributesStore struct { - factory *MediaAttributesStoreFactory // just for reference - ctx rcontext.RequestContext - statements *mediaAttributesStoreStatements // copied from factory -} - -func InitMediaAttributesStore(sqlDb *sql.DB) (*MediaAttributesStoreFactory, error) { - store := MediaAttributesStoreFactory{stmts: &mediaAttributesStoreStatements{}} - var err error - - store.sqlDb = sqlDb - - if store.stmts.selectMediaAttributes, err = store.sqlDb.Prepare(selectMediaAttributes); err != nil { - return nil, err - } - if store.stmts.upsertMediaPurpose, err = store.sqlDb.Prepare(upsertMediaPurpose); err != nil { - return nil, err - } - - return &store, nil -} - -func (f *MediaAttributesStoreFactory) Create(ctx rcontext.RequestContext) *MediaAttributesStore { - return &MediaAttributesStore{ - factory: f, - ctx: ctx, - statements: f.stmts, // we copy this intentionally - } -} - -func (s *MediaAttributesStore) GetAttributes(origin string, mediaId string) (*types.MediaAttributes, error) { - r := s.statements.selectMediaAttributes.QueryRowContext(s.ctx, origin, mediaId) - obj := &types.MediaAttributes{} - err := r.Scan( - &obj.Origin, - &obj.MediaId, - &obj.Purpose, - ) - return obj, err -} - -func (s *MediaAttributesStore) GetAttributesDefaulted(origin string, mediaId string) (*types.MediaAttributes, error) { - attr, err := s.GetAttributes(origin, mediaId) - if err == sql.ErrNoRows { - return &types.MediaAttributes{ - Origin: origin, - MediaId: mediaId, - Purpose: types.PurposeNone, - }, nil - } - return attr, err -} - -func (s *MediaAttributesStore) UpsertPurpose(origin string, mediaId string, purpose string) error { - _, err := s.statements.upsertMediaPurpose.ExecContext(s.ctx, origin, mediaId, purpose) - return err -} diff --git a/storage/stores/media_store.go b/storage/stores/media_store.go deleted file mode 100644 index 958f1b0a79447667b2ab54d1f515b770d23dc9a3..0000000000000000000000000000000000000000 --- a/storage/stores/media_store.go +++ /dev/null @@ -1,860 +0,0 @@ -package stores - -import ( - "database/sql" - "errors" - "fmt" - "github.com/turt2live/matrix-media-repo/util" - "strings" - "sync" - - "github.com/lib/pq" - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/types" -) - -const selectMedia = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE origin = $1 and media_id = $2;" -const selectMediaByHash = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE sha256_hash = $1;" -const insertMedia = "INSERT INTO media (origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);" -const selectOldMedia = "SELECT m.origin, m.media_id, m.upload_name, m.content_type, m.user_id, m.sha256_hash, m.size_bytes, m.datastore_id, m.location, m.creation_ts, quarantined FROM media AS m WHERE m.origin <> ANY($1) AND m.creation_ts < $2 AND (SELECT COUNT(*) FROM media AS d WHERE d.sha256_hash = m.sha256_hash AND d.creation_ts >= $2) = 0 AND (SELECT COUNT(*) FROM media AS d WHERE d.sha256_hash = m.sha256_hash AND d.origin = ANY($1)) = 0;" -const selectOrigins = "SELECT DISTINCT origin FROM media;" -const deleteMedia = "DELETE FROM media WHERE origin = $1 AND media_id = $2;" -const updateQuarantined = "UPDATE media SET quarantined = $3 WHERE origin = $1 AND media_id = $2;" -const selectDatastore = "SELECT datastore_id, ds_type, uri FROM datastores WHERE datastore_id = $1;" -const selectDatastoreByUri = "SELECT datastore_id, ds_type, uri FROM datastores WHERE uri = $1;" -const insertDatastore = "INSERT INTO datastores (datastore_id, ds_type, uri) VALUES ($1, $2, $3);" -const selectMediaWithoutDatastore = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE datastore_id IS NULL OR datastore_id = '';" -const updateMediaDatastoreAndLocation = "UPDATE media SET location = $4, datastore_id = $3 WHERE origin = $1 AND media_id = $2;" -const selectAllDatastores = "SELECT datastore_id, ds_type, uri FROM datastores;" -const selectAllMediaForServer = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE origin = $1" -const selectAllMediaForServerUsers = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE origin = $1 AND user_id = ANY($2)" -const selectAllMediaForServerIds = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE origin = $1 AND media_id = ANY($2)" -const selectQuarantinedMedia = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE quarantined = true;" -const selectServerQuarantinedMedia = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE quarantined = true AND origin = $1;" -const selectMediaByUser = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE user_id = $1" -const selectMediaByUserBefore = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE user_id = $1 AND creation_ts <= $2" -const selectMediaByDomainBefore = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE origin = $1 AND creation_ts <= $2" -const selectMediaByLocation = "SELECT origin, media_id, upload_name, content_type, user_id, sha256_hash, size_bytes, datastore_id, location, creation_ts, quarantined FROM media WHERE datastore_id = $1 AND location = $2" -const selectIfQuarantined = "SELECT 1 FROM media WHERE sha256_hash = $1 AND quarantined = $2 LIMIT 1;" -const selectMediaLocationsForDatastore = "SELECT distinct location FROM media WHERE datastore_id = $1;" - -var UsersUsageStatsSorts = []string{"media_count", "media_length", "user_id"} - -var dsCacheByPath = sync.Map{} // [string] => Datastore -var dsCacheById = sync.Map{} // [string] => Datastore - -type mediaStoreStatements struct { - selectMedia *sql.Stmt - selectMediaByHash *sql.Stmt - insertMedia *sql.Stmt - selectOldMedia *sql.Stmt - selectOrigins *sql.Stmt - deleteMedia *sql.Stmt - updateQuarantined *sql.Stmt - selectDatastore *sql.Stmt - selectDatastoreByUri *sql.Stmt - insertDatastore *sql.Stmt - selectMediaWithoutDatastore *sql.Stmt - updateMediaDatastoreAndLocation *sql.Stmt - selectAllDatastores *sql.Stmt - selectMediaInDatastoreOlderThan *sql.Stmt - selectAllMediaForServer *sql.Stmt - selectAllMediaForServerUsers *sql.Stmt - selectAllMediaForServerIds *sql.Stmt - selectQuarantinedMedia *sql.Stmt - selectServerQuarantinedMedia *sql.Stmt - selectMediaByUser *sql.Stmt - selectMediaByUserBefore *sql.Stmt - selectMediaByDomainBefore *sql.Stmt - selectMediaByLocation *sql.Stmt - selectIfQuarantined *sql.Stmt - selectMediaLocationsForDatastore *sql.Stmt -} - -type MediaStoreFactory struct { - sqlDb *sql.DB - stmts *mediaStoreStatements -} - -type MediaStore struct { - factory *MediaStoreFactory // just for reference - ctx rcontext.RequestContext - statements *mediaStoreStatements // copied from factory -} - -func InitMediaStore(sqlDb *sql.DB) (*MediaStoreFactory, error) { - store := MediaStoreFactory{stmts: &mediaStoreStatements{}} - var err error - - store.sqlDb = sqlDb - - if store.stmts.selectMedia, err = store.sqlDb.Prepare(selectMedia); err != nil { - return nil, err - } - if store.stmts.selectMediaByHash, err = store.sqlDb.Prepare(selectMediaByHash); err != nil { - return nil, err - } - if store.stmts.insertMedia, err = store.sqlDb.Prepare(insertMedia); err != nil { - return nil, err - } - if store.stmts.selectOldMedia, err = store.sqlDb.Prepare(selectOldMedia); err != nil { - return nil, err - } - if store.stmts.selectOrigins, err = store.sqlDb.Prepare(selectOrigins); err != nil { - return nil, err - } - if store.stmts.deleteMedia, err = store.sqlDb.Prepare(deleteMedia); err != nil { - return nil, err - } - if store.stmts.updateQuarantined, err = store.sqlDb.Prepare(updateQuarantined); err != nil { - return nil, err - } - if store.stmts.selectDatastore, err = store.sqlDb.Prepare(selectDatastore); err != nil { - return nil, err - } - if store.stmts.selectDatastoreByUri, err = store.sqlDb.Prepare(selectDatastoreByUri); err != nil { - return nil, err - } - if store.stmts.insertDatastore, err = store.sqlDb.Prepare(insertDatastore); err != nil { - return nil, err - } - if store.stmts.selectMediaWithoutDatastore, err = store.sqlDb.Prepare(selectMediaWithoutDatastore); err != nil { - return nil, err - } - if store.stmts.updateMediaDatastoreAndLocation, err = store.sqlDb.Prepare(updateMediaDatastoreAndLocation); err != nil { - return nil, err - } - if store.stmts.selectAllDatastores, err = store.sqlDb.Prepare(selectAllDatastores); err != nil { - return nil, err - } - if store.stmts.selectAllMediaForServer, err = store.sqlDb.Prepare(selectAllMediaForServer); err != nil { - return nil, err - } - if store.stmts.selectAllMediaForServerUsers, err = store.sqlDb.Prepare(selectAllMediaForServerUsers); err != nil { - return nil, err - } - if store.stmts.selectAllMediaForServerIds, err = store.sqlDb.Prepare(selectAllMediaForServerIds); err != nil { - return nil, err - } - if store.stmts.selectQuarantinedMedia, err = store.sqlDb.Prepare(selectQuarantinedMedia); err != nil { - return nil, err - } - if store.stmts.selectServerQuarantinedMedia, err = store.sqlDb.Prepare(selectServerQuarantinedMedia); err != nil { - return nil, err - } - if store.stmts.selectMediaByUser, err = store.sqlDb.Prepare(selectMediaByUser); err != nil { - return nil, err - } - if store.stmts.selectMediaByUserBefore, err = store.sqlDb.Prepare(selectMediaByUserBefore); err != nil { - return nil, err - } - if store.stmts.selectMediaByDomainBefore, err = store.sqlDb.Prepare(selectMediaByDomainBefore); err != nil { - return nil, err - } - if store.stmts.selectMediaByLocation, err = store.sqlDb.Prepare(selectMediaByLocation); err != nil { - return nil, err - } - if store.stmts.selectIfQuarantined, err = store.sqlDb.Prepare(selectIfQuarantined); err != nil { - return nil, err - } - if store.stmts.selectMediaLocationsForDatastore, err = store.sqlDb.Prepare(selectMediaLocationsForDatastore); err != nil { - return nil, err - } - - return &store, nil -} - -func (f *MediaStoreFactory) Create(ctx rcontext.RequestContext) *MediaStore { - return &MediaStore{ - factory: f, - ctx: ctx, - statements: f.stmts, // we copy this intentionally - } -} - -func (s *MediaStore) Insert(media *types.Media) error { - _, err := s.statements.insertMedia.ExecContext( - s.ctx, - media.Origin, - media.MediaId, - media.UploadName, - media.ContentType, - media.UserId, - media.Sha256Hash, - media.SizeBytes, - media.DatastoreId, - media.Location, - media.CreationTs, - media.Quarantined, - ) - return err -} - -func (s *MediaStore) GetByHash(hash string) ([]*types.Media, error) { - rows, err := s.statements.selectMediaByHash.QueryContext(s.ctx, hash) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) Get(origin string, mediaId string) (*types.Media, error) { - m := &types.Media{} - err := s.statements.selectMedia.QueryRowContext(s.ctx, origin, mediaId).Scan( - &m.Origin, - &m.MediaId, - &m.UploadName, - &m.ContentType, - &m.UserId, - &m.Sha256Hash, - &m.SizeBytes, - &m.DatastoreId, - &m.Location, - &m.CreationTs, - &m.Quarantined, - ) - return m, err -} - -func (s *MediaStore) GetOldMedia(exceptOrigins []string, beforeTs int64) ([]*types.Media, error) { - rows, err := s.statements.selectOldMedia.QueryContext(s.ctx, pq.Array(exceptOrigins), beforeTs) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetOrigins() ([]string, error) { - rows, err := s.statements.selectOrigins.QueryContext(s.ctx) - if err != nil { - return nil, err - } - - var results []string - for rows.Next() { - obj := "" - err = rows.Scan(&obj) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) Delete(origin string, mediaId string) error { - _, err := s.statements.deleteMedia.ExecContext(s.ctx, origin, mediaId) - return err -} - -func (s *MediaStore) SetQuarantined(origin string, mediaId string, isQuarantined bool) error { - _, err := s.statements.updateQuarantined.ExecContext(s.ctx, origin, mediaId, isQuarantined) - return err -} - -func (s *MediaStore) UpdateDatastoreAndLocation(media *types.Media) error { - _, err := s.statements.updateMediaDatastoreAndLocation.ExecContext( - s.ctx, - media.Origin, - media.MediaId, - media.DatastoreId, - media.Location, - ) - - return err -} - -func (s *MediaStore) GetDatastore(id string) (*types.Datastore, error) { - if v, ok := dsCacheById.Load(id); ok { - ds := v.(*types.Datastore) - return &types.Datastore{ - DatastoreId: ds.DatastoreId, - Type: ds.Type, - Uri: ds.Uri, - }, nil - } - - d := &types.Datastore{} - err := s.statements.selectDatastore.QueryRowContext(s.ctx, id).Scan( - &d.DatastoreId, - &d.Type, - &d.Uri, - ) - if err != nil { - dsCacheById.Store(d.Uri, d) - dsCacheByPath.Store(d.Uri, d) - } - - return &types.Datastore{ - DatastoreId: d.DatastoreId, - Type: d.Type, - Uri: d.Uri, - }, err -} - -func (s *MediaStore) InsertDatastore(datastore *types.Datastore) error { - _, err := s.statements.insertDatastore.ExecContext( - s.ctx, - datastore.DatastoreId, - datastore.Type, - datastore.Uri, - ) - if err != nil { - d := &types.Datastore{ - DatastoreId: datastore.DatastoreId, - Type: datastore.Type, - Uri: datastore.Uri, - } - dsCacheById.Store(d.Uri, d) - dsCacheByPath.Store(d.Uri, d) - } - return err -} - -func (s *MediaStore) GetDatastoreByUri(uri string) (*types.Datastore, error) { - if v, ok := dsCacheByPath.Load(uri); ok { - ds := v.(*types.Datastore) - return &types.Datastore{ - DatastoreId: ds.DatastoreId, - Type: ds.Type, - Uri: ds.Uri, - }, nil - } - - d := &types.Datastore{} - err := s.statements.selectDatastoreByUri.QueryRowContext(s.ctx, uri).Scan( - &d.DatastoreId, - &d.Type, - &d.Uri, - ) - if err != nil { - dsCacheById.Store(d.Uri, d) - dsCacheByPath.Store(d.Uri, d) - } - - return &types.Datastore{ - DatastoreId: d.DatastoreId, - Type: d.Type, - Uri: d.Uri, - }, err -} - -func (s *MediaStore) GetAllWithoutDatastore() ([]*types.Media, error) { - rows, err := s.statements.selectMediaWithoutDatastore.QueryContext(s.ctx) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetAllDatastores() ([]*types.Datastore, error) { - rows, err := s.statements.selectAllDatastores.QueryContext(s.ctx) - if err != nil { - return nil, err - } - - var results []*types.Datastore - for rows.Next() { - obj := &types.Datastore{} - err = rows.Scan( - &obj.DatastoreId, - &obj.Type, - &obj.Uri, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetAllMediaForServer(serverName string) ([]*types.Media, error) { - rows, err := s.statements.selectAllMediaForServer.QueryContext(s.ctx, serverName) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetUsersUsageStatsForServer( - serverName string, - orderBy string, - start int64, - limit int64, - fromTS int64, - untilTS int64, - searchTerm string, - isAscendingOrder bool, -) ([]*types.UserUsageStats, int64, error) { - if !util.ArrayContains(UsersUsageStatsSorts, orderBy) { - return nil, 0, errors.New("invalid orderBy") - } - if start < 0 { - return nil, 0, errors.New("invalid start") - } - if limit < 0 { - return nil, 0, errors.New("invalid limit") - } - - orderDirection := "DESC" - if isAscendingOrder { - orderDirection = "ASC" - } - - var queryParamIdx int64 = 1 - var commonQueryParams = []interface{}{serverName} - - var filters = []string{fmt.Sprintf("origin = $%d", queryParamIdx)} - queryParamIdx++ - if fromTS >= 0 { - filters = append(filters, fmt.Sprintf("creation_ts >= $%d", queryParamIdx)) - queryParamIdx++ - commonQueryParams = append(commonQueryParams, fromTS) - } - if untilTS >= 0 { - filters = append(filters, fmt.Sprintf("creation_ts <= $%d", queryParamIdx)) - queryParamIdx++ - commonQueryParams = append(commonQueryParams, untilTS) - } - if searchTerm != "" { - filters = append(filters, fmt.Sprintf("user_id LIKE $%d", queryParamIdx)) - queryParamIdx++ - commonQueryParams = append(commonQueryParams, fmt.Sprintf("@%%%s%%:%%", searchTerm)) - } - - var otherPaginationParams []interface{} - limitClause := fmt.Sprintf("LIMIT $%d", queryParamIdx) - queryParamIdx++ - otherPaginationParams = append(otherPaginationParams, limit) - - offsetClause := fmt.Sprintf("OFFSET $%d", queryParamIdx) - queryParamIdx++ - otherPaginationParams = append(otherPaginationParams, start) - - commonQueryPortion := fmt.Sprintf( - "FROM media "+ - "WHERE %s "+ - "GROUP BY user_id", strings.Join(filters, " AND ")) - - paginationQuery := fmt.Sprintf( - "SELECT COUNT(user_id) AS media_count, SUM(size_bytes) AS media_length, user_id "+ - "%s "+ - "ORDER BY %s %s "+ - "%s "+ - "%s;", - commonQueryPortion, - orderBy, - orderDirection, - limitClause, - offsetClause) - rows, err := s.factory.sqlDb.QueryContext( - s.ctx, - paginationQuery, - append(commonQueryParams, otherPaginationParams...)...) - if err != nil { - return nil, 0, err - } - - var results []*types.UserUsageStats - for rows.Next() { - obj := &types.UserUsageStats{} - err = rows.Scan( - &obj.MediaCount, - &obj.MediaLength, - &obj.UserId, - ) - if err != nil { - return nil, 0, err - } - results = append(results, obj) - } - - totalQuery := fmt.Sprintf( - "SELECT COUNT(*) "+ - "FROM ("+ - " SELECT user_id "+ - " %s "+ - ") as count_user_ids; ", - commonQueryPortion) - var totalNumRows int64 = 0 - err = s.factory.sqlDb.QueryRowContext(s.ctx, totalQuery, commonQueryParams...).Scan(&totalNumRows) - if err != nil { - return nil, 0, err - } - - return results, totalNumRows, nil -} - -func (s *MediaStore) GetAllMediaForServerUsers(serverName string, userIds []string) ([]*types.Media, error) { - rows, err := s.statements.selectAllMediaForServerUsers.QueryContext(s.ctx, serverName, pq.Array(userIds)) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetAllMediaInIds(serverName string, mediaIds []string) ([]*types.Media, error) { - rows, err := s.statements.selectAllMediaForServerIds.QueryContext(s.ctx, serverName, pq.Array(mediaIds)) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetAllQuarantinedMedia() ([]*types.Media, error) { - rows, err := s.statements.selectQuarantinedMedia.QueryContext(s.ctx) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetQuarantinedMediaFor(serverName string) ([]*types.Media, error) { - rows, err := s.statements.selectServerQuarantinedMedia.QueryContext(s.ctx, serverName) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetMediaByUser(userId string) ([]*types.Media, error) { - rows, err := s.statements.selectMediaByUser.QueryContext(s.ctx, userId) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetMediaByUserBefore(userId string, beforeTs int64) ([]*types.Media, error) { - rows, err := s.statements.selectMediaByUserBefore.QueryContext(s.ctx, userId, beforeTs) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetMediaByDomainBefore(serverName string, beforeTs int64) ([]*types.Media, error) { - rows, err := s.statements.selectMediaByDomainBefore.QueryContext(s.ctx, serverName, beforeTs) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) GetMediaByLocation(datastoreId string, location string) ([]*types.Media, error) { - rows, err := s.statements.selectMediaByLocation.QueryContext(s.ctx, datastoreId, location) - if err != nil { - return nil, err - } - - var results []*types.Media - for rows.Next() { - obj := &types.Media{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.UploadName, - &obj.ContentType, - &obj.UserId, - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Quarantined, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MediaStore) IsQuarantined(sha256hash string) (bool, error) { - r := s.statements.selectIfQuarantined.QueryRow(sha256hash, true) - var i int - err := r.Scan(&i) - if err == sql.ErrNoRows { - return false, nil - } else if err != nil { - return false, err - } - return true, nil -} - -func (s *MediaStore) GetDistinctLocationsForDatastore(datastoreId string) ([]string, error) { - rows, err := s.statements.selectMediaLocationsForDatastore.QueryContext(s.ctx, datastoreId) - if err != nil { - return nil, err - } - - locations := make([]string, 0) - for rows.Next() { - s := "" - err = rows.Scan(&s) - if err != nil { - return nil, err - } - locations = append(locations, s) - } - - return locations, nil -} diff --git a/storage/stores/metadata_store.go b/storage/stores/metadata_store.go deleted file mode 100644 index 82a4c22f11b2be3d5efb7d8a3e5a890f45584e5e..0000000000000000000000000000000000000000 --- a/storage/stores/metadata_store.go +++ /dev/null @@ -1,429 +0,0 @@ -package stores - -import ( - "database/sql" - "encoding/json" - - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/types" - "github.com/turt2live/matrix-media-repo/util" -) - -type folderSize struct { - Size int64 -} - -const selectSizeOfDatastore = "SELECT COALESCE(SUM(size_bytes), 0) + COALESCE((SELECT SUM(size_bytes) FROM thumbnails WHERE datastore_id = $1), 0) AS size_total FROM media WHERE datastore_id = $1;" -const upsertLastAccessed = "INSERT INTO last_access (sha256_hash, last_access_ts) VALUES ($1, $2) ON CONFLICT (sha256_hash) DO UPDATE SET last_access_ts = $2" -const selectMediaLastAccessedBeforeInDatastore = "SELECT m.sha256_hash, m.size_bytes, m.datastore_id, m.location, m.creation_ts, a.last_access_ts FROM media AS m JOIN last_access AS a ON m.sha256_hash = a.sha256_hash WHERE a.last_access_ts < $1 AND m.datastore_id = $2" -const selectThumbnailsLastAccessedBeforeInDatastore = "SELECT m.sha256_hash, m.size_bytes, m.datastore_id, m.location, m.creation_ts, a.last_access_ts FROM thumbnails AS m JOIN last_access AS a ON m.sha256_hash = a.sha256_hash WHERE a.last_access_ts < $1 AND m.datastore_id = $2" -const changeDatastoreOfMediaHash = "UPDATE media SET datastore_id = $1, location = $2 WHERE sha256_hash = $3" -const changeDatastoreOfThumbnailHash = "UPDATE thumbnails SET datastore_id = $1, location = $2 WHERE sha256_hash = $3" -const selectUploadCountsForServer = "SELECT COALESCE((SELECT COUNT(origin) FROM media WHERE origin = $1), 0) AS media, COALESCE((SELECT COUNT(origin) FROM thumbnails WHERE origin = $1), 0) AS thumbnails" -const selectUploadSizesForServer = "SELECT COALESCE((SELECT SUM(size_bytes) FROM media WHERE origin = $1), 0) AS media, COALESCE((SELECT SUM(size_bytes) FROM thumbnails WHERE origin = $1), 0) AS thumbnails" -const selectUsersForServer = "SELECT DISTINCT user_id FROM media WHERE origin = $1 AND user_id IS NOT NULL AND LENGTH(user_id) > 0" -const insertNewBackgroundTask = "INSERT INTO background_tasks (task, params, start_ts) VALUES ($1, $2, $3) RETURNING id;" -const selectBackgroundTask = "SELECT id, task, params, start_ts, end_ts FROM background_tasks WHERE id = $1" -const updateBackgroundTask = "UPDATE background_tasks SET end_ts = $2 WHERE id = $1" -const selectAllBackgroundTasks = "SELECT id, task, params, start_ts, end_ts FROM background_tasks" -const insertReservation = "INSERT INTO reserved_media (origin, media_id, reason) VALUES ($1, $2, $3);" -const selectReservation = "SELECT origin, media_id, reason FROM reserved_media WHERE origin = $1 AND media_id = $2;" -const selectMediaLastAccessed = "SELECT m.sha256_hash, m.size_bytes, m.datastore_id, m.location, m.creation_ts, a.last_access_ts FROM media AS m JOIN last_access AS a ON m.sha256_hash = a.sha256_hash WHERE a.last_access_ts < $1;" -const insertBlurhash = "INSERT INTO blurhashes (sha256_hash, blurhash) VALUES ($1, $2);" -const selectBlurhash = "SELECT blurhash FROM blurhashes WHERE sha256_hash = $1;" -const selectUserStats = "SELECT user_id, uploaded_bytes FROM user_stats WHERE user_id = $1;" - -type metadataStoreStatements struct { - upsertLastAccessed *sql.Stmt - selectSizeOfDatastore *sql.Stmt - selectMediaLastAccessedBeforeInDatastore *sql.Stmt - selectThumbnailsLastAccessedBeforeInDatastore *sql.Stmt - changeDatastoreOfMediaHash *sql.Stmt - changeDatastoreOfThumbnailHash *sql.Stmt - selectUploadCountsForServer *sql.Stmt - selectUploadSizesForServer *sql.Stmt - selectUsersForServer *sql.Stmt - insertNewBackgroundTask *sql.Stmt - selectBackgroundTask *sql.Stmt - updateBackgroundTask *sql.Stmt - selectAllBackgroundTasks *sql.Stmt - insertReservation *sql.Stmt - selectReservation *sql.Stmt - selectMediaLastAccessed *sql.Stmt - insertBlurhash *sql.Stmt - selectBlurhash *sql.Stmt - selectUserStats *sql.Stmt -} - -type MetadataStoreFactory struct { - sqlDb *sql.DB - stmts *metadataStoreStatements -} - -type MetadataStore struct { - factory *MetadataStoreFactory // just for reference - ctx rcontext.RequestContext - statements *metadataStoreStatements // copied from factory -} - -func InitMetadataStore(sqlDb *sql.DB) (*MetadataStoreFactory, error) { - store := MetadataStoreFactory{stmts: &metadataStoreStatements{}} - var err error - - store.sqlDb = sqlDb - - if store.stmts.upsertLastAccessed, err = store.sqlDb.Prepare(upsertLastAccessed); err != nil { - return nil, err - } - if store.stmts.selectSizeOfDatastore, err = store.sqlDb.Prepare(selectSizeOfDatastore); err != nil { - return nil, err - } - if store.stmts.selectMediaLastAccessedBeforeInDatastore, err = store.sqlDb.Prepare(selectMediaLastAccessedBeforeInDatastore); err != nil { - return nil, err - } - if store.stmts.selectThumbnailsLastAccessedBeforeInDatastore, err = store.sqlDb.Prepare(selectThumbnailsLastAccessedBeforeInDatastore); err != nil { - return nil, err - } - if store.stmts.changeDatastoreOfMediaHash, err = store.sqlDb.Prepare(changeDatastoreOfMediaHash); err != nil { - return nil, err - } - if store.stmts.changeDatastoreOfThumbnailHash, err = store.sqlDb.Prepare(changeDatastoreOfThumbnailHash); err != nil { - return nil, err - } - if store.stmts.selectUsersForServer, err = store.sqlDb.Prepare(selectUsersForServer); err != nil { - return nil, err - } - if store.stmts.selectUploadSizesForServer, err = store.sqlDb.Prepare(selectUploadSizesForServer); err != nil { - return nil, err - } - if store.stmts.selectUploadCountsForServer, err = store.sqlDb.Prepare(selectUploadCountsForServer); err != nil { - return nil, err - } - if store.stmts.insertNewBackgroundTask, err = store.sqlDb.Prepare(insertNewBackgroundTask); err != nil { - return nil, err - } - if store.stmts.selectBackgroundTask, err = store.sqlDb.Prepare(selectBackgroundTask); err != nil { - return nil, err - } - if store.stmts.updateBackgroundTask, err = store.sqlDb.Prepare(updateBackgroundTask); err != nil { - return nil, err - } - if store.stmts.selectAllBackgroundTasks, err = store.sqlDb.Prepare(selectAllBackgroundTasks); err != nil { - return nil, err - } - if store.stmts.insertReservation, err = store.sqlDb.Prepare(insertReservation); err != nil { - return nil, err - } - if store.stmts.selectReservation, err = store.sqlDb.Prepare(selectReservation); err != nil { - return nil, err - } - if store.stmts.selectMediaLastAccessed, err = store.sqlDb.Prepare(selectMediaLastAccessed); err != nil { - return nil, err - } - if store.stmts.insertBlurhash, err = store.sqlDb.Prepare(insertBlurhash); err != nil { - return nil, err - } - if store.stmts.selectBlurhash, err = store.sqlDb.Prepare(selectBlurhash); err != nil { - return nil, err - } - if store.stmts.selectUserStats, err = store.sqlDb.Prepare(selectUserStats); err != nil { - return nil, err - } - - return &store, nil -} - -func (f *MetadataStoreFactory) Create(ctx rcontext.RequestContext) *MetadataStore { - return &MetadataStore{ - factory: f, - ctx: ctx, - statements: f.stmts, // we copy this intentionally - } -} - -func (s *MetadataStore) UpsertLastAccess(sha256Hash string, timestamp int64) error { - _, err := s.statements.upsertLastAccessed.ExecContext(s.ctx, sha256Hash, timestamp) - return err -} - -func (s *MetadataStore) ChangeDatastoreOfHash(datastoreId string, location string, sha256hash string) error { - _, err1 := s.statements.changeDatastoreOfMediaHash.ExecContext(s.ctx, datastoreId, location, sha256hash) - if err1 != nil { - return err1 - } - _, err2 := s.statements.changeDatastoreOfThumbnailHash.ExecContext(s.ctx, datastoreId, location, sha256hash) - if err2 != nil { - return err2 - } - return nil -} - -func (s *MetadataStore) GetEstimatedSizeOfDatastore(datastoreId string) (int64, error) { - r := &folderSize{} - err := s.statements.selectSizeOfDatastore.QueryRowContext(s.ctx, datastoreId).Scan(&r.Size) - return r.Size, err -} - -func (s *MetadataStore) GetOldMedia(beforeTs int64) ([]*types.MinimalMediaMetadata, error) { - rows, err := s.statements.selectMediaLastAccessed.QueryContext(s.ctx, beforeTs) - if err != nil { - return nil, err - } - - var results []*types.MinimalMediaMetadata - for rows.Next() { - obj := &types.MinimalMediaMetadata{} - err = rows.Scan( - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.LastAccessTs, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MetadataStore) GetOldMediaInDatastore(datastoreId string, beforeTs int64) ([]*types.MinimalMediaMetadata, error) { - rows, err := s.statements.selectMediaLastAccessedBeforeInDatastore.QueryContext(s.ctx, beforeTs, datastoreId) - if err != nil { - return nil, err - } - - var results []*types.MinimalMediaMetadata - for rows.Next() { - obj := &types.MinimalMediaMetadata{} - err = rows.Scan( - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.LastAccessTs, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MetadataStore) GetOldThumbnailsInDatastore(datastoreId string, beforeTs int64) ([]*types.MinimalMediaMetadata, error) { - rows, err := s.statements.selectThumbnailsLastAccessedBeforeInDatastore.QueryContext(s.ctx, beforeTs, datastoreId) - if err != nil { - return nil, err - } - - var results []*types.MinimalMediaMetadata - for rows.Next() { - obj := &types.MinimalMediaMetadata{} - err = rows.Scan( - &obj.Sha256Hash, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.LastAccessTs, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *MetadataStore) GetUsersForServer(serverName string) ([]string, error) { - rows, err := s.statements.selectUsersForServer.QueryContext(s.ctx, serverName) - if err != nil { - return nil, err - } - - results := make([]string, 0) - for rows.Next() { - v := "" - err = rows.Scan(&v) - if err != nil { - return nil, err - } - results = append(results, v) - } - - return results, nil -} - -func (s *MetadataStore) GetByteUsageForServer(serverName string) (int64, int64, error) { - row := s.statements.selectUploadSizesForServer.QueryRowContext(s.ctx, serverName) - - media := int64(0) - thumbs := int64(0) - err := row.Scan(&media, &thumbs) - if err != nil { - return 0, 0, err - } - - return media, thumbs, nil -} - -func (s *MetadataStore) GetCountUsageForServer(serverName string) (int64, int64, error) { - row := s.statements.selectUploadCountsForServer.QueryRowContext(s.ctx, serverName) - - media := int64(0) - thumbs := int64(0) - err := row.Scan(&media, &thumbs) - if err != nil { - return 0, 0, err - } - - return media, thumbs, nil -} - -func (s *MetadataStore) CreateBackgroundTask(name string, params map[string]interface{}) (*types.BackgroundTask, error) { - now := util.NowMillis() - b, err := json.Marshal(params) - if err != nil { - return nil, err - } - r := s.statements.insertNewBackgroundTask.QueryRowContext(s.ctx, name, string(b), now) - var id int - err = r.Scan(&id) - if err != nil { - return nil, err - } - return &types.BackgroundTask{ - ID: id, - Name: name, - StartTs: now, - EndTs: 0, - }, nil -} - -func (s *MetadataStore) FinishedBackgroundTask(id int) error { - now := util.NowMillis() - _, err := s.statements.updateBackgroundTask.ExecContext(s.ctx, id, now) - return err -} - -func (s *MetadataStore) GetBackgroundTask(id int) (*types.BackgroundTask, error) { - r := s.statements.selectBackgroundTask.QueryRowContext(s.ctx, id) - task := &types.BackgroundTask{} - var paramsStr string - var endTs sql.NullInt64 - - err := r.Scan(&task.ID, &task.Name, ¶msStr, &task.StartTs, &endTs) - if err != nil { - return nil, err - } - - err = json.Unmarshal([]byte(paramsStr), &task.Params) - if err != nil { - return nil, err - } - - if endTs.Valid { - task.EndTs = endTs.Int64 - } - - return task, nil -} - -func (s *MetadataStore) GetAllBackgroundTasks() ([]*types.BackgroundTask, error) { - rows, err := s.statements.selectAllBackgroundTasks.QueryContext(s.ctx) - if err != nil { - return nil, err - } - - results := make([]*types.BackgroundTask, 0) - for rows.Next() { - task := &types.BackgroundTask{} - var paramsStr string - var endTs sql.NullInt64 - - err := rows.Scan(&task.ID, &task.Name, ¶msStr, &task.StartTs, &endTs) - if err != nil { - return nil, err - } - - err = json.Unmarshal([]byte(paramsStr), &task.Params) - if err != nil { - return nil, err - } - - if endTs.Valid { - task.EndTs = endTs.Int64 - } - - results = append(results, task) - } - - return results, nil -} - -func (s *MetadataStore) ReserveMediaId(origin string, mediaId string, reason string) error { - _, err := s.statements.insertReservation.ExecContext(s.ctx, origin, mediaId, reason) - if err != nil { - return err - } - return nil -} - -func (s *MetadataStore) IsReserved(origin string, mediaId string) (bool, error) { - r := s.statements.selectReservation.QueryRowContext(s.ctx, origin, mediaId) - var dbOrigin string - var dbMediaId string - var dbReason string - - err := r.Scan(&dbOrigin, &dbMediaId, &dbReason) - if err == sql.ErrNoRows { - return false, nil - } - if err != nil { - return true, err - } - return true, nil -} - -func (s *MetadataStore) InsertBlurhash(sha256Hash string, blurhash string) error { - _, err := s.statements.insertBlurhash.ExecContext(s.ctx, sha256Hash, blurhash) - if err != nil { - return err - } - return nil -} - -func (s *MetadataStore) GetBlurhash(sha256Hash string) (string, error) { - r := s.statements.selectBlurhash.QueryRowContext(s.ctx, sha256Hash) - var blurhash string - - err := r.Scan(&blurhash) - if err == sql.ErrNoRows { - return "", nil - } - if err != nil { - return "", err - } - return blurhash, nil -} - -func (s *MetadataStore) GetUserStats(userId string) (*types.UserStats, error) { - r := s.statements.selectUserStats.QueryRowContext(s.ctx, userId) - - stat := &types.UserStats{} - err := r.Scan( - &stat.UserId, - &stat.UploadedBytes, - ) - if err != nil { - return nil, err - } - return stat, nil -} diff --git a/storage/stores/thumbnail_store.go b/storage/stores/thumbnail_store.go deleted file mode 100644 index 65f489b15b2f31da51f1b8a1bd59449bebc525c4..0000000000000000000000000000000000000000 --- a/storage/stores/thumbnail_store.go +++ /dev/null @@ -1,329 +0,0 @@ -package stores - -import ( - "database/sql" - - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/types" -) - -const selectThumbnail = "SELECT origin, media_id, width, height, method, animated, content_type, size_bytes, datastore_id, location, creation_ts, sha256_hash FROM thumbnails WHERE origin = $1 and media_id = $2 and width = $3 and height = $4 and method = $5 and animated = $6;" -const insertThumbnail = "INSERT INTO thumbnails (origin, media_id, width, height, method, animated, content_type, size_bytes, datastore_id, location, creation_ts, sha256_hash) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);" -const updateThumbnailHash = "UPDATE thumbnails SET sha256_hash = $7 WHERE origin = $1 and media_id = $2 and width = $3 and height = $4 and method = $5 and animated = $6;" -const selectThumbnailsWithoutHash = "SELECT origin, media_id, width, height, method, animated, content_type, size_bytes, datastore_id, location, creation_ts, sha256_hash FROM thumbnails WHERE sha256_hash IS NULL OR sha256_hash = '';" -const selectThumbnailsWithoutDatastore = "SELECT origin, media_id, width, height, method, animated, content_type, size_bytes, datastore_id, location, creation_ts, sha256_hash FROM thumbnails WHERE datastore_id IS NULL OR datastore_id = '';" -const updateThumbnailDatastoreAndLocation = "UPDATE thumbnails SET location = $8, datastore_id = $7 WHERE origin = $1 and media_id = $2 and width = $3 and height = $4 and method = $5 and animated = $6;" -const selectThumbnailsForMedia = "SELECT origin, media_id, width, height, method, animated, content_type, size_bytes, datastore_id, location, creation_ts, sha256_hash FROM thumbnails WHERE origin = $1 AND media_id = $2;" -const deleteThumbnailsForMedia = "DELETE FROM thumbnails WHERE origin = $1 AND media_id = $2;" -const selectThumbnailsCreatedBefore = "SELECT origin, media_id, width, height, method, animated, content_type, size_bytes, datastore_id, location, creation_ts, sha256_hash FROM thumbnails WHERE creation_ts < $1;" -const deleteThumbnailsWithHash = "DELETE FROM thumbnails WHERE sha256_hash = $1;" -const selectThumbnailLocationsForDatastore = "SELECT distinct location FROM thumbnails WHERE datastore_id = $1;" - -type thumbnailStatements struct { - selectThumbnail *sql.Stmt - insertThumbnail *sql.Stmt - updateThumbnailHash *sql.Stmt - selectThumbnailsWithoutHash *sql.Stmt - selectThumbnailsWithoutDatastore *sql.Stmt - updateThumbnailDatastoreAndLocation *sql.Stmt - selectThumbnailsForMedia *sql.Stmt - deleteThumbnailsForMedia *sql.Stmt - selectThumbnailsCreatedBefore *sql.Stmt - deleteThumbnailsWithHash *sql.Stmt - selectThumbnailLocationsForDatastore *sql.Stmt -} - -type ThumbnailStoreFactory struct { - sqlDb *sql.DB - stmts *thumbnailStatements -} - -type ThumbnailStore struct { - factory *ThumbnailStoreFactory // just for reference - ctx rcontext.RequestContext - statements *thumbnailStatements // copied from factory -} - -func InitThumbnailStore(sqlDb *sql.DB) (*ThumbnailStoreFactory, error) { - store := ThumbnailStoreFactory{stmts: &thumbnailStatements{}} - var err error - - store.sqlDb = sqlDb - - if store.stmts.selectThumbnail, err = store.sqlDb.Prepare(selectThumbnail); err != nil { - return nil, err - } - if store.stmts.insertThumbnail, err = store.sqlDb.Prepare(insertThumbnail); err != nil { - return nil, err - } - if store.stmts.updateThumbnailHash, err = store.sqlDb.Prepare(updateThumbnailHash); err != nil { - return nil, err - } - if store.stmts.selectThumbnailsWithoutHash, err = store.sqlDb.Prepare(selectThumbnailsWithoutHash); err != nil { - return nil, err - } - if store.stmts.selectThumbnailsWithoutDatastore, err = store.sqlDb.Prepare(selectThumbnailsWithoutDatastore); err != nil { - return nil, err - } - if store.stmts.updateThumbnailDatastoreAndLocation, err = store.sqlDb.Prepare(updateThumbnailDatastoreAndLocation); err != nil { - return nil, err - } - if store.stmts.selectThumbnailsForMedia, err = store.sqlDb.Prepare(selectThumbnailsForMedia); err != nil { - return nil, err - } - if store.stmts.deleteThumbnailsForMedia, err = store.sqlDb.Prepare(deleteThumbnailsForMedia); err != nil { - return nil, err - } - if store.stmts.selectThumbnailsCreatedBefore, err = store.sqlDb.Prepare(selectThumbnailsCreatedBefore); err != nil { - return nil, err - } - if store.stmts.deleteThumbnailsWithHash, err = store.sqlDb.Prepare(deleteThumbnailsWithHash); err != nil { - return nil, err - } - if store.stmts.selectThumbnailLocationsForDatastore, err = store.sqlDb.Prepare(selectThumbnailLocationsForDatastore); err != nil { - return nil, err - } - - return &store, nil -} - -func (f *ThumbnailStoreFactory) New(ctx rcontext.RequestContext) *ThumbnailStore { - return &ThumbnailStore{ - factory: f, - ctx: ctx, - statements: f.stmts, // we copy this intentionally - } -} - -func (s *ThumbnailStore) Insert(thumbnail *types.Thumbnail) error { - _, err := s.statements.insertThumbnail.ExecContext( - s.ctx, - thumbnail.Origin, - thumbnail.MediaId, - thumbnail.Width, - thumbnail.Height, - thumbnail.Method, - thumbnail.Animated, - thumbnail.ContentType, - thumbnail.SizeBytes, - thumbnail.DatastoreId, - thumbnail.Location, - thumbnail.CreationTs, - thumbnail.Sha256Hash, - ) - - return err -} - -func (s *ThumbnailStore) Get(origin string, mediaId string, width int, height int, method string, animated bool) (*types.Thumbnail, error) { - t := &types.Thumbnail{} - err := s.statements.selectThumbnail.QueryRowContext(s.ctx, origin, mediaId, width, height, method, animated).Scan( - &t.Origin, - &t.MediaId, - &t.Width, - &t.Height, - &t.Method, - &t.Animated, - &t.ContentType, - &t.SizeBytes, - &t.DatastoreId, - &t.Location, - &t.CreationTs, - &t.Sha256Hash, - ) - return t, err -} - -func (s *ThumbnailStore) UpdateHash(thumbnail *types.Thumbnail) error { - _, err := s.statements.updateThumbnailHash.ExecContext( - s.ctx, - thumbnail.Origin, - thumbnail.MediaId, - thumbnail.Width, - thumbnail.Height, - thumbnail.Method, - thumbnail.Animated, - thumbnail.Sha256Hash, - ) - - return err -} - -func (s *ThumbnailStore) UpdateDatastoreAndLocation(thumbnail *types.Thumbnail) error { - _, err := s.statements.updateThumbnailDatastoreAndLocation.ExecContext( - s.ctx, - thumbnail.Origin, - thumbnail.MediaId, - thumbnail.Width, - thumbnail.Height, - thumbnail.Method, - thumbnail.Animated, - thumbnail.DatastoreId, - thumbnail.Location, - ) - - return err -} - -func (s *ThumbnailStore) GetAllWithoutHash() ([]*types.Thumbnail, error) { - rows, err := s.statements.selectThumbnailsWithoutHash.QueryContext(s.ctx) - if err != nil { - return nil, err - } - - var results []*types.Thumbnail - for rows.Next() { - obj := &types.Thumbnail{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.Width, - &obj.Height, - &obj.Method, - &obj.Animated, - &obj.ContentType, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Sha256Hash, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *ThumbnailStore) GetAllWithoutDatastore() ([]*types.Thumbnail, error) { - rows, err := s.statements.selectThumbnailsWithoutDatastore.QueryContext(s.ctx) - if err != nil { - return nil, err - } - - var results []*types.Thumbnail - for rows.Next() { - obj := &types.Thumbnail{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.Width, - &obj.Height, - &obj.Method, - &obj.Animated, - &obj.ContentType, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Sha256Hash, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *ThumbnailStore) GetAllForMedia(origin string, mediaId string) ([]*types.Thumbnail, error) { - rows, err := s.statements.selectThumbnailsForMedia.QueryContext(s.ctx, origin, mediaId) - if err != nil { - return nil, err - } - - var results []*types.Thumbnail - for rows.Next() { - obj := &types.Thumbnail{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.Width, - &obj.Height, - &obj.Method, - &obj.Animated, - &obj.ContentType, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Sha256Hash, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *ThumbnailStore) DeleteAllForMedia(origin string, mediaId string) error { - _, err := s.statements.deleteThumbnailsForMedia.ExecContext(s.ctx, origin, mediaId) - if err != nil { - return err - } - return nil -} - -func (s *ThumbnailStore) GetOldThumbnails(beforeTs int64) ([]*types.Thumbnail, error) { - rows, err := s.statements.selectThumbnailsCreatedBefore.QueryContext(s.ctx, beforeTs) - if err != nil { - return nil, err - } - - var results []*types.Thumbnail - for rows.Next() { - obj := &types.Thumbnail{} - err = rows.Scan( - &obj.Origin, - &obj.MediaId, - &obj.Width, - &obj.Height, - &obj.Method, - &obj.Animated, - &obj.ContentType, - &obj.SizeBytes, - &obj.DatastoreId, - &obj.Location, - &obj.CreationTs, - &obj.Sha256Hash, - ) - if err != nil { - return nil, err - } - results = append(results, obj) - } - - return results, nil -} - -func (s *ThumbnailStore) DeleteWithHash(sha256hash string) error { - _, err := s.statements.deleteThumbnailsWithHash.ExecContext(s.ctx, sha256hash) - if err != nil { - return err - } - return nil -} - -func (s *ThumbnailStore) GetDistinctLocationsForDatastore(datastoreId string) ([]string, error) { - rows, err := s.statements.selectThumbnailLocationsForDatastore.QueryContext(s.ctx, datastoreId) - if err != nil { - return nil, err - } - - locations := make([]string, 0) - for rows.Next() { - s := "" - err = rows.Scan(&s) - if err != nil { - return nil, err - } - locations = append(locations, s) - } - - return locations, nil -} diff --git a/storage/stores/url_store.go b/storage/stores/url_store.go deleted file mode 100644 index b97cceb6d62d9cd92b84ee42d1e499dcab41f3fc..0000000000000000000000000000000000000000 --- a/storage/stores/url_store.go +++ /dev/null @@ -1,122 +0,0 @@ -package stores - -import ( - "database/sql" - - "github.com/turt2live/matrix-media-repo/common/rcontext" - "github.com/turt2live/matrix-media-repo/types" - "github.com/turt2live/matrix-media-repo/util" -) - -const selectUrlPreview = "SELECT url, error_code, bucket_ts, site_url, site_name, resource_type, description, title, image_mxc, image_type, image_size, image_width, image_height, language_header FROM url_previews WHERE url = $1 AND bucket_ts = $2 AND language_header = $3;" -const insertUrlPreview = "INSERT INTO url_previews (url, error_code, bucket_ts, site_url, site_name, resource_type, description, title, image_mxc, image_type, image_size, image_width, image_height, language_header) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);" -const deletePreviewsOlderThan = "DELETE FROM url_previews WHERE bucket_ts <= $1;" - -type urlStatements struct { - selectUrlPreview *sql.Stmt - insertUrlPreview *sql.Stmt - deletePreviewsOlderThan *sql.Stmt -} - -type UrlStoreFactory struct { - sqlDb *sql.DB - stmts *urlStatements -} - -type UrlStore struct { - factory *UrlStoreFactory // just for reference - ctx rcontext.RequestContext - statements *urlStatements // copied from factory -} - -func InitUrlStore(sqlDb *sql.DB) (*UrlStoreFactory, error) { - store := UrlStoreFactory{stmts: &urlStatements{}} - var err error - - store.sqlDb = sqlDb - - if store.stmts.selectUrlPreview, err = store.sqlDb.Prepare(selectUrlPreview); err != nil { - return nil, err - } - if store.stmts.insertUrlPreview, err = store.sqlDb.Prepare(insertUrlPreview); err != nil { - return nil, err - } - if store.stmts.deletePreviewsOlderThan, err = store.sqlDb.Prepare(deletePreviewsOlderThan); err != nil { - return nil, err - } - - return &store, nil -} - -func (f *UrlStoreFactory) Create(ctx rcontext.RequestContext) *UrlStore { - return &UrlStore{ - factory: f, - ctx: ctx, - statements: f.stmts, // we copy this intentionally - } -} - -func (s *UrlStore) GetPreview(url string, ts int64, languageHeader string) (*types.CachedUrlPreview, error) { - r := &types.CachedUrlPreview{ - Preview: &types.UrlPreview{}, - } - err := s.statements.selectUrlPreview.QueryRowContext(s.ctx, url, GetBucketTs(ts), languageHeader).Scan( - &r.SearchUrl, - &r.ErrorCode, - &r.FetchedTs, - &r.Preview.Url, - &r.Preview.SiteName, - &r.Preview.Type, - &r.Preview.Description, - &r.Preview.Title, - &r.Preview.ImageMxc, - &r.Preview.ImageType, - &r.Preview.ImageSize, - &r.Preview.ImageWidth, - &r.Preview.ImageHeight, - &r.Preview.LanguageHeader, - ) - - return r, err -} - -func (s *UrlStore) InsertPreview(record *types.CachedUrlPreview) error { - _, err := s.statements.insertUrlPreview.ExecContext( - s.ctx, - record.SearchUrl, - record.ErrorCode, - GetBucketTs(record.FetchedTs), - record.Preview.Url, - record.Preview.SiteName, - record.Preview.Type, - record.Preview.Description, - record.Preview.Title, - record.Preview.ImageMxc, - record.Preview.ImageType, - record.Preview.ImageSize, - record.Preview.ImageWidth, - record.Preview.ImageHeight, - record.Preview.LanguageHeader, - ) - - return err -} - -func (s *UrlStore) InsertPreviewError(url string, errorCode string) error { - return s.InsertPreview(&types.CachedUrlPreview{ - Preview: &types.UrlPreview{}, - SearchUrl: url, - ErrorCode: errorCode, - FetchedTs: util.NowMillis(), - }) -} - -func (s *UrlStore) DeleteOlderThan(beforeTs int64) error { - _, err := s.statements.deletePreviewsOlderThan.ExecContext(s.ctx, beforeTs) - return err -} - -func GetBucketTs(ts int64) int64 { - // 1 hour buckets - return (ts / 3600000) * 3600000 -} diff --git a/tasks/task_runner/import_data.go b/tasks/task_runner/import_data.go index 26a9092ec4650f24059e84b0bd31456701e02a2d..236fa38aff7de90b9b3120c50573de29ac66725e 100644 --- a/tasks/task_runner/import_data.go +++ b/tasks/task_runner/import_data.go @@ -173,7 +173,7 @@ func AppendImportFile(ctx rcontext.RequestContext, importId string, data io.Read // caller *just* started the import job and is already trying to append. for i := 0; i < 5; i++ { err := try() - if err == common.ErrMediaNotFound { + if errors.Is(err, common.ErrMediaNotFound) { time.Sleep(100 * time.Millisecond) } else { return err diff --git a/templates/export_index.html b/templates/export_index.html index bf110445a12129af2915c814ab716cc62c0cd766..5be76eb4f08954a7b57d4c22db80184175a259a8 100644 --- a/templates/export_index.html +++ b/templates/export_index.html @@ -2,7 +2,7 @@ <html lang="en"> <head> <title>{{.Entity}} media export</title> - <style type="text/css"> + <style> body, html { margin: 0; padding: 0; @@ -63,6 +63,7 @@ {{range .Media}} <tr> <td> + <!--suppress HtmlUnknownTarget --> <a href="{{.ArchivedName}}"><code>{{if eq .FileName ""}}<no name>{{else}}{{.FileName}}{{end}}</code></a> <span class="muted"><code>mxc://{{.Origin}}/{{.MediaID}}</code></span> </td> diff --git a/templates/view_export.html b/templates/view_export.html index 380ddb1b3e880b5e5ac9ac0f055234569d3ed7fa..b2b6cff023bf34fb44f7349e4e87743405496aa2 100644 --- a/templates/view_export.html +++ b/templates/view_export.html @@ -2,7 +2,7 @@ <html lang="en"> <head> <title>{{.Entity}} media export</title> - <style type="text/css"> + <style> body, html { margin: 0; padding: 0; @@ -24,7 +24,7 @@ } </style> <noscript> - <style type="text/css"> + <style> #delete-option { display: none; } @@ -33,12 +33,12 @@ <script type="text/javascript"> <!-- function deleteExport() { - var url = "/_matrix/media/unstable/admin/export/{{.ExportID}}/delete"; - var xhr = new XMLHttpRequest(); + const url = "/_matrix/media/unstable/admin/export/{{.ExportID}}/delete"; + const xhr = new XMLHttpRequest(); xhr.open("DELETE", url, true); xhr.onreadystatechange = function() { if (this.readyState !== 4) return; - var element = document.getElementById("main"); + const element = document.getElementById("main"); if (!element) return; if (this.status === 200) { @@ -62,7 +62,7 @@ </p> <ul> {{range .ExportParts}} - <li><a href="/_matrix/media/unstable/admin/export/{{.ExportID}}/part/{{.Index}}" download>{{.FileName}}</a> ({{.SizeBytesHuman}})</li> + <li><!--suppress HtmlUnknownTarget --><a href="/_matrix/media/unstable/admin/export/{{.ExportID}}/part/{{.Index}}" download>{{.FileName}}</a> ({{.SizeBytesHuman}})</li> {{end}} </ul> <p id="delete-option">Downloaded all your data? <a href="javascript:deleteExport()">Delete your export</a></p> diff --git a/thumbnailing/i/01-factories.go b/thumbnailing/i/01-factories.go index 2779175c83a0dc01e7522c8e3e8fd6281792c537..e8410865c51bc9b1e0fa54cb6a262d8abd88a2b1 100644 --- a/thumbnailing/i/01-factories.go +++ b/thumbnailing/i/01-factories.go @@ -49,16 +49,3 @@ func GetSupportedContentTypes() []string { } return a } - -func GetSupportedAnimationTypes() []string { - a := make([]string, 0) - for _, d := range generators { - if !d.supportsAnimation() { - continue - } - for _, c := range d.supportedContentTypes() { - a = append(a, c) - } - } - return a -} diff --git a/types/MAYBE_LEGACY.txt b/types/MAYBE_LEGACY.txt deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/types/background_task.go b/types/background_task.go deleted file mode 100644 index 9f7111dd92cfdd125830fff1a57ec1bf5e88b7f1..0000000000000000000000000000000000000000 --- a/types/background_task.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -type BackgroundTask struct { - ID int - Name string - Params map[string]interface{} - StartTs int64 - EndTs int64 -} diff --git a/types/datastore.go b/types/datastore.go deleted file mode 100644 index 6cc13167fa163607711c451a47dc70c501b64fb4..0000000000000000000000000000000000000000 --- a/types/datastore.go +++ /dev/null @@ -1,18 +0,0 @@ -package types - -type Datastore struct { - DatastoreId string - Type string - Uri string -} - -type DatastoreMigrationEstimate struct { - ThumbnailsAffected int64 `json:"thumbnails_affected"` - ThumbnailHashesAffected int64 `json:"thumbnail_hashes_affected"` - ThumbnailBytes int64 `json:"thumbnail_bytes"` - MediaAffected int64 `json:"media_affected"` - MediaHashesAffected int64 `json:"media_hashes_affected"` - MediaBytes int64 `json:"media_bytes"` - TotalHashesAffected int64 `json:"total_hashes_affected"` - TotalBytes int64 `json:"total_bytes"` -} diff --git a/types/exports.go b/types/exports.go deleted file mode 100644 index 17006fb79b59de5dd7f6fcd64f79b2709b3296d4..0000000000000000000000000000000000000000 --- a/types/exports.go +++ /dev/null @@ -1,15 +0,0 @@ -package types - -type ExportMetadata struct { - ExportID string - Entity string -} - -type ExportPart struct { - ExportID string - Index int - FileName string - SizeBytes int64 - DatastoreID string - Location string -} diff --git a/types/media.go b/types/media.go deleted file mode 100644 index 873da958825193c03c6152b283ff46bdcadf234d..0000000000000000000000000000000000000000 --- a/types/media.go +++ /dev/null @@ -1,46 +0,0 @@ -package types - -import "io" - -type Media struct { - Origin string - MediaId string - UploadName string - ContentType string - UserId string - Sha256Hash string - SizeBytes int64 - DatastoreId string - Location string - CreationTs int64 - Quarantined bool -} - -type MinimalMedia struct { - Origin string - MediaId string - Stream io.ReadCloser - UploadName string - ContentType string - SizeBytes int64 - KnownMedia *Media -} - -type MinimalMediaMetadata struct { - SizeBytes int64 - Sha256Hash string - Location string - CreationTs int64 - LastAccessTs int64 - DatastoreId string -} - -type UserUsageStats struct { - MediaCount int64 - MediaLength int64 - UserId string -} - -func (m *Media) MxcUri() string { - return "mxc://" + m.Origin + "/" + m.MediaId -} diff --git a/types/media_attributes.go b/types/media_attributes.go deleted file mode 100644 index c938e0e95d7b64f4d5c08a1c2cc5611ee260cde2..0000000000000000000000000000000000000000 --- a/types/media_attributes.go +++ /dev/null @@ -1,12 +0,0 @@ -package types - -type MediaAttributes struct { - Origin string - MediaId string - Purpose string -} - -const PurposeNone = "none" -const PurposePinned = "pinned" - -var AllPurposes = []string{PurposeNone, PurposePinned} diff --git a/types/object_info.go b/types/object_info.go deleted file mode 100644 index 11fe6a40bc6c29edfc21ad8dee539c6658583b7d..0000000000000000000000000000000000000000 --- a/types/object_info.go +++ /dev/null @@ -1,7 +0,0 @@ -package types - -type ObjectInfo struct { - Location string - Sha256Hash string - SizeBytes int64 -} diff --git a/types/stats.go b/types/stats.go deleted file mode 100644 index 01188c9de64fa1e8ef0a4c28d1e3fc9060cb05f9..0000000000000000000000000000000000000000 --- a/types/stats.go +++ /dev/null @@ -1,6 +0,0 @@ -package types - -type UserStats struct { - UserId string - UploadedBytes int64 -} diff --git a/types/thumbnail.go b/types/thumbnail.go deleted file mode 100644 index de71be3445d6be63af0bc25d4db791307cff8031..0000000000000000000000000000000000000000 --- a/types/thumbnail.go +++ /dev/null @@ -1,25 +0,0 @@ -package types - -import ( - "io" -) - -type Thumbnail struct { - Origin string - MediaId string - Width int - Height int - Method string // "crop" or "scale" - Animated bool - ContentType string - SizeBytes int64 - DatastoreId string - Location string - CreationTs int64 - Sha256Hash string -} - -type StreamedThumbnail struct { - Thumbnail *Thumbnail - Stream io.ReadCloser -} diff --git a/types/url_preview.go b/types/url_preview.go deleted file mode 100644 index 6dd1a8dde12e2d68eaa1226e14f1ebffdf7fc5d9..0000000000000000000000000000000000000000 --- a/types/url_preview.go +++ /dev/null @@ -1,22 +0,0 @@ -package types - -type UrlPreview struct { - Url string - SiteName string - Type string - Description string - Title string - ImageMxc string - ImageType string - ImageSize int64 - ImageWidth int - ImageHeight int - LanguageHeader string -} - -type CachedUrlPreview struct { - Preview *UrlPreview - SearchUrl string - ErrorCode string - FetchedTs int64 -} diff --git a/url_previewing/p/calculated.go b/url_previewing/p/calculated.go index ac2b41845607bfd970dffac6b279087efff72726..60421d1672cdca7f6ab1eea4a2cb7411edd701cc 100644 --- a/url_previewing/p/calculated.go +++ b/url_previewing/p/calculated.go @@ -1,6 +1,8 @@ package p import ( + "errors" + "github.com/prometheus/client_golang/prometheus" "github.com/turt2live/matrix-media-repo/common" "github.com/turt2live/matrix-media-repo/common/rcontext" @@ -16,7 +18,7 @@ func GenerateCalculatedPreview(urlPayload *m.UrlPayload, languageHeader string, ctx.Log.Warn("Error downloading content: ", err) // Make sure the unsupported error gets passed through - if err == m.ErrPreviewUnsupported { + if errors.Is(err, m.ErrPreviewUnsupported) { return m.PreviewResult{}, m.ErrPreviewUnsupported } diff --git a/url_previewing/p/opengraph.go b/url_previewing/p/opengraph.go index cb510a766270aad81331733c03f56fcc28e2f633..55a7bd78b8ca6957acab838eeb232c6906ee45bc 100644 --- a/url_previewing/p/opengraph.go +++ b/url_previewing/p/opengraph.go @@ -1,6 +1,7 @@ package p import ( + "errors" "net/url" "strconv" "strings" @@ -26,7 +27,7 @@ func GenerateOpenGraphPreview(urlPayload *m.UrlPayload, languageHeader string, c ctx.Log.Error("Error downloading content: ", err) // Make sure the unsupported error gets passed through - if err == m.ErrPreviewUnsupported { + if errors.Is(err, m.ErrPreviewUnsupported) { return m.PreviewResult{}, m.ErrPreviewUnsupported } diff --git a/util/LEGACY_files.go b/util/LEGACY_files.go deleted file mode 100644 index 178ae1d8815cd4be638504bc2640705bf043c360..0000000000000000000000000000000000000000 --- a/util/LEGACY_files.go +++ /dev/null @@ -1,39 +0,0 @@ -package util - -import ( - "os" - "path" - - "github.com/turt2live/matrix-media-repo/util/stream_util" -) - -func FileExists(path string) (bool, error) { - _, err := os.Stat(path) - if err == nil { - return true, nil - } - if os.IsNotExist(err) { - return false, nil - } - return true, err -} - -func GetLastSegmentsOfPath(strPath string, segments int) string { - combined := "" - for i := 1; i <= segments; i++ { - d, p := path.Split(strPath) - strPath = path.Clean(d) - combined = path.Join(p, combined) - } - return combined -} - -func GetFileHash(filePath string) (string, error) { - f, err := os.Open(filePath) - if err != nil { - return "", err - } - defer stream_util.DumpAndCloseStream(f) - - return stream_util.GetSha256HashOfStream(f) -} diff --git a/util/resource_handler/LEGACY_handler.go b/util/resource_handler/LEGACY_handler.go deleted file mode 100644 index 66c8d05d286619eb5c74415ec42dcac5412813d4..0000000000000000000000000000000000000000 --- a/util/resource_handler/LEGACY_handler.go +++ /dev/null @@ -1,92 +0,0 @@ -package resource_handler - -import ( - "reflect" - "time" - - "github.com/Jeffail/tunny" - "github.com/olebedev/emitter" - "github.com/patrickmn/go-cache" - "github.com/sirupsen/logrus" -) - -type ResourceHandler struct { - pool *tunny.Pool - eventBus *emitter.Emitter - itemCache *cache.Cache -} - -type resource struct { - isComplete bool - result interface{} -} - -type WorkRequest struct { - Id string - Metadata interface{} -} - -func New(workers int, fetchFn func(object *WorkRequest) interface{}) (*ResourceHandler, error) { - workFn := func(i interface{}) interface{} { return fetchFn(i.(*WorkRequest)) } - pool := tunny.NewFunc(workers, workFn) - - bus := &emitter.Emitter{} - itemCache := cache.New(30*time.Second, 1*time.Minute) // cache work for 30ish seconds - - handler := &ResourceHandler{pool, bus, itemCache} - return handler, nil -} - -func (h *ResourceHandler) Close() { - logrus.Warn("Closing resource handler: " + reflect.TypeOf(h).Name()) - h.pool.Close() -} - -func (h *ResourceHandler) GetResource(id string, metadata interface{}) chan interface{} { - resultChan := make(chan interface{}) - - // First see if we have already cached this request - cachedResource, found := h.itemCache.Get(id) - if found { - res := cachedResource.(*resource) - - // If the request has already been completed, return that result - if res.isComplete { - // This is a goroutine to avoid a problem where the sending and return can race - go func() { - logrus.Warn("Returning cached reply from resource handler for resource ID " + id) - resultChan <- res.result - }() - return resultChan - } - - // Otherwise queue a wait function to handle the resource when it is complete - go func() { - result := <-h.eventBus.Once("complete_" + id) - resultChan <- result.Args[0] - }() - - return resultChan - } - - // Cache that we're starting the request (never expire) - h.itemCache.Set(id, &resource{false, nil}, cache.NoExpiration) - - go func() { - // Queue the work (ignore errors) - result := h.pool.Process(&WorkRequest{id, metadata}) - h.eventBus.Emit("complete_"+id, result) - - // Cache the result for future callers - newResource := &resource{ - isComplete: true, - result: result, - } - h.itemCache.Set(id, newResource, cache.DefaultExpiration) - - // and finally feed it back to the caller - resultChan <- result - }() - - return resultChan -} diff --git a/util/singleflight-counter/LEGACY_singleflight.go b/util/singleflight-counter/LEGACY_singleflight.go deleted file mode 100644 index b32095c95cd40ff1e12b6fdb68c1fdb98e128a90..0000000000000000000000000000000000000000 --- a/util/singleflight-counter/LEGACY_singleflight.go +++ /dev/null @@ -1,78 +0,0 @@ -package singleflight_counter - -import ( - "sync" -) - -// Largely inspired by Go's singleflight package. -// https://github.com/golang/sync/blob/112230192c580c3556b8cee6403af37a4fc5f28c/singleflight/singleflight.go - -type call struct { - wg sync.WaitGroup - - valsMu sync.Mutex - nextIndex int - vals []interface{} - - val interface{} - err error - count int -} - -type Group struct { - mu sync.Mutex - m map[string]*call -} - -func (c *call) NextVal() interface{} { - c.valsMu.Lock() - val := c.val - if c.vals != nil && len(c.vals) >= c.count { - val = c.vals[c.nextIndex] - c.nextIndex++ - } - c.valsMu.Unlock() - return val -} - -func (g *Group) DoWithoutPost(key string, fn func() (interface{}, error)) (interface{}, int, error) { - return g.Do(key, fn, func(v interface{}, total int, e error) []interface{} { - return nil - }) -} - -func (g *Group) Do(key string, fn func() (interface{}, error), postprocess func(v interface{}, total int, e error) []interface{}) (interface{}, int, error) { - g.mu.Lock() - if g.m == nil { - g.m = make(map[string]*call) - } - if c, ok := g.m[key]; ok { - c.count++ - g.mu.Unlock() - c.wg.Wait() - - return c.NextVal(), c.count, c.err - } - - c := new(call) - c.count = 1 // Always start at 1 (for us) - c.nextIndex = 0 - c.wg.Add(1) - g.m[key] = c - g.mu.Unlock() - - c.val, c.err = fn() - - g.mu.Lock() - delete(g.m, key) - g.mu.Unlock() - - c.vals = nil - if postprocess != nil { - c.vals = postprocess(c.val, c.count, c.err) - } - - c.wg.Done() - - return c.NextVal(), c.count, c.err -} diff --git a/util/stream_util/LEGACY.txt b/util/stream_util/LEGACY.txt deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/util/stream_util/streams.go b/util/stream_util/streams.go deleted file mode 100644 index 3e921e5d218a5323198eb2ae6f816195e93af648..0000000000000000000000000000000000000000 --- a/util/stream_util/streams.go +++ /dev/null @@ -1,78 +0,0 @@ -package stream_util - -import ( - "bytes" - "crypto/sha256" - "encoding/hex" - "errors" - "fmt" - "io" - "math" -) - -func BufferToStream(buf *bytes.Buffer) io.ReadCloser { - newBuf := bytes.NewReader(buf.Bytes()) - return io.NopCloser(newBuf) -} - -func BytesToStream(b []byte) io.ReadCloser { - return io.NopCloser(bytes.NewBuffer(b)) -} - -func GetSha256HashOfStream(r io.ReadCloser) (string, error) { - defer DumpAndCloseStream(r) - - hasher := sha256.New() - - if _, err := io.Copy(hasher, r); err != nil { - return "", err - } - - return hex.EncodeToString(hasher.Sum(nil)), nil -} - -func ForceDiscard(r io.Reader, nBytes int64) error { - if nBytes == 0 { - return nil // weird call, but ok - } - - buf := make([]byte, 128) - - if nBytes < 0 { - for true { - _, err := r.Read(buf) - if err == io.EOF { - break - } else if err != nil { - return err - } - } - return nil - } - - read := int64(0) - for (nBytes - read) > 0 { - toRead := int(math.Min(float64(len(buf)), float64(nBytes-read))) - if toRead != len(buf) { - buf = make([]byte, toRead) - } - actuallyRead, err := r.Read(buf) - if err != nil { - return err - } - read += int64(actuallyRead) - if (nBytes - read) < 0 { - return errors.New(fmt.Sprintf("over-discarded from stream by %d bytes", nBytes-read)) - } - } - - return nil -} - -func DumpAndCloseStream(r io.ReadCloser) { - if r == nil { - return // nothing to dump or close - } - _ = ForceDiscard(r, -1) - _ = r.Close() -} diff --git a/util/urls.go b/util/urls.go index 7b02ede39041d4ecb7e30f20501de08f65b4de57..59f93bd846fb64097154a416de5e06a2fcb44f47 100644 --- a/util/urls.go +++ b/util/urls.go @@ -12,4 +12,4 @@ func MakeUrl(parts ...string) string { } } return res -} \ No newline at end of file +} diff --git a/util/util_byte_seeker/LEGACY.txt b/util/util_byte_seeker/LEGACY.txt deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/util/util_byte_seeker/seekable.go b/util/util_byte_seeker/seekable.go deleted file mode 100644 index 3011677e36f766df3ecb3f76f9cd01a82f09463e..0000000000000000000000000000000000000000 --- a/util/util_byte_seeker/seekable.go +++ /dev/null @@ -1,27 +0,0 @@ -package util_byte_seeker - -import ( - "bytes" - "io" -) - -type ByteSeeker struct { - io.ReadSeekCloser - s *bytes.Reader -} - -func NewByteSeeker(b []byte) ByteSeeker { - return ByteSeeker{s: bytes.NewReader(b)} -} - -func (s ByteSeeker) Close() error { - return nil // no-op -} - -func (s ByteSeeker) Read(p []byte) (n int, err error) { - return s.s.Read(p) -} - -func (s ByteSeeker) Seek(offset int64, whence int) (int64, error) { - return s.s.Seek(offset, whence) -}