From 2f6d9cdf03359a60379334388c6e67724904b6f8 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Mon, 8 Jan 2018 21:29:44 -0700
Subject: [PATCH] Use more pointers; Make the db a singleton; Go back to
 passing context/logs around

---
 .../client/error_handlers.go                  |   6 +-
 .../matrix-media-repo/client/r0/download.go   |  32 ++++--
 .../matrix-media-repo/client/r0/identicon.go  |  13 +--
 .../client/r0/preview_url.go                  |  13 ++-
 .../matrix-media-repo/client/r0/thumbnail.go  |  44 +++++---
 .../matrix-media-repo/client/r0/upload.go     |  12 +-
 .../cmd/import_synapse/main.go                |  23 +---
 .../matrix-media-repo/cmd/media_repo/main.go  |  31 +-----
 .../rcontext/request_info.go                  |  14 ---
 .../services/handlers/opengraph_previewer.go  |  45 ++++----
 .../handlers/remote_media_downloader.go       |  38 ++++---
 .../services/handlers/thumbnailer.go          |  44 ++++----
 .../services/media_service.go                 |  66 +++++------
 .../services/thumbnail_service.go             |  45 ++++----
 .../matrix-media-repo/services/url_service.go | 105 +++++++++---------
 .../matrix-media-repo/storage/file_store.go   |   4 +-
 .../matrix-media-repo/storage/storage.go      |  38 +++++--
 .../storage/stores/media_store.go             |  10 +-
 .../storage/stores/thumbnail_store.go         |   6 +-
 .../matrix-media-repo/types/media.go          |   4 +
 .../util/{errcodes => errs}/errorcodes.go     |   2 +-
 .../util/{ => errs}/errors.go                 |   2 +-
 .../turt2live/matrix-media-repo/util/mxc.go   |   7 --
 23 files changed, 302 insertions(+), 302 deletions(-)
 delete mode 100644 src/github.com/turt2live/matrix-media-repo/rcontext/request_info.go
 rename src/github.com/turt2live/matrix-media-repo/util/{errcodes => errs}/errorcodes.go (92%)
 rename src/github.com/turt2live/matrix-media-repo/util/{ => errs}/errors.go (95%)
 delete mode 100644 src/github.com/turt2live/matrix-media-repo/util/mxc.go

diff --git a/src/github.com/turt2live/matrix-media-repo/client/error_handlers.go b/src/github.com/turt2live/matrix-media-repo/client/error_handlers.go
index dabbe61d..506a02ae 100644
--- a/src/github.com/turt2live/matrix-media-repo/client/error_handlers.go
+++ b/src/github.com/turt2live/matrix-media-repo/client/error_handlers.go
@@ -3,13 +3,13 @@ package client
 import (
 	"net/http"
 
-	"github.com/turt2live/matrix-media-repo/rcontext"
+	"github.com/sirupsen/logrus"
 )
 
-func NotFoundHandler(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) interface{} {
+func NotFoundHandler(w http.ResponseWriter, r *http.Request, log *logrus.Entry) interface{} {
 	return NotFoundError()
 }
 
-func MethodNotAllowedHandler(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) interface{} {
+func MethodNotAllowedHandler(w http.ResponseWriter, r *http.Request, log *logrus.Entry) interface{} {
 	return MethodNotAllowed()
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/client/r0/download.go b/src/github.com/turt2live/matrix-media-repo/client/r0/download.go
index 624b0832..59ca6841 100644
--- a/src/github.com/turt2live/matrix-media-repo/client/r0/download.go
+++ b/src/github.com/turt2live/matrix-media-repo/client/r0/download.go
@@ -1,25 +1,27 @@
 package r0
 
 import (
+	"io"
 	"net/http"
+	"os"
 
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/client"
-	"github.com/turt2live/matrix-media-repo/rcontext"
 	"github.com/turt2live/matrix-media-repo/services"
 	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/errs"
 )
 
 type DownloadMediaResponse struct {
 	ContentType string
 	Filename    string
 	SizeBytes   int64
-	Location    string
+	Data        io.ReadCloser
 }
 
-func DownloadMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) interface{} {
-	if !ValidateUserCanDownload(r, i) {
+func DownloadMedia(w http.ResponseWriter, r *http.Request, log *logrus.Entry) interface{} {
+	if !ValidateUserCanDownload(r) {
 		return client.AuthFailed()
 	}
 
@@ -29,22 +31,22 @@ func DownloadMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestInf
 	mediaId := params["mediaId"]
 	filename := params["filename"]
 
-	i.Log = i.Log.WithFields(logrus.Fields{
+	log = log.WithFields(logrus.Fields{
 		"mediaId":  mediaId,
 		"server":   server,
 		"filename": filename,
 	})
 
-	svc := services.CreateMediaService(i)
+	svc := services.NewMediaService(r.Context(), log)
 
 	media, err := svc.GetMedia(server, mediaId)
 	if err != nil {
-		if err == util.ErrMediaNotFound {
+		if err == errs.ErrMediaNotFound {
 			return client.NotFoundError()
-		} else if err == util.ErrMediaTooLarge {
+		} else if err == errs.ErrMediaTooLarge {
 			return client.RequestTooLarge()
 		}
-		i.Log.Error("Unexpected error locating media: " + err.Error())
+		log.Error("Unexpected error locating media: " + err.Error())
 		return client.InternalServerError("Unexpected Error")
 	}
 
@@ -52,21 +54,27 @@ func DownloadMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestInf
 		filename = media.UploadName
 	}
 
+	fstream, err := os.Open(media.Location)
+	if err != nil {
+		log.Error("Unexpected error opening media: " + err.Error())
+		return client.InternalServerError("Unexpected Error")
+	}
+
 	return &DownloadMediaResponse{
 		ContentType: media.ContentType,
 		Filename:    filename,
 		SizeBytes:   media.SizeBytes,
-		Location:    media.Location,
+		Data:        fstream,
 	}
 }
 
-func ValidateUserCanDownload(r *http.Request, i rcontext.RequestInfo) (bool) {
+func ValidateUserCanDownload(r *http.Request) (bool) {
 	hs := util.GetHomeserverConfig(r.Host)
 	if !hs.DownloadRequiresAuth {
 		return true // no auth required == can access
 	}
 
 	accessToken := util.GetAccessTokenFromRequest(r)
-	userId, err := util.GetUserIdFromToken(i.Context, r.Host, accessToken)
+	userId, err := util.GetUserIdFromToken(r.Context(), r.Host, accessToken)
 	return userId != "" && err != nil
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/client/r0/identicon.go b/src/github.com/turt2live/matrix-media-repo/client/r0/identicon.go
index fa511a06..ca030225 100644
--- a/src/github.com/turt2live/matrix-media-repo/client/r0/identicon.go
+++ b/src/github.com/turt2live/matrix-media-repo/client/r0/identicon.go
@@ -14,18 +14,17 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/client"
 	"github.com/turt2live/matrix-media-repo/config"
-	"github.com/turt2live/matrix-media-repo/rcontext"
 )
 
 type IdenticonResponse struct {
 	Avatar io.Reader
 }
 
-func Identicon(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) interface{} {
+func Identicon(w http.ResponseWriter, r *http.Request, log *logrus.Entry) interface{} {
 	if !config.Get().Identicons.Enabled {
 		return client.NotFoundError()
 	}
-	if !ValidateUserCanDownload(r, i) {
+	if !ValidateUserCanDownload(r) {
 		return client.AuthFailed()
 	}
 
@@ -52,7 +51,7 @@ func Identicon(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) i
 		}
 	}
 
-	i.Log = i.Log.WithFields(logrus.Fields{
+	log = log.WithFields(logrus.Fields{
 		"identiconWidth":  width,
 		"identiconHeight": height,
 		"identiconSeed":   seed,
@@ -76,18 +75,18 @@ func Identicon(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) i
 		},
 	}
 
-	i.Log.Info("Generating identicon")
+	log.Info("Generating identicon")
 	img := sig.Make(width, false, []byte(hashed))
 	if width != height {
 		// Resize to the desired height
-		i.Log.Info("Resizing image to fit height")
+		log.Info("Resizing image to fit height")
 		img = imaging.Resize(img, width, height, imaging.Lanczos)
 	}
 
 	imgData := &bytes.Buffer{}
 	err = imaging.Encode(imgData, img, imaging.PNG)
 	if err != nil {
-		i.Log.Error("Error generating image:" + err.Error())
+		log.Error("Error generating image:" + err.Error())
 		return client.InternalServerError("error generating identicon")
 	}
 
diff --git a/src/github.com/turt2live/matrix-media-repo/client/r0/preview_url.go b/src/github.com/turt2live/matrix-media-repo/client/r0/preview_url.go
index 69d99442..3283f26e 100644
--- a/src/github.com/turt2live/matrix-media-repo/client/r0/preview_url.go
+++ b/src/github.com/turt2live/matrix-media-repo/client/r0/preview_url.go
@@ -5,11 +5,12 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/client"
 	"github.com/turt2live/matrix-media-repo/config"
-	"github.com/turt2live/matrix-media-repo/rcontext"
 	"github.com/turt2live/matrix-media-repo/services"
 	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/errs"
 )
 
 type MatrixOpenGraph struct {
@@ -25,7 +26,7 @@ type MatrixOpenGraph struct {
 	ImageHeight int    `json:"og:image:height,omitempty"`
 }
 
-func PreviewUrl(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) interface{} {
+func PreviewUrl(w http.ResponseWriter, r *http.Request, log *logrus.Entry) interface{} {
 	if !config.Get().UrlPreviews.Enabled {
 		return client.NotFoundError()
 	}
@@ -45,7 +46,7 @@ func PreviewUrl(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo)
 	if tsStr != "" {
 		ts, err = strconv.ParseInt(tsStr, 10, 64)
 		if err != nil {
-			i.Log.Error("Error parsing ts: " + err.Error())
+			log.Error("Error parsing ts: " + err.Error())
 			return client.BadRequest(err.Error())
 		}
 	}
@@ -58,12 +59,12 @@ func PreviewUrl(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo)
 		return client.BadRequest("Scheme not accepted")
 	}
 
-	svc := services.CreateUrlService(i)
+	svc := services.NewUrlService(r.Context(), log)
 	preview, err := svc.GetPreview(urlStr, r.Host, userId, ts)
 	if err != nil {
-		if err == util.ErrMediaNotFound || err == util.ErrHostNotFound {
+		if err == errs.ErrMediaNotFound || err == errs.ErrHostNotFound {
 			return client.NotFoundError()
-		} else if err == util.ErrInvalidHost || err == util.ErrHostBlacklisted {
+		} else if err == errs.ErrInvalidHost || err == errs.ErrHostBlacklisted {
 			return client.BadRequest(err.Error())
 		} else {
 			return client.InternalServerError("unexpected error during request")
diff --git a/src/github.com/turt2live/matrix-media-repo/client/r0/thumbnail.go b/src/github.com/turt2live/matrix-media-repo/client/r0/thumbnail.go
index 12482f73..d7e8cce1 100644
--- a/src/github.com/turt2live/matrix-media-repo/client/r0/thumbnail.go
+++ b/src/github.com/turt2live/matrix-media-repo/client/r0/thumbnail.go
@@ -2,19 +2,19 @@ package r0
 
 import (
 	"net/http"
+	"os"
 	"strconv"
 
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/client"
 	"github.com/turt2live/matrix-media-repo/config"
-	"github.com/turt2live/matrix-media-repo/rcontext"
 	"github.com/turt2live/matrix-media-repo/services"
-	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/errs"
 )
 
-func ThumbnailMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) interface{} {
-	if !ValidateUserCanDownload(r, i) {
+func ThumbnailMedia(w http.ResponseWriter, r *http.Request, log *logrus.Entry) interface{} {
+	if !ValidateUserCanDownload(r) {
 		return client.AuthFailed()
 	}
 
@@ -23,7 +23,7 @@ func ThumbnailMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestIn
 	server := params["server"]
 	mediaId := params["mediaId"]
 
-	i.Log = i.Log.WithFields(logrus.Fields{
+	log = log.WithFields(logrus.Fields{
 		"mediaId": mediaId,
 		"server":  server,
 	})
@@ -53,45 +53,57 @@ func ThumbnailMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestIn
 		method = "crop"
 	}
 
-	i.Log = i.Log.WithFields(logrus.Fields{
+	log = log.WithFields(logrus.Fields{
 		"requestedWidth":  width,
 		"requestedHeight": height,
 		"requestedMethod": method,
 	})
 
-	mediaSvc := services.CreateMediaService(i)
-	thumbSvc := services.CreateThumbnailService(i)
+	mediaSvc := services.NewMediaService(r.Context(), log)
+	thumbSvc := services.NewThumbnailService(r.Context(), log)
 
 	media, err := mediaSvc.GetMedia(server, mediaId)
 	if err != nil {
-		if err == util.ErrMediaNotFound {
+		if err == errs.ErrMediaNotFound {
 			return client.NotFoundError()
-		} else if err == util.ErrMediaTooLarge {
+		} else if err == errs.ErrMediaTooLarge {
 			return client.RequestTooLarge()
 		}
-		i.Log.Error("Unexpected error locating media: " + err.Error())
+		log.Error("Unexpected error locating media: " + err.Error())
 		return client.InternalServerError("Unexpected Error")
 	}
 
 	thumb, err := thumbSvc.GetThumbnail(media, width, height, method)
 	if err != nil {
-		if err == util.ErrMediaTooLarge {
-			i.Log.Warn("Media too large to thumbnail, returning source image instead")
+		fstream, err := os.Open(media.Location)
+		if err != nil {
+			log.Error("Unexpected error opening media: " + err.Error())
+			return client.InternalServerError("Unexpected Error")
+		}
+
+		if err == errs.ErrMediaTooLarge {
+			log.Warn("Media too large to thumbnail, returning source image instead")
 			return &DownloadMediaResponse{
 				ContentType: media.ContentType,
 				SizeBytes:   media.SizeBytes,
-				Location:    media.Location,
+				Data:        fstream,
 				Filename:    "thumbnail",
 			}
 		}
-		i.Log.Error("Unexpected error getting thumbnail: " + err.Error())
+		log.Error("Unexpected error getting thumbnail: " + err.Error())
+		return client.InternalServerError("Unexpected Error")
+	}
+
+	fstream, err := os.Open(thumb.Location)
+	if err != nil {
+		log.Error("Unexpected error opening thumbnail media: " + err.Error())
 		return client.InternalServerError("Unexpected Error")
 	}
 
 	return &DownloadMediaResponse{
 		ContentType: thumb.ContentType,
 		SizeBytes:   thumb.SizeBytes,
-		Location:    thumb.Location,
+		Data:        fstream,
 		Filename:    "thumbnail",
 	}
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/client/r0/upload.go b/src/github.com/turt2live/matrix-media-repo/client/r0/upload.go
index 80e29358..da2900c9 100644
--- a/src/github.com/turt2live/matrix-media-repo/client/r0/upload.go
+++ b/src/github.com/turt2live/matrix-media-repo/client/r0/upload.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/client"
-	"github.com/turt2live/matrix-media-repo/rcontext"
 	"github.com/turt2live/matrix-media-repo/services"
 	"github.com/turt2live/matrix-media-repo/util"
 )
@@ -16,7 +15,7 @@ type MediaUploadedResponse struct {
 	ContentUri string `json:"content_uri"`
 }
 
-func UploadMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) interface{} {
+func UploadMedia(w http.ResponseWriter, r *http.Request, log *logrus.Entry) interface{} {
 	accessToken := util.GetAccessTokenFromRequest(r)
 	userId, err := util.GetUserIdFromToken(r.Context(), r.Host, accessToken)
 	if err != nil || userId == "" {
@@ -28,7 +27,7 @@ func UploadMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo)
 		filename = "upload.bin"
 	}
 
-	i.Log = i.Log.WithFields(logrus.Fields{
+	log = log.WithFields(logrus.Fields{
 		"filename": filename,
 		"userId":   userId,
 	})
@@ -38,7 +37,7 @@ func UploadMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo)
 		contentType = "application/octet-stream" // binary
 	}
 
-	svc := services.CreateMediaService(i)
+	svc := services.NewMediaService(r.Context(), log)
 
 	if svc.IsTooLarge(r.ContentLength, r.Header.Get("Content-Length")) {
 		io.Copy(ioutil.Discard, r.Body) // Ditch the entire request
@@ -51,10 +50,9 @@ func UploadMedia(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo)
 		io.Copy(ioutil.Discard, r.Body) // Ditch the entire request
 		defer r.Body.Close()
 
-		i.Log.Error("Unexpected error storing media: " + err.Error())
+		log.Error("Unexpected error storing media: " + err.Error())
 		return client.InternalServerError("Unexpected Error")
 	}
 
-	mxc := util.MediaToMxc(&media)
-	return &MediaUploadedResponse{mxc}
+	return &MediaUploadedResponse{media.MxcUri()}
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/cmd/import_synapse/main.go b/src/github.com/turt2live/matrix-media-repo/cmd/import_synapse/main.go
index 5dc6f5a4..8c3f8874 100644
--- a/src/github.com/turt2live/matrix-media-repo/cmd/import_synapse/main.go
+++ b/src/github.com/turt2live/matrix-media-repo/cmd/import_synapse/main.go
@@ -13,9 +13,7 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/config"
 	"github.com/turt2live/matrix-media-repo/logging"
-	"github.com/turt2live/matrix-media-repo/rcontext"
 	"github.com/turt2live/matrix-media-repo/services"
-	"github.com/turt2live/matrix-media-repo/storage"
 	"github.com/turt2live/matrix-media-repo/synapse"
 )
 
@@ -48,11 +46,6 @@ func main() {
 
 	logrus.Info("Setting up for importing...")
 
-	db, err := storage.OpenDatabase(config.Get().Database.Postgres)
-	if err != nil {
-		panic(err)
-	}
-
 	connectionString := "postgres://" + *postgresUsername + ":" + realPsqlPassword + "@" + *postgresHost + ":" + strconv.Itoa(*postgresPort) + "/" + *postgresDatabase + "?sslmode=disable"
 	csApiUrl := *baseUrl
 	if csApiUrl[len(csApiUrl)-1:] == "/" {
@@ -77,27 +70,19 @@ func main() {
 		percent := int((float32(i+1) / float32(len(records))) * 100)
 		record := records[i]
 
-		info := rcontext.RequestInfo{
-			Log: logrus.WithFields(logrus.Fields{
-				"mediaId": record.MediaId,
-			}),
-			Context: ctx,
-			Db:      *db,
-		}
-
-		info.Log.Info(fmt.Sprintf("Downloading %s (%d/%d %d%%)", record.MediaId, i+1, len(records), percent))
+		logrus.Info(fmt.Sprintf("Downloading %s (%d/%d %d%%)", record.MediaId, i+1, len(records), percent))
 
 		body, err := downloadMedia(csApiUrl, *serverName, record.MediaId)
 		if err != nil {
-			info.Log.Error(err.Error())
+			logrus.Error(err.Error())
 			continue
 		}
 
-		svc := services.CreateMediaService(info)
+		svc := services.NewMediaService(ctx, logrus.WithFields(logrus.Fields{}))
 
 		_, err = svc.StoreMedia(body, record.ContentType, record.UploadName, record.UserId, *serverName, record.MediaId)
 		if err != nil {
-			info.Log.Error(err.Error())
+			logrus.Error(err.Error())
 			continue
 		}
 
diff --git a/src/github.com/turt2live/matrix-media-repo/cmd/media_repo/main.go b/src/github.com/turt2live/matrix-media-repo/cmd/media_repo/main.go
index 884c4140..a7b7c22e 100644
--- a/src/github.com/turt2live/matrix-media-repo/cmd/media_repo/main.go
+++ b/src/github.com/turt2live/matrix-media-repo/cmd/media_repo/main.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"os"
 	"reflect"
 	"strconv"
 	"strings"
@@ -18,8 +17,6 @@ import (
 	"github.com/turt2live/matrix-media-repo/client/r0"
 	"github.com/turt2live/matrix-media-repo/config"
 	"github.com/turt2live/matrix-media-repo/logging"
-	"github.com/turt2live/matrix-media-repo/rcontext"
-	"github.com/turt2live/matrix-media-repo/storage"
 	"github.com/turt2live/matrix-media-repo/util"
 )
 
@@ -30,12 +27,11 @@ type requestCounter struct {
 }
 
 type Handler struct {
-	h    func(http.ResponseWriter, *http.Request, rcontext.RequestInfo) interface{}
+	h    func(http.ResponseWriter, *http.Request, *log.Entry) interface{}
 	opts HandlerOpts
 }
 
 type HandlerOpts struct {
-	db         storage.Database
 	reqCounter *requestCounter
 }
 
@@ -56,13 +52,8 @@ func main() {
 
 	log.Info("Starting media repository...")
 
-	db, err := storage.OpenDatabase(config.Get().Database.Postgres)
-	if err != nil {
-		panic(err)
-	}
-
 	counter := requestCounter{}
-	hOpts := HandlerOpts{*db, &counter}
+	hOpts := HandlerOpts{&counter}
 
 	optionsHandler := Handler{optionsRequest, hOpts}
 	uploadHandler := Handler{r0.UploadMedia, hOpts}
@@ -146,11 +137,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	var res interface{} = client.AuthFailed()
 	if util.IsServerOurs(r.Host) {
 		contextLog.Info("Server is owned by us, processing request")
-		res = h.h(w, r, rcontext.RequestInfo{
-			Log:     contextLog,
-			Context: r.Context(),
-			Db:      h.opts.db,
-		})
+		res = h.h(w, r, contextLog)
 		if res == nil {
 			res = &EmptyResponse{}
 		}
@@ -194,14 +181,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", result.ContentType)
 		w.Header().Set("Content-Disposition", "inline; filename=\""+result.Filename+"\"")
 		w.Header().Set("Content-Length", fmt.Sprint(result.SizeBytes))
-		f, err := os.Open(result.Location)
-		if err != nil {
-			w.Header().Set("Content-Type", "application/json")
-			http.Error(w, UnkErrJson, http.StatusInternalServerError)
-			break
-		}
-		defer f.Close()
-		io.Copy(w, f)
+		defer result.Data.Close()
+		io.Copy(w, result.Data)
 		break
 	case *r0.IdenticonResponse:
 		w.Header().Set("Content-Type", "image/png")
@@ -221,6 +202,6 @@ func (c *requestCounter) GetNextId() string {
 	return "REQ-" + strId
 }
 
-func optionsRequest(w http.ResponseWriter, r *http.Request, i rcontext.RequestInfo) interface{} {
+func optionsRequest(w http.ResponseWriter, r *http.Request, log *log.Entry) interface{} {
 	return &EmptyResponse{}
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/rcontext/request_info.go b/src/github.com/turt2live/matrix-media-repo/rcontext/request_info.go
deleted file mode 100644
index 1f95b7f7..00000000
--- a/src/github.com/turt2live/matrix-media-repo/rcontext/request_info.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package rcontext
-
-import (
-	"context"
-
-	"github.com/sirupsen/logrus"
-	"github.com/turt2live/matrix-media-repo/storage"
-)
-
-type RequestInfo struct {
-	Context context.Context
-	Log     *logrus.Entry
-	Db      storage.Database
-}
diff --git a/src/github.com/turt2live/matrix-media-repo/services/handlers/opengraph_previewer.go b/src/github.com/turt2live/matrix-media-repo/services/handlers/opengraph_previewer.go
index b889b532..f3b6daf5 100644
--- a/src/github.com/turt2live/matrix-media-repo/services/handlers/opengraph_previewer.go
+++ b/src/github.com/turt2live/matrix-media-repo/services/handlers/opengraph_previewer.go
@@ -1,6 +1,7 @@
 package handlers
 
 import (
+	"context"
 	"errors"
 	"io"
 	"io/ioutil"
@@ -14,8 +15,7 @@ import (
 	"github.com/dyatlov/go-opengraph/opengraph"
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/config"
-	"github.com/turt2live/matrix-media-repo/rcontext"
-	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/errs"
 )
 
 type OpenGraphResult struct {
@@ -24,8 +24,7 @@ type OpenGraphResult struct {
 	Type        string
 	Description string
 	Title       string
-	Image       OpenGraphImage
-	HasImage    bool
+	Image       *OpenGraphImage
 }
 
 type OpenGraphImage struct {
@@ -37,22 +36,27 @@ type OpenGraphImage struct {
 }
 
 type OpenGraphUrlPreviewer struct {
-	Info rcontext.RequestInfo
+	ctx context.Context
+	log *logrus.Entry
+}
+
+func NewOpenGraphPreviewer(ctx context.Context, log *logrus.Entry) *OpenGraphUrlPreviewer {
+	return &OpenGraphUrlPreviewer{ctx, log}
 }
 
 func (p *OpenGraphUrlPreviewer) GeneratePreview(urlStr string) (OpenGraphResult, error) {
-	html, err := downloadContent(urlStr, p.Info.Log)
+	html, err := downloadContent(urlStr, p.log)
 	if err != nil {
-		p.Info.Log.Error("Error downloading content: " + err.Error())
+		p.log.Error("Error downloading content: " + err.Error())
 
 		// We'll consider it not found for the sake of processing
-		return OpenGraphResult{}, util.ErrMediaNotFound
+		return OpenGraphResult{}, errs.ErrMediaNotFound
 	}
 
 	og := opengraph.NewOpenGraph()
 	err = og.ProcessHTML(strings.NewReader(html))
 	if err != nil {
-		p.Info.Log.Error("Error getting OpenGraph: " + err.Error())
+		p.log.Error("Error getting OpenGraph: " + err.Error())
 		return OpenGraphResult{}, err
 	}
 
@@ -77,25 +81,24 @@ func (p *OpenGraphUrlPreviewer) GeneratePreview(urlStr string) (OpenGraphResult,
 	if og.Images != nil && len(og.Images) > 0 {
 		baseUrl, err := url.Parse(urlStr)
 		if err != nil {
-			p.Info.Log.Error("Non-fatal error getting thumbnail (parsing base url): " + err.Error())
+			p.log.Error("Non-fatal error getting thumbnail (parsing base url): " + err.Error())
 			return *graph, nil
 		}
 
 		imgUrl, err := url.Parse(og.Images[0].URL)
 		if err != nil {
-			p.Info.Log.Error("Non-fatal error getting thumbnail (parsing image url): " + err.Error())
+			p.log.Error("Non-fatal error getting thumbnail (parsing image url): " + err.Error())
 			return *graph, nil
 		}
 
 		imgAbsUrl := baseUrl.ResolveReference(imgUrl)
-		img, err := downloadImage(imgAbsUrl.String(), p.Info)
+		img, err := downloadImage(imgAbsUrl.String(), p.log)
 		if err != nil {
-			p.Info.Log.Error("Non-fatal error getting thumbnail (downloading image): " + err.Error())
+			p.log.Error("Non-fatal error getting thumbnail (downloading image): " + err.Error())
 			return *graph, nil
 		}
 
 		graph.Image = img
-		graph.HasImage = true
 	}
 
 	return *graph, nil
@@ -113,7 +116,7 @@ func downloadContent(urlStr string, log *logrus.Entry) (string, error) {
 	}
 
 	if config.Get().UrlPreviews.MaxPageSizeBytes > 0 && resp.ContentLength >= 0 && resp.ContentLength > config.Get().UrlPreviews.MaxPageSizeBytes {
-		return "", util.ErrMediaTooLarge
+		return "", errs.ErrMediaTooLarge
 	}
 
 	var reader io.Reader
@@ -133,15 +136,15 @@ func downloadContent(urlStr string, log *logrus.Entry) (string, error) {
 	return html, nil
 }
 
-func downloadImage(imageUrl string, i rcontext.RequestInfo) (OpenGraphImage, error) {
-	i.Log.Info("Getting image from " + imageUrl)
+func downloadImage(imageUrl string, log *logrus.Entry) (*OpenGraphImage, error) {
+	log.Info("Getting image from " + imageUrl)
 	resp, err := http.Get(imageUrl)
 	if err != nil {
-		return OpenGraphImage{}, err
+		return nil, err
 	}
 	if resp.StatusCode != http.StatusOK {
-		i.Log.Warn("Received status code " + strconv.Itoa(resp.StatusCode))
-		return OpenGraphImage{}, errors.New("error during transfer")
+		log.Warn("Received status code " + strconv.Itoa(resp.StatusCode))
+		return nil, errors.New("error during transfer")
 	}
 
 	image := &OpenGraphImage{
@@ -156,7 +159,7 @@ func downloadImage(imageUrl string, i rcontext.RequestInfo) (OpenGraphImage, err
 		image.Filename = params["filename"]
 	}
 
-	return *image, nil
+	return image, nil
 }
 
 func calcTitle(html string) string {
diff --git a/src/github.com/turt2live/matrix-media-repo/services/handlers/remote_media_downloader.go b/src/github.com/turt2live/matrix-media-repo/services/handlers/remote_media_downloader.go
index bbb80c12..e1865940 100644
--- a/src/github.com/turt2live/matrix-media-repo/services/handlers/remote_media_downloader.go
+++ b/src/github.com/turt2live/matrix-media-repo/services/handlers/remote_media_downloader.go
@@ -1,16 +1,16 @@
 package handlers
 
 import (
+	"context"
 	"errors"
 	"io"
 	"mime"
 	"strconv"
 
 	"github.com/matrix-org/gomatrixserverlib"
+	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/config"
-	"github.com/turt2live/matrix-media-repo/rcontext"
-	"github.com/turt2live/matrix-media-repo/storage/stores"
-	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/errs"
 )
 
 type DownloadedMedia struct {
@@ -20,33 +20,37 @@ type DownloadedMedia struct {
 }
 
 type RemoteMediaDownloader struct {
-	MediaStore stores.MediaStore
-	Info       rcontext.RequestInfo
+	ctx context.Context
+	log *logrus.Entry
 }
 
-func (r *RemoteMediaDownloader) Download(server string, mediaId string) (DownloadedMedia, error) {
+func NewRemoteMediaDownloader(ctx context.Context, log *logrus.Entry) *RemoteMediaDownloader {
+	return &RemoteMediaDownloader{ctx, log}
+}
+
+func (r *RemoteMediaDownloader) Download(server string, mediaId string) (*DownloadedMedia, error) {
 	mtxClient := gomatrixserverlib.NewClient()
 	mtxServer := gomatrixserverlib.ServerName(server)
-	resp, err := mtxClient.CreateMediaDownloadRequest(r.Info.Context, mtxServer, mediaId)
+	resp, err := mtxClient.CreateMediaDownloadRequest(r.ctx, mtxServer, mediaId)
 	if err != nil {
-		return DownloadedMedia{}, err
+		return nil, err
 	}
 
 	if resp.StatusCode == 404 {
-		r.Info.Log.Info("Remote media not found")
-		return DownloadedMedia{}, util.ErrMediaNotFound
+		r.log.Info("Remote media not found")
+		return nil, errs.ErrMediaNotFound
 	} else if resp.StatusCode != 200 {
-		r.Info.Log.Info("Unknown error fetching remote media; received status code " + strconv.Itoa(resp.StatusCode))
-		return DownloadedMedia{}, errors.New("could not fetch remote media")
+		r.log.Info("Unknown error fetching remote media; received status code " + strconv.Itoa(resp.StatusCode))
+		return nil, errors.New("could not fetch remote media")
 	}
 
 	contentLength, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
 	if err != nil {
-		return DownloadedMedia{}, err
+		return nil, err
 	}
 	if config.Get().Downloads.MaxSizeBytes > 0 && contentLength > config.Get().Downloads.MaxSizeBytes {
-		r.Info.Log.Warn("Attempted to download media that was too large")
-		return DownloadedMedia{}, util.ErrMediaTooLarge
+		r.log.Warn("Attempted to download media that was too large")
+		return nil, errs.ErrMediaTooLarge
 	}
 
 	request := &DownloadedMedia{
@@ -60,6 +64,6 @@ func (r *RemoteMediaDownloader) Download(server string, mediaId string) (Downloa
 		request.DesiredFilename = params["filename"]
 	}
 
-	r.Info.Log.Info("Persisting downloaded media")
-	return *request, nil
+	r.log.Info("Persisting downloaded media")
+	return request, nil
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/services/handlers/thumbnailer.go b/src/github.com/turt2live/matrix-media-repo/services/handlers/thumbnailer.go
index 52585230..1136bbe3 100644
--- a/src/github.com/turt2live/matrix-media-repo/services/handlers/thumbnailer.go
+++ b/src/github.com/turt2live/matrix-media-repo/services/handlers/thumbnailer.go
@@ -2,12 +2,12 @@ package handlers
 
 import (
 	"bytes"
+	"context"
 	"errors"
 
 	"github.com/disintegration/imaging"
-	"github.com/turt2live/matrix-media-repo/rcontext"
+	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/storage"
-	"github.com/turt2live/matrix-media-repo/storage/stores"
 	"github.com/turt2live/matrix-media-repo/types"
 	"github.com/turt2live/matrix-media-repo/util"
 )
@@ -19,16 +19,18 @@ type GeneratedThumbnail struct {
 }
 
 type Thumbnailer struct {
-	ThumbnailStore stores.ThumbnailStore
-	Info           rcontext.RequestInfo
+	ctx context.Context
+	log *logrus.Entry
 }
 
-func (t *Thumbnailer) GenerateThumbnail(media types.Media, width int, height int, method string) (GeneratedThumbnail, error) {
-	thumb := &GeneratedThumbnail{}
+func NewThumbnailer(ctx context.Context, log *logrus.Entry) *Thumbnailer {
+	return &Thumbnailer{ctx, log}
+}
 
+func (t *Thumbnailer) GenerateThumbnail(media *types.Media, width int, height int, method string) (*GeneratedThumbnail, error) {
 	src, err := imaging.Open(media.Location)
 	if err != nil {
-		return *thumb, err
+		return nil, err
 	}
 
 	srcWidth := src.Bounds().Max.X
@@ -39,16 +41,18 @@ func (t *Thumbnailer) GenerateThumbnail(media types.Media, width int, height int
 	if aspectRatio == targetAspectRatio {
 		// Highly unlikely, but if the aspect ratios match then just resize
 		method = "scale"
-		t.Info.Log.Info("Aspect ratio is the same, converting method to 'scale'")
+		t.log.Info("Aspect ratio is the same, converting method to 'scale'")
 	}
 
+	thumb := &GeneratedThumbnail{}
+
 	if srcWidth <= width && srcHeight <= height {
 		// Image is too small - don't upscale
 		thumb.ContentType = media.ContentType
 		thumb.DiskLocation = media.Location
 		thumb.SizeBytes = media.SizeBytes
-		t.Info.Log.Warn("Image too small, returning raw image")
-		return *thumb, nil
+		t.log.Warn("Image too small, returning raw image")
+		return thumb, nil
 	}
 
 	if method == "scale" {
@@ -56,34 +60,34 @@ func (t *Thumbnailer) GenerateThumbnail(media types.Media, width int, height int
 	} else if method == "crop" {
 		src = imaging.Fill(src, width, height, imaging.Center, imaging.Lanczos)
 	} else {
-		t.Info.Log.Error("Unrecognized thumbnail method: " + method)
-		return *thumb, errors.New("unrecognized method: " + method)
+		t.log.Error("Unrecognized thumbnail method: " + method)
+		return nil, errors.New("unrecognized method: " + method)
 	}
 
 	// Put the image bytes into a memory buffer
 	imgData := &bytes.Buffer{}
 	err = imaging.Encode(imgData, src, imaging.PNG)
 	if err != nil {
-		t.Info.Log.Error("Unexpected error encoding thumbnail: " + err.Error())
-		return *thumb, err
+		t.log.Error("Unexpected error encoding thumbnail: " + err.Error())
+		return nil, err
 	}
 
 	// Reset the buffer pointer and store the file
-	location, err := storage.PersistFile(imgData, t.Info.Context, &t.Info.Db)
+	location, err := storage.PersistFile(imgData, t.ctx)
 	if err != nil {
-		t.Info.Log.Error("Unexpected error saving thumbnail: " + err.Error())
-		return *thumb, err
+		t.log.Error("Unexpected error saving thumbnail: " + err.Error())
+		return nil, err
 	}
 
 	fileSize, err := util.FileSize(location)
 	if err != nil {
-		t.Info.Log.Error("Unexpected error getting the size of the thumbnail: " + err.Error())
-		return *thumb, err
+		t.log.Error("Unexpected error getting the size of the thumbnail: " + err.Error())
+		return nil, err
 	}
 
 	thumb.DiskLocation = location
 	thumb.ContentType = "image/png"
 	thumb.SizeBytes = fileSize
 
-	return *thumb, nil
+	return thumb, nil
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/services/media_service.go b/src/github.com/turt2live/matrix-media-repo/services/media_service.go
index 7068314a..bd250a5f 100644
--- a/src/github.com/turt2live/matrix-media-repo/services/media_service.go
+++ b/src/github.com/turt2live/matrix-media-repo/services/media_service.go
@@ -1,6 +1,7 @@
 package services
 
 import (
+	"context"
 	"database/sql"
 	"io"
 	"os"
@@ -8,31 +9,33 @@ import (
 
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/config"
-	"github.com/turt2live/matrix-media-repo/rcontext"
 	"github.com/turt2live/matrix-media-repo/services/handlers"
 	"github.com/turt2live/matrix-media-repo/storage"
 	"github.com/turt2live/matrix-media-repo/storage/stores"
 	"github.com/turt2live/matrix-media-repo/types"
 	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/errs"
 )
 
 type MediaService struct {
 	store *stores.MediaStore
-	i     rcontext.RequestInfo
+	ctx   context.Context
+	log   *logrus.Entry
 }
 
-func CreateMediaService(i rcontext.RequestInfo) (*MediaService) {
-	return &MediaService{i.Db.GetMediaStore(i.Context, i.Log), i}
+func NewMediaService(ctx context.Context, log *logrus.Entry) (*MediaService) {
+	store := storage.GetDatabase().GetMediaStore(ctx, log)
+	return &MediaService{store, ctx, log}
 }
 
-func (s *MediaService) GetMedia(server string, mediaId string) (types.Media, error) {
-	s.i.Log.Info("Looking up media")
+func (s *MediaService) GetMedia(server string, mediaId string) (*types.Media, error) {
+	s.log.Info("Looking up media")
 	media, err := s.store.Get(server, mediaId)
 	if err != nil {
 		if err == sql.ErrNoRows {
 			if util.IsServerOurs(server) {
-				s.i.Log.Warn("Media not found")
-				return media, util.ErrMediaNotFound
+				s.log.Warn("Media not found")
+				return media, errs.ErrMediaNotFound
 			}
 		}
 
@@ -42,10 +45,10 @@ func (s *MediaService) GetMedia(server string, mediaId string) (types.Media, err
 	exists, err := util.FileExists(media.Location)
 	if !exists || err != nil {
 		if util.IsServerOurs(server) {
-			s.i.Log.Error("Media not found in file store when we expected it to")
-			return media, util.ErrMediaNotFound
+			s.log.Error("Media not found in file store when we expected it to")
+			return media, errs.ErrMediaNotFound
 		} else {
-			s.i.Log.Warn("Media appears to have been deleted - redownloading")
+			s.log.Warn("Media appears to have been deleted - redownloading")
 			return s.downloadRemoteMedia(server, mediaId)
 		}
 	}
@@ -53,16 +56,13 @@ func (s *MediaService) GetMedia(server string, mediaId string) (types.Media, err
 	return media, nil
 }
 
-func (s *MediaService) downloadRemoteMedia(server string, mediaId string) (types.Media, error) {
-	s.i.Log.Info("Attempting to download remote media")
-	downloader := &handlers.RemoteMediaDownloader{
-		Info:       s.i,
-		MediaStore: *s.store,
-	}
+func (s *MediaService) downloadRemoteMedia(server string, mediaId string) (*types.Media, error) {
+	s.log.Info("Attempting to download remote media")
+	downloader := handlers.NewRemoteMediaDownloader(s.ctx, s.log)
 
 	downloaded, err := downloader.Download(server, mediaId)
 	if err != nil {
-		return types.Media{}, err
+		return nil, err
 	}
 
 	defer downloaded.Contents.Close()
@@ -79,7 +79,7 @@ func (s *MediaService) IsTooLarge(contentLength int64, contentLengthHeader strin
 	if contentLengthHeader != "" {
 		parsed, err := strconv.ParseInt(contentLengthHeader, 10, 64)
 		if err != nil {
-			s.i.Log.Warn("Invalid content length header given; assuming too large. Value received: " + contentLengthHeader)
+			s.log.Warn("Invalid content length header given; assuming too large. Value received: " + contentLengthHeader)
 			return true // Invalid header
 		}
 
@@ -89,7 +89,7 @@ func (s *MediaService) IsTooLarge(contentLength int64, contentLengthHeader strin
 	return false // We can only assume
 }
 
-func (s *MediaService) UploadMedia(contents io.ReadCloser, contentType string, filename string, userId string, host string) (types.Media, error) {
+func (s *MediaService) UploadMedia(contents io.ReadCloser, contentType string, filename string, userId string, host string) (*types.Media, error) {
 	defer contents.Close()
 	var data io.Reader
 	if config.Get().Uploads.MaxSizeBytes > 0 {
@@ -101,41 +101,41 @@ func (s *MediaService) UploadMedia(contents io.ReadCloser, contentType string, f
 	return s.StoreMedia(data, contentType, filename, userId, host, "")
 }
 
-func (s *MediaService) StoreMedia(contents io.Reader, contentType string, filename string, userId string, host string, mediaId string) (types.Media, error) {
+func (s *MediaService) StoreMedia(contents io.Reader, contentType string, filename string, userId string, host string, mediaId string) (*types.Media, error) {
 	isGeneratedId := false
 	if mediaId == "" {
 		mediaId = generateMediaId()
 		isGeneratedId = true
 	}
-	log := s.i.Log.WithFields(logrus.Fields{
+	log := s.log.WithFields(logrus.Fields{
 		"mediaService_mediaId":            mediaId,
 		"mediaService_host":               host,
 		"mediaService_mediaIdIsGenerated": isGeneratedId,
 	})
 
 	// Store the file in a temporary location
-	fileLocation, err := storage.PersistFile(contents, s.i.Context, &s.i.Db)
+	fileLocation, err := storage.PersistFile(contents, s.ctx)
 	if err != nil {
-		return types.Media{}, err
+		return nil, err
 	}
 
 	hash, err := storage.GetFileHash(fileLocation)
 	if err != nil {
 		defer os.Remove(fileLocation) // attempt cleanup
-		return types.Media{}, err
+		return nil, err
 	}
 
 	records, err := s.store.GetByHash(hash)
 	if err != nil {
 		defer os.Remove(fileLocation) // attempt cleanup
-		return types.Media{}, err
+		return nil, err
 	}
 
 	// If there's at least one record, then we have a duplicate hash - try and process it
 	if len(records) > 0 {
 		// See if we one of the duplicate records is a match for the host and media ID. We'll otherwise use
 		// the last duplicate (should only be 1 anyways) as our starting point for a new record.
-		var media types.Media
+		var media *types.Media
 		for i := 0; i < len(records); i++ {
 			media = records[i]
 
@@ -165,9 +165,9 @@ func (s *MediaService) StoreMedia(contents io.Reader, contentType string, filena
 		media.ContentType = contentType
 		media.CreationTs = util.NowMillis()
 
-		err = s.store.Insert(&media)
+		err = s.store.Insert(media)
 		if err != nil {
-			return types.Media{}, err
+			return nil, err
 		}
 
 		overwriteExistingOrDeleteTempFile(fileLocation, media)
@@ -179,7 +179,7 @@ func (s *MediaService) StoreMedia(contents io.Reader, contentType string, filena
 	fileSize, err := util.FileSize(fileLocation)
 	if err != nil {
 		defer os.Remove(fileLocation) // attempt cleanup
-		return types.Media{}, err
+		return nil, err
 	}
 
 	log.Info("Persisting unique media record")
@@ -199,10 +199,10 @@ func (s *MediaService) StoreMedia(contents io.Reader, contentType string, filena
 	err = s.store.Insert(media)
 	if err != nil {
 		defer os.Remove(fileLocation) // attempt cleanup
-		return types.Media{}, err
+		return nil, err
 	}
 
-	return *media, nil
+	return media, nil
 }
 
 func generateMediaId() string {
@@ -214,7 +214,7 @@ func generateMediaId() string {
 	return str
 }
 
-func overwriteExistingOrDeleteTempFile(tempFileLocation string, media types.Media) {
+func overwriteExistingOrDeleteTempFile(tempFileLocation string, media *types.Media) {
 	// 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
 	exists, err := util.FileExists(media.Location)
diff --git a/src/github.com/turt2live/matrix-media-repo/services/thumbnail_service.go b/src/github.com/turt2live/matrix-media-repo/services/thumbnail_service.go
index a9cb589e..779fa781 100644
--- a/src/github.com/turt2live/matrix-media-repo/services/thumbnail_service.go
+++ b/src/github.com/turt2live/matrix-media-repo/services/thumbnail_service.go
@@ -1,36 +1,40 @@
 package services
 
 import (
+	"context"
 	"database/sql"
 	"errors"
 
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/config"
-	"github.com/turt2live/matrix-media-repo/rcontext"
 	"github.com/turt2live/matrix-media-repo/services/handlers"
+	"github.com/turt2live/matrix-media-repo/storage"
 	"github.com/turt2live/matrix-media-repo/storage/stores"
 	"github.com/turt2live/matrix-media-repo/types"
 	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/errs"
 )
 
 type ThumbnailService struct {
 	store *stores.ThumbnailStore
-	i     rcontext.RequestInfo
+	ctx   context.Context
+	log   *logrus.Entry
 }
 
-func CreateThumbnailService(i rcontext.RequestInfo) (*ThumbnailService) {
-	return &ThumbnailService{i.Db.GetThumbnailStore(i.Context, i.Log), i}
+func NewThumbnailService(ctx context.Context, log *logrus.Entry) (*ThumbnailService) {
+	store := storage.GetDatabase().GetThumbnailStore(ctx, log)
+	return &ThumbnailService{store, ctx, log}
 }
 
-func (s *ThumbnailService) GetThumbnail(media types.Media, width int, height int, method string) (types.Thumbnail, error) {
+func (s *ThumbnailService) GetThumbnail(media *types.Media, width int, height int, method string) (*types.Thumbnail, error) {
 	if width <= 0 {
-		return types.Thumbnail{}, errors.New("width must be positive")
+		return nil, errors.New("width must be positive")
 	}
 	if height <= 0 {
-		return types.Thumbnail{}, errors.New("height must be positive")
+		return nil, errors.New("height must be positive")
 	}
 	if method != "crop" && method != "scale" {
-		return types.Thumbnail{}, errors.New("method must be crop or scale")
+		return nil, errors.New("method must be crop or scale")
 	}
 
 	targetWidth := width
@@ -66,32 +70,29 @@ func (s *ThumbnailService) GetThumbnail(media types.Media, width int, height int
 		}
 	}
 
-	s.i.Log = s.i.Log.WithFields(logrus.Fields{
+	s.log = s.log.WithFields(logrus.Fields{
 		"targetWidth":  targetWidth,
 		"targetHeight": targetHeight,
 	})
-	s.i.Log.Info("Looking up thumbnail")
+	s.log.Info("Looking up thumbnail")
 
 	thumb, err := s.store.Get(media.Origin, media.MediaId, targetWidth, targetHeight, method)
 	if err != nil && err != sql.ErrNoRows {
-		s.i.Log.Error("Unexpected error processing thumbnail lookup: " + err.Error())
+		s.log.Error("Unexpected error processing thumbnail lookup: " + err.Error())
 		return thumb, err
 	}
 	if err != sql.ErrNoRows {
-		s.i.Log.Info("Found existing thumbnail")
+		s.log.Info("Found existing thumbnail")
 		return thumb, nil
 	}
 
 	if media.SizeBytes > config.Get().Thumbnails.MaxSourceBytes {
-		s.i.Log.Warn("Media too large to thumbnail")
-		return thumb, util.ErrMediaTooLarge
+		s.log.Warn("Media too large to thumbnail")
+		return thumb, errs.ErrMediaTooLarge
 	}
 
-	s.i.Log.Info("Generating new thumbnail")
-	thumbnailer := &handlers.Thumbnailer{
-		Info:           s.i,
-		ThumbnailStore: *s.store,
-	}
+	s.log.Info("Generating new thumbnail")
+	thumbnailer := handlers.NewThumbnailer(s.ctx, s.log)
 
 	generated, err := thumbnailer.GenerateThumbnail(media, targetWidth, targetHeight, method)
 	if err != nil {
@@ -112,9 +113,9 @@ func (s *ThumbnailService) GetThumbnail(media types.Media, width int, height int
 
 	err = s.store.Insert(newThumb)
 	if err != nil {
-		s.i.Log.Error("Unexpected error caching thumbnail: " + err.Error())
-		return *newThumb, err
+		s.log.Error("Unexpected error caching thumbnail: " + err.Error())
+		return newThumb, err
 	}
 
-	return *newThumb, nil
+	return newThumb, nil
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/services/url_service.go b/src/github.com/turt2live/matrix-media-repo/services/url_service.go
index dfe0b8d7..c173feed 100644
--- a/src/github.com/turt2live/matrix-media-repo/services/url_service.go
+++ b/src/github.com/turt2live/matrix-media-repo/services/url_service.go
@@ -1,6 +1,7 @@
 package services
 
 import (
+	"context"
 	"database/sql"
 	"fmt"
 	"net"
@@ -10,53 +11,55 @@ import (
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/config"
-	"github.com/turt2live/matrix-media-repo/rcontext"
 	"github.com/turt2live/matrix-media-repo/services/handlers"
+	"github.com/turt2live/matrix-media-repo/storage"
+	"github.com/turt2live/matrix-media-repo/storage/stores"
 	"github.com/turt2live/matrix-media-repo/types"
 	"github.com/turt2live/matrix-media-repo/util"
-	"github.com/turt2live/matrix-media-repo/util/errcodes"
+	"github.com/turt2live/matrix-media-repo/util/errs"
 )
 
 type UrlService struct {
-	//store *stores.UrlStore
-	i rcontext.RequestInfo
+	store *stores.UrlStore
+	ctx   context.Context
+	log   *logrus.Entry
 }
 
-func CreateUrlService(i rcontext.RequestInfo) (*UrlService) {
-	return &UrlService{i}
+func NewUrlService(ctx context.Context, log *logrus.Entry) (*UrlService) {
+	store := storage.GetDatabase().GetUrlStore(ctx, log)
+	return &UrlService{store, ctx, log}
 }
 
-func returnCachedPreview(cached *types.CachedUrlPreview) (types.UrlPreview, error) {
-	if cached.ErrorCode == errcodes.ErrCodeInvalidHost {
-		return types.UrlPreview{}, util.ErrInvalidHost
-	} else if cached.ErrorCode == errcodes.ErrCodeHostNotFound {
-		return types.UrlPreview{}, util.ErrHostNotFound
-	} else if cached.ErrorCode == errcodes.ErrCodeHostBlacklisted {
-		return types.UrlPreview{}, util.ErrHostBlacklisted
-	} else if cached.ErrorCode == errcodes.ErrCodeNotFound {
-		return types.UrlPreview{}, util.ErrMediaNotFound
-	} else if cached.ErrorCode == errcodes.ErrCodeUnknown {
-		return types.UrlPreview{}, errors.New("unknown error")
+func returnCachedPreview(cached *types.CachedUrlPreview) (*types.UrlPreview, error) {
+	if cached.ErrorCode == errs.ErrCodeInvalidHost {
+		return nil, errs.ErrInvalidHost
+	} else if cached.ErrorCode == errs.ErrCodeHostNotFound {
+		return nil, errs.ErrHostNotFound
+	} else if cached.ErrorCode == errs.ErrCodeHostBlacklisted {
+		return nil, errs.ErrHostBlacklisted
+	} else if cached.ErrorCode == errs.ErrCodeNotFound {
+		return nil, errs.ErrMediaNotFound
+	} else if cached.ErrorCode == errs.ErrCodeUnknown {
+		return nil, errors.New("unknown error")
 	}
 
-	return *cached.Preview, nil
+	return cached.Preview, nil
 }
 
-func (s *UrlService) GetPreview(urlStr string, onHost string, forUserId string, atTs int64) (types.UrlPreview, error) {
-	s.i.Log = s.i.Log.WithFields(logrus.Fields{
+func (s *UrlService) GetPreview(urlStr string, onHost string, forUserId string, atTs int64) (*types.UrlPreview, error) {
+	s.log = s.log.WithFields(logrus.Fields{
 		"urlService_ts": atTs,
 	})
 
-	urlStore := s.i.Db.GetUrlStore(s.i.Context, s.i.Log)
-	cached, err := urlStore.GetPreview(urlStr, atTs)
+	cached, err := s.store.GetPreview(urlStr, atTs)
 	if err != nil {
-		s.i.Log.Error("Error getting cached URL: " + err.Error())
+		s.log.Error("Error getting cached URL: " + err.Error())
 	}
 	if err != nil && err != sql.ErrNoRows {
-		return types.UrlPreview{}, err
+		return nil, err
 	}
 	if err != sql.ErrNoRows {
-		s.i.Log.Info("Returning cached URL preview")
+		s.log.Info("Returning cached URL preview")
 		return returnCachedPreview(cached)
 	}
 
@@ -66,24 +69,24 @@ func (s *UrlService) GetPreview(urlStr string, onHost string, forUserId string,
 		return s.GetPreview(urlStr, onHost, forUserId, now)
 	}
 
-	s.i.Log.Info("URL preview not cached - fetching resource")
+	s.log.Info("URL preview not cached - fetching resource")
 
 	parsedUrl, err := url.ParseRequestURI(urlStr)
 	if err != nil {
-		s.i.Log.Error("Error parsing url: " + err.Error())
-		urlStore.InsertPreviewError(urlStr, errcodes.ErrCodeInvalidHost)
-		return types.UrlPreview{}, util.ErrInvalidHost
+		s.log.Error("Error parsing url: " + err.Error())
+		s.store.InsertPreviewError(urlStr, errs.ErrCodeInvalidHost)
+		return nil, errs.ErrInvalidHost
 	}
 
 	addrs, err := net.LookupIP(parsedUrl.Host)
 	if err != nil {
-		s.i.Log.Error("Error getting host info: " + err.Error())
-		urlStore.InsertPreviewError(urlStr, errcodes.ErrCodeInvalidHost)
-		return types.UrlPreview{}, util.ErrInvalidHost
+		s.log.Error("Error getting host info: " + err.Error())
+		s.store.InsertPreviewError(urlStr, errs.ErrCodeInvalidHost)
+		return nil, errs.ErrInvalidHost
 	}
 	if len(addrs) == 0 {
-		urlStore.InsertPreviewError(urlStr, errcodes.ErrCodeHostNotFound)
-		return types.UrlPreview{}, util.ErrHostNotFound
+		s.store.InsertPreviewError(urlStr, errs.ErrCodeHostNotFound)
+		return nil, errs.ErrHostNotFound
 	}
 	addr := addrs[0]
 	addrStr := fmt.Sprintf("%v", addr)[1:]
@@ -98,24 +101,24 @@ func (s *UrlService) GetPreview(urlStr string, onHost string, forUserId string,
 	if deniedCidrs == nil {
 		deniedCidrs = []string{}
 	}
-	if !isAllowed(addr, allowedCidrs, deniedCidrs, s.i.Log) {
-		urlStore.InsertPreviewError(urlStr, errcodes.ErrCodeHostBlacklisted)
-		return types.UrlPreview{}, util.ErrHostBlacklisted
+	if !isAllowed(addr, allowedCidrs, deniedCidrs, s.log) {
+		s.store.InsertPreviewError(urlStr, errs.ErrCodeHostBlacklisted)
+		return nil, errs.ErrHostBlacklisted
 	}
 
-	s.i.Log = s.i.Log.WithFields(logrus.Fields{
+	s.log = s.log.WithFields(logrus.Fields{
 		"previewer": "OpenGraph",
 	})
 
-	previewer := &handlers.OpenGraphUrlPreviewer{Info: s.i}
+	previewer := handlers.NewOpenGraphPreviewer(s.ctx, s.log)
 	preview, err := previewer.GeneratePreview(urlStr)
 	if err != nil {
-		if err == util.ErrMediaNotFound {
-			urlStore.InsertPreviewError(urlStr, errcodes.ErrCodeNotFound)
+		if err == errs.ErrMediaNotFound {
+			s.store.InsertPreviewError(urlStr, errs.ErrCodeNotFound)
 		} else {
-			urlStore.InsertPreviewError(urlStr, errcodes.ErrCodeUnknown)
+			s.store.InsertPreviewError(urlStr, errs.ErrCodeUnknown)
 		}
-		return types.UrlPreview{}, err
+		return nil, err
 	}
 
 	result := &types.UrlPreview{
@@ -127,18 +130,18 @@ func (s *UrlService) GetPreview(urlStr string, onHost string, forUserId string,
 	}
 
 	// Store the thumbnail, if there is one
-	mediaSvc := CreateMediaService(s.i)
-	if preview.HasImage && !mediaSvc.IsTooLarge(preview.Image.ContentLength, preview.Image.ContentLengthHeader) {
+	mediaSvc := NewMediaService(s.ctx, s.log)
+	if preview.Image != nil && !mediaSvc.IsTooLarge(preview.Image.ContentLength, preview.Image.ContentLengthHeader) {
 		// UploadMedia will close the read stream for the thumbnail
 		media, err := mediaSvc.UploadMedia(preview.Image.Data, preview.Image.ContentType, preview.Image.Filename, forUserId, onHost)
 		if err != nil {
-			s.i.Log.Warn("Non-fatal error storing preview thumbnail: " + err.Error())
+			s.log.Warn("Non-fatal error storing preview thumbnail: " + err.Error())
 		} else {
 			img, err := imaging.Open(media.Location)
 			if err != nil {
-				s.i.Log.Warn("Non-fatal error getting thumbnail dimensions: " + err.Error())
+				s.log.Warn("Non-fatal error getting thumbnail dimensions: " + err.Error())
 			} else {
-				result.ImageMxc = util.MediaToMxc(&media)
+				result.ImageMxc = media.MxcUri()
 				result.ImageType = media.ContentType
 				result.ImageSize = media.SizeBytes
 				result.ImageWidth = img.Bounds().Max.X
@@ -153,12 +156,12 @@ func (s *UrlService) GetPreview(urlStr string, onHost string, forUserId string,
 		ErrorCode: "",
 		FetchedTs: util.NowMillis(),
 	}
-	err = urlStore.InsertPreview(dbRecord)
+	err = s.store.InsertPreview(dbRecord)
 	if err != nil {
-		s.i.Log.Warn("Error caching URL preview: " + err.Error())
+		s.log.Warn("Error caching URL preview: " + err.Error())
 	}
 
-	return *result, nil
+	return result, nil
 }
 
 func isAllowed(ip net.IP, allowed []string, disallowed []string, log *logrus.Entry) bool {
diff --git a/src/github.com/turt2live/matrix-media-repo/storage/file_store.go b/src/github.com/turt2live/matrix-media-repo/storage/file_store.go
index 51c724f7..fc86dd41 100644
--- a/src/github.com/turt2live/matrix-media-repo/storage/file_store.go
+++ b/src/github.com/turt2live/matrix-media-repo/storage/file_store.go
@@ -13,12 +13,12 @@ import (
 	"github.com/turt2live/matrix-media-repo/util"
 )
 
-func PersistFile(file io.Reader, ctx context.Context, db *Database) (string, error) {
+func PersistFile(file io.Reader, ctx context.Context) (string, error) {
 	var basePath string
 	var pathSize int64
 	for i := 0; i < len(config.Get().Uploads.StoragePaths); i++ {
 		currPath := config.Get().Uploads.StoragePaths[i]
-		size, err := db.GetSizeOfFolderBytes(ctx, currPath)
+		size, err := GetDatabase().GetSizeOfFolderBytes(ctx, currPath)
 		if err != nil {
 			continue
 		}
diff --git a/src/github.com/turt2live/matrix-media-repo/storage/storage.go b/src/github.com/turt2live/matrix-media-repo/storage/storage.go
index 047cd760..e01c9c3b 100644
--- a/src/github.com/turt2live/matrix-media-repo/storage/storage.go
+++ b/src/github.com/turt2live/matrix-media-repo/storage/storage.go
@@ -3,9 +3,11 @@ package storage
 import (
 	"context"
 	"database/sql"
+	"sync"
 
 	_ "github.com/lib/pq" // postgres driver
 	"github.com/sirupsen/logrus"
+	"github.com/turt2live/matrix-media-repo/config"
 	"github.com/turt2live/matrix-media-repo/storage/schema"
 	"github.com/turt2live/matrix-media-repo/storage/stores"
 )
@@ -32,12 +34,27 @@ type repos struct {
 	urlStore       *stores.UrlStoreFactory
 }
 
-func OpenDatabase(connectionString string) (*Database, error) {
-	var d Database
+var dbInstance *Database
+
+func GetDatabase() (*Database) {
+	if dbInstance == nil {
+		var once sync.Once
+		once.Do(func() {
+			err := OpenDatabase(config.Get().Database.Postgres)
+			if err != nil {
+				panic(err)
+			}
+		})
+	}
+	return dbInstance
+}
+
+func OpenDatabase(connectionString string) (error) {
+	d := &Database{}
 	var err error
 
 	if d.db, err = sql.Open("postgres", connectionString); err != nil {
-		return nil, err
+		return err
 	}
 
 	// Make sure the database is how we want it
@@ -45,23 +62,24 @@ func OpenDatabase(connectionString string) (*Database, error) {
 	schema.PrepareThumbnails(d.db)
 	schema.PrepareUrls(d.db)
 
-	// Create the repo factories
+	// New the repo factories
 	if d.repos.mediaStore, err = stores.InitMediaStore(d.db); err != nil {
-		return nil, err
+		return err
 	}
 	if d.repos.thumbnailStore, err = stores.InitThumbnailStore(d.db); err != nil {
-		return nil, err
+		return err
 	}
 	if d.repos.urlStore, err = stores.InitUrlStore(d.db); err != nil {
-		return nil, err
+		return err
 	}
 
 	// Prepare the general statements
 	if d.statements.selectSizeOfFolder, err = d.db.Prepare(selectSizeOfFolder); err != nil {
-		return nil, err
+		return err
 	}
 
-	return &d, nil
+	dbInstance = d
+	return nil
 }
 
 func (d *Database) GetMediaStore(ctx context.Context, log *logrus.Entry) (*stores.MediaStore) {
@@ -69,7 +87,7 @@ func (d *Database) GetMediaStore(ctx context.Context, log *logrus.Entry) (*store
 }
 
 func (d *Database) GetThumbnailStore(ctx context.Context, log *logrus.Entry) (*stores.ThumbnailStore) {
-	return d.repos.thumbnailStore.Create(ctx, log)
+	return d.repos.thumbnailStore.New(ctx, log)
 }
 
 func (d *Database) GetUrlStore(ctx context.Context, log *logrus.Entry) (*stores.UrlStore) {
diff --git a/src/github.com/turt2live/matrix-media-repo/storage/stores/media_store.go b/src/github.com/turt2live/matrix-media-repo/storage/stores/media_store.go
index 4db659fd..768c2550 100644
--- a/src/github.com/turt2live/matrix-media-repo/storage/stores/media_store.go
+++ b/src/github.com/turt2live/matrix-media-repo/storage/stores/media_store.go
@@ -74,15 +74,15 @@ func (s *MediaStore) Insert(media *types.Media) (error) {
 	return err
 }
 
-func (s *MediaStore) GetByHash(hash string) ([]types.Media, error) {
+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
+	var results []*types.Media
 	for rows.Next() {
-		obj := types.Media{}
+		obj := &types.Media{}
 		err = rows.Scan(
 			&obj.Origin,
 			&obj.MediaId,
@@ -103,7 +103,7 @@ func (s *MediaStore) GetByHash(hash string) ([]types.Media, error) {
 	return results, nil
 }
 
-func (s *MediaStore) Get(origin string, mediaId string) (types.Media, error) {
+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,
@@ -116,5 +116,5 @@ func (s *MediaStore) Get(origin string, mediaId string) (types.Media, error) {
 		&m.Location,
 		&m.CreationTs,
 	)
-	return *m, err
+	return m, err
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/storage/stores/thumbnail_store.go b/src/github.com/turt2live/matrix-media-repo/storage/stores/thumbnail_store.go
index 5e064126..e28651e3 100644
--- a/src/github.com/turt2live/matrix-media-repo/storage/stores/thumbnail_store.go
+++ b/src/github.com/turt2live/matrix-media-repo/storage/stores/thumbnail_store.go
@@ -44,7 +44,7 @@ func InitThumbnailStore(sqlDb *sql.DB) (*ThumbnailStoreFactory, error) {
 	return &store, nil
 }
 
-func (f *ThumbnailStoreFactory) Create(ctx context.Context, entry *logrus.Entry) (*ThumbnailStore) {
+func (f *ThumbnailStoreFactory) New(ctx context.Context, entry *logrus.Entry) (*ThumbnailStore) {
 	return &ThumbnailStore{
 		factory:    f,
 		ctx:        ctx,
@@ -70,7 +70,7 @@ func (s *ThumbnailStore) Insert(thumbnail *types.Thumbnail) (error) {
 	return err
 }
 
-func (s *ThumbnailStore) Get(origin string, mediaId string, width int, height int, method string) (types.Thumbnail, error) {
+func (s *ThumbnailStore) Get(origin string, mediaId string, width int, height int, method string) (*types.Thumbnail, error) {
 	t := &types.Thumbnail{}
 	err := s.statements.selectThumbnail.QueryRowContext(s.ctx, origin, mediaId, width, height, method).Scan(
 		&t.Origin,
@@ -83,5 +83,5 @@ func (s *ThumbnailStore) Get(origin string, mediaId string, width int, height in
 		&t.Location,
 		&t.CreationTs,
 	)
-	return *t, err
+	return t, err
 }
diff --git a/src/github.com/turt2live/matrix-media-repo/types/media.go b/src/github.com/turt2live/matrix-media-repo/types/media.go
index 99789bc4..d0e0baad 100644
--- a/src/github.com/turt2live/matrix-media-repo/types/media.go
+++ b/src/github.com/turt2live/matrix-media-repo/types/media.go
@@ -11,3 +11,7 @@ type Media struct {
 	Location    string
 	CreationTs  int64
 }
+
+func (m *Media) MxcUri() string {
+	return "mxc://" + m.Origin + "/" + m.MediaId
+}
diff --git a/src/github.com/turt2live/matrix-media-repo/util/errcodes/errorcodes.go b/src/github.com/turt2live/matrix-media-repo/util/errs/errorcodes.go
similarity index 92%
rename from src/github.com/turt2live/matrix-media-repo/util/errcodes/errorcodes.go
rename to src/github.com/turt2live/matrix-media-repo/util/errs/errorcodes.go
index b40b7a1a..8a2e76b2 100644
--- a/src/github.com/turt2live/matrix-media-repo/util/errcodes/errorcodes.go
+++ b/src/github.com/turt2live/matrix-media-repo/util/errs/errorcodes.go
@@ -1,4 +1,4 @@
-package errcodes
+package errs
 
 const ErrCodeInvalidHost = "M_INVALID_HOST"
 const ErrCodeHostNotFound = "M_HOST_NOT_FOUND"
diff --git a/src/github.com/turt2live/matrix-media-repo/util/errors.go b/src/github.com/turt2live/matrix-media-repo/util/errs/errors.go
similarity index 95%
rename from src/github.com/turt2live/matrix-media-repo/util/errors.go
rename to src/github.com/turt2live/matrix-media-repo/util/errs/errors.go
index c7c98eec..9857438a 100644
--- a/src/github.com/turt2live/matrix-media-repo/util/errors.go
+++ b/src/github.com/turt2live/matrix-media-repo/util/errs/errors.go
@@ -1,4 +1,4 @@
-package util
+package errs
 
 import (
 	"errors"
diff --git a/src/github.com/turt2live/matrix-media-repo/util/mxc.go b/src/github.com/turt2live/matrix-media-repo/util/mxc.go
deleted file mode 100644
index 98c9bdea..00000000
--- a/src/github.com/turt2live/matrix-media-repo/util/mxc.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package util
-
-import "github.com/turt2live/matrix-media-repo/types"
-
-func MediaToMxc(media *types.Media) string {
-	return "mxc://" + media.Origin + "/" + media.MediaId
-}
-- 
GitLab