mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 00:32:25 -05:00 
			
		
		
		
	test the media manager a bit, add shutdown logic
This commit is contained in:
		
					parent
					
						
							
								0ef478584c
							
						
					
				
			
			
				commit
				
					
						e0f9323b9a
					
				
			
		
					 37 changed files with 688 additions and 354 deletions
				
			
		|  | @ -105,7 +105,7 @@ var Start action.GTSAction = func(ctx context.Context) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// build backend handlers | 	// build backend handlers | ||||||
| 	mediaManager, err := media.New(dbService, storage) | 	mediaManager, err := media.NewManager(dbService, storage) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("error creating media manager: %s", err) | 		return fmt.Errorf("error creating media manager: %s", err) | ||||||
| 	} | 	} | ||||||
|  | @ -203,7 +203,7 @@ var Start action.GTSAction = func(ctx context.Context) error { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	gts, err := gotosocial.NewServer(dbService, router, federator) | 	gts, err := gotosocial.NewServer(dbService, router, federator, mediaManager) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("error creating gotosocial service: %s", err) | 		return fmt.Errorf("error creating gotosocial service: %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -80,11 +80,12 @@ var Start action.GTSAction = func(ctx context.Context) error { | ||||||
| 			Body:       r, | 			Body:       r, | ||||||
| 		}, nil | 		}, nil | ||||||
| 	}), dbService) | 	}), dbService) | ||||||
| 	federator := testrig.NewTestFederator(dbService, transportController, storageBackend) | 	mediaManager := testrig.NewTestMediaManager(dbService, storageBackend) | ||||||
|  | 	federator := testrig.NewTestFederator(dbService, transportController, storageBackend, mediaManager) | ||||||
| 
 | 
 | ||||||
| 	emailSender := testrig.NewEmailSender("./web/template/", nil) | 	emailSender := testrig.NewEmailSender("./web/template/", nil) | ||||||
| 
 | 
 | ||||||
| 	processor := testrig.NewTestProcessor(dbService, storageBackend, federator, emailSender) | 	processor := testrig.NewTestProcessor(dbService, storageBackend, federator, emailSender, mediaManager) | ||||||
| 	if err := processor.Start(ctx); err != nil { | 	if err := processor.Start(ctx); err != nil { | ||||||
| 		return fmt.Errorf("error starting processor: %s", err) | 		return fmt.Errorf("error starting processor: %s", err) | ||||||
| 	} | 	} | ||||||
|  | @ -156,7 +157,7 @@ var Start action.GTSAction = func(ctx context.Context) error { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	gts, err := gotosocial.NewServer(dbService, router, federator) | 	gts, err := gotosocial.NewServer(dbService, router, federator, mediaManager) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("error creating gotosocial service: %s", err) | 		return fmt.Errorf("error creating gotosocial service: %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/email" | 	"github.com/superseriousbusiness/gotosocial/internal/email" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
|  | @ -28,6 +29,7 @@ type AccountStandardTestSuite struct { | ||||||
| 	db           db.DB | 	db           db.DB | ||||||
| 	tc           typeutils.TypeConverter | 	tc           typeutils.TypeConverter | ||||||
| 	storage      *kv.KVStore | 	storage      *kv.KVStore | ||||||
|  | 	mediaManager media.Manager | ||||||
| 	federator    federation.Federator | 	federator    federation.Federator | ||||||
| 	processor    processing.Processor | 	processor    processing.Processor | ||||||
| 	emailSender  email.Sender | 	emailSender  email.Sender | ||||||
|  | @ -61,10 +63,11 @@ func (suite *AccountStandardTestSuite) SetupTest() { | ||||||
| 	suite.db = testrig.NewTestDB() | 	suite.db = testrig.NewTestDB() | ||||||
| 	suite.storage = testrig.NewTestStorage() | 	suite.storage = testrig.NewTestStorage() | ||||||
| 	testrig.InitTestLog() | 	testrig.InitTestLog() | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) | 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
|  | 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage, suite.mediaManager) | ||||||
| 	suite.sentEmails = make(map[string]string) | 	suite.sentEmails = make(map[string]string) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) | 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) | ||||||
| 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) | 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager) | ||||||
| 	suite.accountModule = account.New(suite.processor).(*account.Module) | 	suite.accountModule = account.New(suite.processor).(*account.Module) | ||||||
| 	testrig.StandardDBSetup(suite.db, nil) | 	testrig.StandardDBSetup(suite.db, nil) | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | ||||||
|  |  | ||||||
|  | @ -77,10 +77,10 @@ func (suite *ServeFileTestSuite) SetupSuite() { | ||||||
| 	testrig.InitTestLog() | 	testrig.InitTestLog() | ||||||
| 	suite.db = testrig.NewTestDB() | 	suite.db = testrig.NewTestDB() | ||||||
| 	suite.storage = testrig.NewTestStorage() | 	suite.storage = testrig.NewTestStorage() | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) | 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage, testrig.NewTestMediaManager(suite.db, suite.storage)) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 
 | 
 | ||||||
| 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) | 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, testrig.NewTestMediaManager(suite.db, suite.storage)) | ||||||
| 	suite.tc = testrig.NewTestTypeConverter(suite.db) | 	suite.tc = testrig.NewTestTypeConverter(suite.db) | ||||||
| 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
| 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | ||||||
|  |  | ||||||
|  | @ -33,6 +33,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/email" | 	"github.com/superseriousbusiness/gotosocial/internal/email" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/testrig" | 	"github.com/superseriousbusiness/gotosocial/testrig" | ||||||
|  | @ -42,6 +43,7 @@ type FollowRequestStandardTestSuite struct { | ||||||
| 	suite.Suite | 	suite.Suite | ||||||
| 	db           db.DB | 	db           db.DB | ||||||
| 	storage      *kv.KVStore | 	storage      *kv.KVStore | ||||||
|  | 	mediaManager media.Manager | ||||||
| 	federator    federation.Federator | 	federator    federation.Federator | ||||||
| 	processor    processing.Processor | 	processor    processing.Processor | ||||||
| 	emailSender  email.Sender | 	emailSender  email.Sender | ||||||
|  | @ -74,9 +76,10 @@ func (suite *FollowRequestStandardTestSuite) SetupTest() { | ||||||
| 	testrig.InitTestLog() | 	testrig.InitTestLog() | ||||||
| 	suite.db = testrig.NewTestDB() | 	suite.db = testrig.NewTestDB() | ||||||
| 	suite.storage = testrig.NewTestStorage() | 	suite.storage = testrig.NewTestStorage() | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) | 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
|  | 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage, suite.mediaManager) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) | 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager) | ||||||
| 	suite.followRequestModule = followrequest.New(suite.processor).(*followrequest.Module) | 	suite.followRequestModule = followrequest.New(suite.processor).(*followrequest.Module) | ||||||
| 	testrig.StandardDBSetup(suite.db, nil) | 	testrig.StandardDBSetup(suite.db, nil) | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | ||||||
|  |  | ||||||
|  | @ -51,9 +51,9 @@ type MediaCreateTestSuite struct { | ||||||
| 	suite.Suite | 	suite.Suite | ||||||
| 	db           db.DB | 	db           db.DB | ||||||
| 	storage      *kv.KVStore | 	storage      *kv.KVStore | ||||||
|  | 	mediaManager media.Manager | ||||||
| 	federator    federation.Federator | 	federator    federation.Federator | ||||||
| 	tc           typeutils.TypeConverter | 	tc           typeutils.TypeConverter | ||||||
| 	mediaManager media.Manager |  | ||||||
| 	oauthServer  oauth.Server | 	oauthServer  oauth.Server | ||||||
| 	emailSender  email.Sender | 	emailSender  email.Sender | ||||||
| 	processor    processing.Processor | 	processor    processing.Processor | ||||||
|  | @ -83,9 +83,9 @@ func (suite *MediaCreateTestSuite) SetupSuite() { | ||||||
| 	suite.tc = testrig.NewTestTypeConverter(suite.db) | 	suite.tc = testrig.NewTestTypeConverter(suite.db) | ||||||
| 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
| 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) | 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage, suite.mediaManager) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) | 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager) | ||||||
| 
 | 
 | ||||||
| 	// setup module being tested | 	// setup module being tested | ||||||
| 	suite.mediaModule = mediamodule.New(suite.processor).(*mediamodule.Module) | 	suite.mediaModule = mediamodule.New(suite.processor).(*mediamodule.Module) | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/email" | 	"github.com/superseriousbusiness/gotosocial/internal/email" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/testrig" | 	"github.com/superseriousbusiness/gotosocial/testrig" | ||||||
|  | @ -36,6 +37,7 @@ type StatusStandardTestSuite struct { | ||||||
| 	suite.Suite | 	suite.Suite | ||||||
| 	db           db.DB | 	db           db.DB | ||||||
| 	tc           typeutils.TypeConverter | 	tc           typeutils.TypeConverter | ||||||
|  | 	mediaManager media.Manager | ||||||
| 	federator    federation.Federator | 	federator    federation.Federator | ||||||
| 	emailSender  email.Sender | 	emailSender  email.Sender | ||||||
| 	processor    processing.Processor | 	processor    processing.Processor | ||||||
|  | @ -70,9 +72,10 @@ func (suite *StatusStandardTestSuite) SetupTest() { | ||||||
| 	suite.db = testrig.NewTestDB() | 	suite.db = testrig.NewTestDB() | ||||||
| 	suite.tc = testrig.NewTestTypeConverter(suite.db) | 	suite.tc = testrig.NewTestTypeConverter(suite.db) | ||||||
| 	suite.storage = testrig.NewTestStorage() | 	suite.storage = testrig.NewTestStorage() | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) | 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
|  | 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage, suite.mediaManager) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) | 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager) | ||||||
| 	suite.statusModule = status.New(suite.processor).(*status.Module) | 	suite.statusModule = status.New(suite.processor).(*status.Module) | ||||||
| 	testrig.StandardDBSetup(suite.db, nil) | 	testrig.StandardDBSetup(suite.db, nil) | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/email" | 	"github.com/superseriousbusiness/gotosocial/internal/email" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/testrig" | 	"github.com/superseriousbusiness/gotosocial/testrig" | ||||||
|  | @ -35,6 +36,7 @@ type UserStandardTestSuite struct { | ||||||
| 	suite.Suite | 	suite.Suite | ||||||
| 	db           db.DB | 	db           db.DB | ||||||
| 	tc           typeutils.TypeConverter | 	tc           typeutils.TypeConverter | ||||||
|  | 	mediaManager media.Manager | ||||||
| 	federator    federation.Federator | 	federator    federation.Federator | ||||||
| 	emailSender  email.Sender | 	emailSender  email.Sender | ||||||
| 	processor    processing.Processor | 	processor    processing.Processor | ||||||
|  | @ -62,10 +64,11 @@ func (suite *UserStandardTestSuite) SetupTest() { | ||||||
| 	suite.db = testrig.NewTestDB() | 	suite.db = testrig.NewTestDB() | ||||||
| 	suite.storage = testrig.NewTestStorage() | 	suite.storage = testrig.NewTestStorage() | ||||||
| 	suite.tc = testrig.NewTestTypeConverter(suite.db) | 	suite.tc = testrig.NewTestTypeConverter(suite.db) | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) | 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
|  | 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage, suite.mediaManager) | ||||||
| 	suite.sentEmails = make(map[string]string) | 	suite.sentEmails = make(map[string]string) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) | 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails) | ||||||
| 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) | 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager) | ||||||
| 	suite.userModule = user.New(suite.processor).(*user.Module) | 	suite.userModule = user.New(suite.processor).(*user.Module) | ||||||
| 	testrig.StandardDBSetup(suite.db, suite.testAccounts) | 	testrig.StandardDBSetup(suite.db, suite.testAccounts) | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | ||||||
|  |  | ||||||
|  | @ -84,9 +84,9 @@ func (suite *InboxPostTestSuite) TestPostBlock() { | ||||||
| 	body := bytes.NewReader(bodyJson) | 	body := bytes.NewReader(bodyJson) | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  | @ -184,9 +184,9 @@ func (suite *InboxPostTestSuite) TestPostUnblock() { | ||||||
| 	body := bytes.NewReader(bodyJson) | 	body := bytes.NewReader(bodyJson) | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  | @ -274,9 +274,9 @@ func (suite *InboxPostTestSuite) TestPostUpdate() { | ||||||
| 	body := bytes.NewReader(bodyJson) | 	body := bytes.NewReader(bodyJson) | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  | @ -393,9 +393,9 @@ func (suite *InboxPostTestSuite) TestPostDelete() { | ||||||
| 	body := bytes.NewReader(bodyJson) | 	body := bytes.NewReader(bodyJson) | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	err = processor.Start(context.Background()) | 	err = processor.Start(context.Background()) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
|  |  | ||||||
|  | @ -45,9 +45,9 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() { | ||||||
| 	targetAccount := suite.testAccounts["local_account_1"] | 	targetAccount := suite.testAccounts["local_account_1"] | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  | @ -100,9 +100,9 @@ func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() { | ||||||
| 	targetAccount := suite.testAccounts["local_account_1"] | 	targetAccount := suite.testAccounts["local_account_1"] | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  | @ -155,9 +155,9 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() { | ||||||
| 	targetAccount := suite.testAccounts["local_account_1"] | 	targetAccount := suite.testAccounts["local_account_1"] | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  |  | ||||||
|  | @ -48,9 +48,9 @@ func (suite *RepliesGetTestSuite) TestGetReplies() { | ||||||
| 	targetStatus := suite.testStatuses["local_account_1_status_1"] | 	targetStatus := suite.testStatuses["local_account_1_status_1"] | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  | @ -109,9 +109,9 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() { | ||||||
| 	targetStatus := suite.testStatuses["local_account_1_status_1"] | 	targetStatus := suite.testStatuses["local_account_1_status_1"] | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  | @ -173,9 +173,9 @@ func (suite *RepliesGetTestSuite) TestGetRepliesLast() { | ||||||
| 	targetStatus := suite.testStatuses["local_account_1_status_1"] | 	targetStatus := suite.testStatuses["local_account_1_status_1"] | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/email" | 	"github.com/superseriousbusiness/gotosocial/internal/email" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
|  | @ -38,6 +39,7 @@ type UserStandardTestSuite struct { | ||||||
| 	suite.Suite | 	suite.Suite | ||||||
| 	db             db.DB | 	db             db.DB | ||||||
| 	tc             typeutils.TypeConverter | 	tc             typeutils.TypeConverter | ||||||
|  | 	mediaManager   media.Manager | ||||||
| 	federator      federation.Federator | 	federator      federation.Federator | ||||||
| 	emailSender    email.Sender | 	emailSender    email.Sender | ||||||
| 	processor      processing.Processor | 	processor      processing.Processor | ||||||
|  | @ -77,9 +79,10 @@ func (suite *UserStandardTestSuite) SetupTest() { | ||||||
| 	suite.db = testrig.NewTestDB() | 	suite.db = testrig.NewTestDB() | ||||||
| 	suite.tc = testrig.NewTestTypeConverter(suite.db) | 	suite.tc = testrig.NewTestTypeConverter(suite.db) | ||||||
| 	suite.storage = testrig.NewTestStorage() | 	suite.storage = testrig.NewTestStorage() | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) | 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
|  | 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage, suite.mediaManager) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) | 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager) | ||||||
| 	suite.userModule = user.New(suite.processor).(*user.Module) | 	suite.userModule = user.New(suite.processor).(*user.Module) | ||||||
| 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | ||||||
| 	suite.securityModule = security.New(suite.db, suite.oauthServer).(*security.Module) | 	suite.securityModule = security.New(suite.db, suite.oauthServer).(*security.Module) | ||||||
|  |  | ||||||
|  | @ -46,9 +46,9 @@ func (suite *UserGetTestSuite) TestGetUser() { | ||||||
| 	targetAccount := suite.testAccounts["local_account_1"] | 	targetAccount := suite.testAccounts["local_account_1"] | ||||||
| 
 | 
 | ||||||
| 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | 	tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db) | ||||||
| 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage) | 	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager) | ||||||
| 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | 	emailSender := testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender) | 	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager) | ||||||
| 	userModule := user.New(processor).(*user.Module) | 	userModule := user.New(processor).(*user.Module) | ||||||
| 
 | 
 | ||||||
| 	// setup request | 	// setup request | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/email" | 	"github.com/superseriousbusiness/gotosocial/internal/email" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
|  | @ -43,6 +44,7 @@ type WebfingerStandardTestSuite struct { | ||||||
| 	suite.Suite | 	suite.Suite | ||||||
| 	db             db.DB | 	db             db.DB | ||||||
| 	tc             typeutils.TypeConverter | 	tc             typeutils.TypeConverter | ||||||
|  | 	mediaManager   media.Manager | ||||||
| 	federator      federation.Federator | 	federator      federation.Federator | ||||||
| 	emailSender    email.Sender | 	emailSender    email.Sender | ||||||
| 	processor      processing.Processor | 	processor      processing.Processor | ||||||
|  | @ -80,9 +82,10 @@ func (suite *WebfingerStandardTestSuite) SetupTest() { | ||||||
| 	suite.db = testrig.NewTestDB() | 	suite.db = testrig.NewTestDB() | ||||||
| 	suite.tc = testrig.NewTestTypeConverter(suite.db) | 	suite.tc = testrig.NewTestTypeConverter(suite.db) | ||||||
| 	suite.storage = testrig.NewTestStorage() | 	suite.storage = testrig.NewTestStorage() | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage) | 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
|  | 	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db), suite.storage, suite.mediaManager) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) | ||||||
| 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender) | 	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager) | ||||||
| 	suite.webfingerModule = webfinger.New(suite.processor).(*webfinger.Module) | 	suite.webfingerModule = webfinger.New(suite.processor).(*webfinger.Module) | ||||||
| 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | ||||||
| 	suite.securityModule = security.New(suite.db, suite.oauthServer).(*security.Module) | 	suite.securityModule = security.New(suite.db, suite.oauthServer).(*security.Module) | ||||||
|  |  | ||||||
|  | @ -20,7 +20,6 @@ package bundb | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"database/sql" |  | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | @ -48,13 +47,5 @@ func (q *debugQueryHook) AfterQuery(_ context.Context, event *bun.QueryEvent) { | ||||||
| 		"operation": event.Operation(), | 		"operation": event.Operation(), | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if event.Err != nil && event.Err != sql.ErrNoRows { |  | ||||||
| 		// if there's an error the it'll be handled in the application logic, |  | ||||||
| 		// but we can still debug log it here alongside the query |  | ||||||
| 		l = l.WithField("query", event.Query) |  | ||||||
| 		l.Debug(event.Err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	l.Tracef("[%s] %s", dur, event.Operation()) | 	l.Tracef("[%s] %s", dur, event.Operation()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -119,7 +119,7 @@ func (d *deref) GetRemoteAccount(ctx context.Context, username string, remoteAcc | ||||||
| 	} else { | 	} else { | ||||||
| 		// take the id we already have and do an update | 		// take the id we already have and do an update | ||||||
| 		gtsAccount.ID = maybeAccount.ID | 		gtsAccount.ID = maybeAccount.ID | ||||||
| 
 | aaaaaaaaaaaaaaaaaa | ||||||
| 		if err := d.PopulateAccountFields(ctx, gtsAccount, username, refresh); err != nil { | 		if err := d.PopulateAccountFields(ctx, gtsAccount, username, refresh); err != nil { | ||||||
| 			return nil, new, fmt.Errorf("FullyDereferenceAccount: error populating further account fields: %s", err) | 			return nil, new, fmt.Errorf("FullyDereferenceAccount: error populating further account fields: %s", err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ type Dereferencer interface { | ||||||
| 
 | 
 | ||||||
| 	GetRemoteInstance(ctx context.Context, username string, remoteInstanceURI *url.URL) (*gtsmodel.Instance, error) | 	GetRemoteInstance(ctx context.Context, username string, remoteInstanceURI *url.URL) (*gtsmodel.Instance, error) | ||||||
| 
 | 
 | ||||||
| 	GetRemoteMedia(ctx context.Context, requestingUsername string, accountID string, remoteURL string, ai *media.AdditionalInfo) (*media.Media, error) | 	GetRemoteMedia(ctx context.Context, requestingUsername string, accountID string, remoteURL string, ai *media.AdditionalInfo) (*media.Processing, error) | ||||||
| 
 | 
 | ||||||
| 	DereferenceAnnounce(ctx context.Context, announce *gtsmodel.Status, requestingUsername string) error | 	DereferenceAnnounce(ctx context.Context, announce *gtsmodel.Status, requestingUsername string) error | ||||||
| 	DereferenceThread(ctx context.Context, username string, statusIRI *url.URL) error | 	DereferenceThread(ctx context.Context, username string, statusIRI *url.URL) error | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (d *deref) GetRemoteMedia(ctx context.Context, requestingUsername string, accountID string, remoteURL string, ai *media.AdditionalInfo) (*media.Media, error) { | func (d *deref) GetRemoteMedia(ctx context.Context, requestingUsername string, accountID string, remoteURL string, ai *media.AdditionalInfo) (*media.Processing, error) { | ||||||
| 	if accountID == "" { | 	if accountID == "" { | ||||||
| 		return nil, fmt.Errorf("RefreshAttachment: minAttachment account ID was empty") | 		return nil, fmt.Errorf("RefreshAttachment: minAttachment account ID was empty") | ||||||
| 	} | 	} | ||||||
|  | @ -46,10 +46,10 @@ func (d *deref) GetRemoteMedia(ctx context.Context, requestingUsername string, a | ||||||
| 		return nil, fmt.Errorf("RefreshAttachment: error dereferencing media: %s", err) | 		return nil, fmt.Errorf("RefreshAttachment: error dereferencing media: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	m, err := d.mediaManager.ProcessMedia(ctx, data, accountID, ai) | 	processingMedia, err := d.mediaManager.ProcessMedia(ctx, data, accountID, ai) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("RefreshAttachment: error processing attachment: %s", err) | 		return nil, fmt.Errorf("RefreshAttachment: error processing attachment: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return m, nil | 	return processingMedia, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -53,7 +53,7 @@ func (suite *AttachmentTestSuite) TestDereferenceAttachmentBlocking() { | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 
 | 
 | ||||||
| 	// make a blocking call to load the attachment from the in-process media | 	// make a blocking call to load the attachment from the in-process media | ||||||
| 	attachment, err := media.LoadAttachment(ctx) | 	attachment, err := media.Load(ctx) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 
 | 
 | ||||||
| 	suite.NotNil(attachment) | 	suite.NotNil(attachment) | ||||||
|  |  | ||||||
|  | @ -406,7 +406,7 @@ func (d *deref) populateStatusAttachments(ctx context.Context, status *gtsmodel. | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		attachment, err := media.LoadAttachment(ctx) | 		attachment, err := media.Load(ctx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Errorf("populateStatusAttachments: couldn't load remote attachment %s: %s", a.RemoteURL, err) | 			logrus.Errorf("populateStatusAttachments: couldn't load remote attachment %s: %s", a.RemoteURL, err) | ||||||
| 			continue | 			continue | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/router" | 	"github.com/superseriousbusiness/gotosocial/internal/router" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -41,11 +42,12 @@ type Server interface { | ||||||
| // NewServer returns a new gotosocial server, initialized with the given configuration. | // NewServer returns a new gotosocial server, initialized with the given configuration. | ||||||
| // An error will be returned the caller if something goes wrong during initialization | // An error will be returned the caller if something goes wrong during initialization | ||||||
| // eg., no db or storage connection, port for router already in use, etc. | // eg., no db or storage connection, port for router already in use, etc. | ||||||
| func NewServer(db db.DB, apiRouter router.Router, federator federation.Federator) (Server, error) { | func NewServer(db db.DB, apiRouter router.Router, federator federation.Federator, mediaManager media.Manager) (Server, error) { | ||||||
| 	return &gotosocial{ | 	return &gotosocial{ | ||||||
| 		db:           db, | 		db:           db, | ||||||
| 		apiRouter:    apiRouter, | 		apiRouter:    apiRouter, | ||||||
| 		federator:    federator, | 		federator:    federator, | ||||||
|  | 		mediaManager: mediaManager, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -54,6 +56,7 @@ type gotosocial struct { | ||||||
| 	db           db.DB | 	db           db.DB | ||||||
| 	apiRouter    router.Router | 	apiRouter    router.Router | ||||||
| 	federator    federation.Federator | 	federator    federation.Federator | ||||||
|  | 	mediaManager media.Manager | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Start starts up the gotosocial server. If something goes wrong | // Start starts up the gotosocial server. If something goes wrong | ||||||
|  | @ -63,13 +66,16 @@ func (gts *gotosocial) Start(ctx context.Context) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Stop closes down the gotosocial server, first closing the router | // Stop closes down the gotosocial server, first closing the router, | ||||||
| // then the database. If something goes wrong while stopping, an | // then the media manager, then the database. | ||||||
| // error will be returned. | // If something goes wrong while stopping, an error will be returned. | ||||||
| func (gts *gotosocial) Stop(ctx context.Context) error { | func (gts *gotosocial) Stop(ctx context.Context) error { | ||||||
| 	if err := gts.apiRouter.Stop(ctx); err != nil { | 	if err := gts.apiRouter.Stop(ctx); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	if err := gts.mediaManager.Stop(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	if err := gts.db.Stop(ctx); err != nil { | 	if err := gts.db.Stop(ctx); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -20,16 +20,22 @@ package media | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
|  | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"image" | 	"image" | ||||||
| 	"image/gif" | 	"image/gif" | ||||||
| 	"image/jpeg" | 	"image/jpeg" | ||||||
| 	"image/png" | 	"image/png" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/buckket/go-blurhash" | 	"github.com/buckket/go-blurhash" | ||||||
| 	"github.com/nfnt/resize" | 	"github.com/nfnt/resize" | ||||||
| 	"github.com/superseriousbusiness/exifremove/pkg/exifremove" | 	"github.com/superseriousbusiness/exifremove/pkg/exifremove" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/uris" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | @ -39,7 +45,6 @@ const ( | ||||||
| 
 | 
 | ||||||
| type ImageMeta struct { | type ImageMeta struct { | ||||||
| 	image    []byte | 	image    []byte | ||||||
| 	contentType string |  | ||||||
| 	width    int | 	width    int | ||||||
| 	height   int | 	height   int | ||||||
| 	size     int | 	size     int | ||||||
|  | @ -47,6 +52,113 @@ type ImageMeta struct { | ||||||
| 	blurhash string | 	blurhash string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (m *manager) preProcessImage(ctx context.Context, data []byte, contentType string, accountID string, ai *AdditionalInfo) (*Processing, error) { | ||||||
|  | 	if !supportedImage(contentType) { | ||||||
|  | 		return nil, fmt.Errorf("image type %s not supported", contentType) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(data) == 0 { | ||||||
|  | 		return nil, errors.New("image was of size 0") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	id, err := id.NewRandomULID() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	extension := strings.Split(contentType, "/")[1] | ||||||
|  | 
 | ||||||
|  | 	// populate initial fields on the media attachment -- some of these will be overwritten as we proceed | ||||||
|  | 	attachment := >smodel.MediaAttachment{ | ||||||
|  | 		ID:        id, | ||||||
|  | 		CreatedAt: time.Now(), | ||||||
|  | 		UpdatedAt: time.Now(), | ||||||
|  | 		StatusID:  "", | ||||||
|  | 		URL:       uris.GenerateURIForAttachment(accountID, string(TypeAttachment), string(SizeOriginal), id, extension), | ||||||
|  | 		RemoteURL: "", | ||||||
|  | 		Type:      gtsmodel.FileTypeImage, | ||||||
|  | 		FileMeta: gtsmodel.FileMeta{ | ||||||
|  | 			Focus: gtsmodel.Focus{ | ||||||
|  | 				X: 0, | ||||||
|  | 				Y: 0, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		AccountID:         accountID, | ||||||
|  | 		Description:       "", | ||||||
|  | 		ScheduledStatusID: "", | ||||||
|  | 		Blurhash:          "", | ||||||
|  | 		Processing:        gtsmodel.ProcessingStatusReceived, | ||||||
|  | 		File: gtsmodel.File{ | ||||||
|  | 			Path:        fmt.Sprintf("%s/%s/%s/%s.%s", accountID, TypeAttachment, SizeOriginal, id, extension), | ||||||
|  | 			ContentType: contentType, | ||||||
|  | 			UpdatedAt:   time.Now(), | ||||||
|  | 		}, | ||||||
|  | 		Thumbnail: gtsmodel.Thumbnail{ | ||||||
|  | 			URL:         uris.GenerateURIForAttachment(accountID, string(TypeAttachment), string(SizeSmall), id, mimeJpeg), // all thumbnails are encoded as jpeg, | ||||||
|  | 			Path:        fmt.Sprintf("%s/%s/%s/%s.%s", accountID, TypeAttachment, SizeSmall, id, mimeJpeg),                 // all thumbnails are encoded as jpeg, | ||||||
|  | 			ContentType: mimeJpeg, | ||||||
|  | 			UpdatedAt:   time.Now(), | ||||||
|  | 		}, | ||||||
|  | 		Avatar: false, | ||||||
|  | 		Header: false, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// check if we have additional info to add to the attachment, | ||||||
|  | 	// and overwrite some of the attachment fields if so | ||||||
|  | 	if ai != nil { | ||||||
|  | 		if ai.CreatedAt != nil { | ||||||
|  | 			attachment.CreatedAt = *ai.CreatedAt | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ai.StatusID != nil { | ||||||
|  | 			attachment.StatusID = *ai.StatusID | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ai.RemoteURL != nil { | ||||||
|  | 			attachment.RemoteURL = *ai.RemoteURL | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ai.Description != nil { | ||||||
|  | 			attachment.Description = *ai.Description | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ai.ScheduledStatusID != nil { | ||||||
|  | 			attachment.ScheduledStatusID = *ai.ScheduledStatusID | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ai.Blurhash != nil { | ||||||
|  | 			attachment.Blurhash = *ai.Blurhash | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ai.Avatar != nil { | ||||||
|  | 			attachment.Avatar = *ai.Avatar | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ai.Header != nil { | ||||||
|  | 			attachment.Header = *ai.Header | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ai.FocusX != nil { | ||||||
|  | 			attachment.FileMeta.Focus.X = *ai.FocusX | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ai.FocusY != nil { | ||||||
|  | 			attachment.FileMeta.Focus.Y = *ai.FocusY | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	media := &Processing{ | ||||||
|  | 		attachment:    attachment, | ||||||
|  | 		rawData:       data, | ||||||
|  | 		thumbstate:    received, | ||||||
|  | 		fullSizeState: received, | ||||||
|  | 		database:      m.db, | ||||||
|  | 		storage:       m.storage, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return media, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func decodeGif(b []byte) (*ImageMeta, error) { | func decodeGif(b []byte) (*ImageMeta, error) { | ||||||
| 	gif, err := gif.DecodeAll(bytes.NewReader(b)) | 	gif, err := gif.DecodeAll(bytes.NewReader(b)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -106,8 +218,12 @@ func decodeImage(b []byte, contentType string) (*ImageMeta, error) { | ||||||
| // deriveThumbnail returns a byte slice and metadata for a thumbnail | // deriveThumbnail returns a byte slice and metadata for a thumbnail | ||||||
| // of a given jpeg, png, or gif, or an error if something goes wrong. | // of a given jpeg, png, or gif, or an error if something goes wrong. | ||||||
| // | // | ||||||
| // Note that the aspect ratio of the image will be retained, | // If createBlurhash is true, then a blurhash will also be generated from a tiny | ||||||
| // so it will not necessarily be a square, even if x and y are set as the same value. | // version of the image. This costs precious CPU cycles, so only use it if you | ||||||
|  | // really need a blurhash and don't have one already. | ||||||
|  | // | ||||||
|  | // If createBlurhash is false, then the blurhash field on the returned ImageAndMeta | ||||||
|  | // will be an empty string. | ||||||
| func deriveThumbnail(b []byte, contentType string, createBlurhash bool) (*ImageMeta, error) { | func deriveThumbnail(b []byte, contentType string, createBlurhash bool) (*ImageMeta, error) { | ||||||
| 	var i image.Image | 	var i image.Image | ||||||
| 	var err error | 	var err error | ||||||
|  | @ -115,21 +231,20 @@ func deriveThumbnail(b []byte, contentType string, createBlurhash bool) (*ImageM | ||||||
| 	switch contentType { | 	switch contentType { | ||||||
| 	case mimeImageJpeg: | 	case mimeImageJpeg: | ||||||
| 		i, err = jpeg.Decode(bytes.NewReader(b)) | 		i, err = jpeg.Decode(bytes.NewReader(b)) | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	case mimeImagePng: | 	case mimeImagePng: | ||||||
| 		i, err = png.Decode(bytes.NewReader(b)) | 		i, err = png.Decode(bytes.NewReader(b)) | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	case mimeImageGif: | 	case mimeImageGif: | ||||||
| 		i, err = gif.Decode(bytes.NewReader(b)) | 		i, err = gif.Decode(bytes.NewReader(b)) | ||||||
|  | 	default: | ||||||
|  | 		err = fmt.Errorf("content type %s can't be thumbnailed", contentType) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	default: | 
 | ||||||
| 		return nil, fmt.Errorf("content type %s not recognised", contentType) | 	if i == nil { | ||||||
|  | 		return nil, errors.New("processed image was nil") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	thumb := resize.Thumbnail(thumbnailMaxWidth, thumbnailMaxHeight, i, resize.NearestNeighbor) | 	thumb := resize.Thumbnail(thumbnailMaxWidth, thumbnailMaxHeight, i, resize.NearestNeighbor) | ||||||
|  | @ -146,6 +261,8 @@ func deriveThumbnail(b []byte, contentType string, createBlurhash bool) (*ImageM | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if createBlurhash { | 	if createBlurhash { | ||||||
|  | 		// for generating blurhashes, it's more cost effective to lose detail rather than | ||||||
|  | 		// pass a big image into the blurhash algorithm, so make a teeny tiny version | ||||||
| 		tiny := resize.Thumbnail(32, 32, thumb, resize.NearestNeighbor) | 		tiny := resize.Thumbnail(32, 32, thumb, resize.NearestNeighbor) | ||||||
| 		bh, err := blurhash.Encode(4, 3, tiny) | 		bh, err := blurhash.Encode(4, 3, tiny) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -156,6 +273,7 @@ func deriveThumbnail(b []byte, contentType string, createBlurhash bool) (*ImageM | ||||||
| 
 | 
 | ||||||
| 	out := &bytes.Buffer{} | 	out := &bytes.Buffer{} | ||||||
| 	if err := jpeg.Encode(out, thumb, &jpeg.Options{ | 	if err := jpeg.Encode(out, thumb, &jpeg.Options{ | ||||||
|  | 		// Quality isn't extremely important for thumbnails, so 75 is "good enough" | ||||||
| 		Quality: 75, | 		Quality: 75, | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  |  | ||||||
|  | @ -24,63 +24,84 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"runtime" | 	"runtime" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	"codeberg.org/gruf/go-runners" | 	"codeberg.org/gruf/go-runners" | ||||||
| 	"codeberg.org/gruf/go-store/kv" | 	"codeberg.org/gruf/go-store/kv" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/uris" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Manager provides an interface for managing media: parsing, storing, and retrieving media objects like photos, videos, and gifs. | // Manager provides an interface for managing media: parsing, storing, and retrieving media objects like photos, videos, and gifs. | ||||||
| type Manager interface { | type Manager interface { | ||||||
| 	// ProcessMedia begins the process of decoding and storing the given data as a piece of media (aka an attachment). | 	// ProcessMedia begins the process of decoding and storing the given data as a piece of media (aka an attachment). | ||||||
| 	// It will return a pointer to a Media struct upon which further actions can be performed, such as getting | 	// It will return a pointer to a Media struct upon which further actions can be performed, such as getting | ||||||
| 	// the finished media, thumbnail, decoded bytes, attachment, and setting additional fields. | 	// the finished media, thumbnail, attachment, etc. | ||||||
| 	// | 	// | ||||||
| 	// accountID should be the account that the media belongs to. | 	// accountID should be the account that the media belongs to. | ||||||
| 	// | 	// | ||||||
| 	// RemoteURL is optional, and can be an empty string. Setting this to a non-empty string indicates that | 	// ai is optional and can be nil. Any additional information about the attachment provided will be put in the database. | ||||||
| 	// the piece of media originated on a remote instance and has been dereferenced to be cached locally. | 	ProcessMedia(ctx context.Context, data []byte, accountID string, ai *AdditionalInfo) (*Processing, error) | ||||||
| 	ProcessMedia(ctx context.Context, data []byte, accountID string, ai *AdditionalInfo) (*Media, error) | 	ProcessEmoji(ctx context.Context, data []byte, accountID string) (*Processing, error) | ||||||
| 
 | 	// NumWorkers returns the total number of workers available to this manager. | ||||||
| 	ProcessEmoji(ctx context.Context, data []byte, accountID string) (*Media, error) | 	NumWorkers() int | ||||||
|  | 	// QueueSize returns the total capacity of the queue. | ||||||
|  | 	QueueSize() int | ||||||
|  | 	// JobsQueued returns the number of jobs currently in the task queue. | ||||||
|  | 	JobsQueued() int | ||||||
|  | 	// ActiveWorkers returns the number of workers currently performing jobs. | ||||||
|  | 	ActiveWorkers() int | ||||||
|  | 	// Stop stops the underlying worker pool of the manager. It should be called | ||||||
|  | 	// when closing GoToSocial in order to cleanly finish any in-progress jobs. | ||||||
|  | 	// It will block until workers are finished processing. | ||||||
|  | 	Stop() error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type manager struct { | type manager struct { | ||||||
| 	db         db.DB | 	db         db.DB | ||||||
| 	storage    *kv.KVStore | 	storage    *kv.KVStore | ||||||
| 	pool       runners.WorkerPool | 	pool       runners.WorkerPool | ||||||
|  | 	numWorkers int | ||||||
|  | 	queueSize  int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New returns a media manager with the given db and underlying storage. | // NewManager returns a media manager with the given db and underlying storage. | ||||||
| func New(database db.DB, storage *kv.KVStore) (Manager, error) { | // | ||||||
| 	workers := runtime.NumCPU() / 2 | // A worker pool will also be initialized for the manager, to ensure that only | ||||||
| 	queue := workers * 10 | // a limited number of media will be processed in parallel. | ||||||
| 	pool := runners.NewWorkerPool(workers, queue) | // | ||||||
| 
 | // The number of workers will be the number of CPUs available to the Go runtime, | ||||||
| 	if start := pool.Start(); !start { | // divided by 2 (rounding down, but always at least 1). | ||||||
| 		return nil, errors.New("could not start worker pool") | // | ||||||
|  | // The length of the queue will be the number of workers multiplied by 10. | ||||||
|  | // | ||||||
|  | // So for an 8 core machine, the media manager will get 4 workers, and a queue of length 40. | ||||||
|  | // For a 4 core machine, this will be 2 workers, and a queue length of 20. | ||||||
|  | // For a single or 2-core machine, the media manager will get 1 worker, and a queue of length 10. | ||||||
|  | func NewManager(database db.DB, storage *kv.KVStore) (Manager, error) { | ||||||
|  | 	numWorkers := runtime.NumCPU() / 2 | ||||||
|  | 	// make sure we always have at least 1 worker even on single-core machines | ||||||
|  | 	if numWorkers == 0 { | ||||||
|  | 		numWorkers = 1 | ||||||
| 	} | 	} | ||||||
| 	logrus.Debugf("started media manager worker pool with %d workers and queue capacity of %d", workers, queue) | 	queueSize := numWorkers * 10 | ||||||
| 
 | 
 | ||||||
| 	m := &manager{ | 	m := &manager{ | ||||||
| 		db:         database, | 		db:         database, | ||||||
| 		storage:    storage, | 		storage:    storage, | ||||||
| 		pool:    pool, | 		pool:       runners.NewWorkerPool(numWorkers, queueSize), | ||||||
|  | 		numWorkers: numWorkers, | ||||||
|  | 		queueSize:  queueSize, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if start := m.pool.Start(); !start { | ||||||
|  | 		return nil, errors.New("could not start worker pool") | ||||||
|  | 	} | ||||||
|  | 	logrus.Debugf("started media manager worker pool with %d workers and queue capacity of %d", numWorkers, queueSize) | ||||||
|  | 
 | ||||||
| 	return m, nil | 	return m, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | func (m *manager) ProcessMedia(ctx context.Context, data []byte, accountID string, ai *AdditionalInfo) (*Processing, error) { | ||||||
| 	INTERFACE FUNCTIONS |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| func (m *manager) ProcessMedia(ctx context.Context, data []byte, accountID string, ai *AdditionalInfo) (*Media, error) { |  | ||||||
| 	contentType, err := parseContentType(data) | 	contentType, err := parseContentType(data) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | @ -100,16 +121,20 @@ func (m *manager) ProcessMedia(ctx context.Context, data []byte, accountID strin | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		logrus.Tracef("ProcessMedia: about to enqueue media with attachmentID %s, queue length is %d", media.AttachmentID(), m.pool.Queue()) | ||||||
| 		m.pool.Enqueue(func(innerCtx context.Context) { | 		m.pool.Enqueue(func(innerCtx context.Context) { | ||||||
| 			select { | 			select { | ||||||
| 			case <-innerCtx.Done(): | 			case <-innerCtx.Done(): | ||||||
| 				// if the inner context is done that means the worker pool is closing, so we should just return | 				// if the inner context is done that means the worker pool is closing, so we should just return | ||||||
| 				return | 				return | ||||||
| 			default: | 			default: | ||||||
| 				// start preloading the media for the caller's convenience | 				// start loading the media already for the caller's convenience | ||||||
| 				media.preLoad(innerCtx) | 				if _, err := media.Load(innerCtx); err != nil { | ||||||
|  | 					logrus.Errorf("ProcessMedia: error processing media with attachmentID %s: %s", media.AttachmentID(), err) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
|  | 		logrus.Tracef("ProcessMedia: succesfully queued media with attachmentID %s, queue length is %d", media.AttachmentID(), m.pool.Queue()) | ||||||
| 
 | 
 | ||||||
| 		return media, nil | 		return media, nil | ||||||
| 	default: | 	default: | ||||||
|  | @ -117,112 +142,32 @@ func (m *manager) ProcessMedia(ctx context.Context, data []byte, accountID strin | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *manager) ProcessEmoji(ctx context.Context, data []byte, accountID string) (*Media, error) { | func (m *manager) ProcessEmoji(ctx context.Context, data []byte, accountID string) (*Processing, error) { | ||||||
| 	return nil, nil | 	return nil, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // preProcessImage initializes processing | func (m *manager) NumWorkers() int { | ||||||
| func (m *manager) preProcessImage(ctx context.Context, data []byte, contentType string, accountID string, ai *AdditionalInfo) (*Media, error) { | 	return m.numWorkers | ||||||
| 	if !supportedImage(contentType) { | } | ||||||
| 		return nil, fmt.Errorf("image type %s not supported", contentType) | 
 | ||||||
| 	} | func (m *manager) QueueSize() int { | ||||||
| 
 | 	return m.queueSize | ||||||
| 	if len(data) == 0 { | } | ||||||
| 		return nil, errors.New("image was of size 0") | 
 | ||||||
| 	} | func (m *manager) JobsQueued() int { | ||||||
| 
 | 	return m.pool.Queue() | ||||||
| 	id, err := id.NewRandomULID() | } | ||||||
| 	if err != nil { | 
 | ||||||
| 		return nil, err | func (m *manager) ActiveWorkers() int { | ||||||
| 	} | 	return m.pool.Workers() | ||||||
| 
 | } | ||||||
| 	extension := strings.Split(contentType, "/")[1] | 
 | ||||||
| 
 | func (m *manager) Stop() error { | ||||||
| 	attachment := >smodel.MediaAttachment{ | 	logrus.Info("stopping media manager worker pool") | ||||||
| 		ID:        id, | 
 | ||||||
| 		CreatedAt: time.Now(), | 	stopped := m.pool.Stop() | ||||||
| 		UpdatedAt: time.Now(), | 	if !stopped { | ||||||
| 		StatusID:  "", | 		return errors.New("could not stop media manager worker pool") | ||||||
| 		URL:       uris.GenerateURIForAttachment(accountID, string(TypeAttachment), string(SizeOriginal), id, extension), | 	} | ||||||
| 		RemoteURL: "", | 	return nil | ||||||
| 		Type:      gtsmodel.FileTypeImage, |  | ||||||
| 		FileMeta: gtsmodel.FileMeta{ |  | ||||||
| 			Focus: gtsmodel.Focus{ |  | ||||||
| 				X: 0, |  | ||||||
| 				Y: 0, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		AccountID:         accountID, |  | ||||||
| 		Description:       "", |  | ||||||
| 		ScheduledStatusID: "", |  | ||||||
| 		Blurhash:          "", |  | ||||||
| 		Processing:        0, |  | ||||||
| 		File: gtsmodel.File{ |  | ||||||
| 			Path:        fmt.Sprintf("%s/%s/%s/%s.%s", accountID, TypeAttachment, SizeOriginal, id, extension), |  | ||||||
| 			ContentType: contentType, |  | ||||||
| 			UpdatedAt:   time.Now(), |  | ||||||
| 		}, |  | ||||||
| 		Thumbnail: gtsmodel.Thumbnail{ |  | ||||||
| 			URL:         uris.GenerateURIForAttachment(accountID, string(TypeAttachment), string(SizeSmall), id, mimeJpeg), // all thumbnails are encoded as jpeg, |  | ||||||
| 			Path:        fmt.Sprintf("%s/%s/%s/%s.%s", accountID, TypeAttachment, SizeSmall, id, mimeJpeg),                 // all thumbnails are encoded as jpeg, |  | ||||||
| 			ContentType: mimeJpeg, |  | ||||||
| 			UpdatedAt:   time.Now(), |  | ||||||
| 		}, |  | ||||||
| 		Avatar: false, |  | ||||||
| 		Header: false, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// check if we have additional info to add to the attachment |  | ||||||
| 	if ai != nil { |  | ||||||
| 		if ai.CreatedAt != nil { |  | ||||||
| 			attachment.CreatedAt = *ai.CreatedAt |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ai.StatusID != nil { |  | ||||||
| 			attachment.StatusID = *ai.StatusID |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ai.RemoteURL != nil { |  | ||||||
| 			attachment.RemoteURL = *ai.RemoteURL |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ai.Description != nil { |  | ||||||
| 			attachment.Description = *ai.Description |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ai.ScheduledStatusID != nil { |  | ||||||
| 			attachment.ScheduledStatusID = *ai.ScheduledStatusID |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ai.Blurhash != nil { |  | ||||||
| 			attachment.Blurhash = *ai.Blurhash |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ai.Avatar != nil { |  | ||||||
| 			attachment.Avatar = *ai.Avatar |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ai.Header != nil { |  | ||||||
| 			attachment.Header = *ai.Header |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ai.FocusX != nil { |  | ||||||
| 			attachment.FileMeta.Focus.X = *ai.FocusX |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ai.FocusY != nil { |  | ||||||
| 			attachment.FileMeta.Focus.Y = *ai.FocusY |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	media := &Media{ |  | ||||||
| 		attachment:    attachment, |  | ||||||
| 		rawData:       data, |  | ||||||
| 		thumbstate:    received, |  | ||||||
| 		fullSizeState: received, |  | ||||||
| 		database:      m.db, |  | ||||||
| 		storage:       m.storage, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return media, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -19,36 +19,238 @@ | ||||||
| package media_test | package media_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"codeberg.org/gruf/go-store/kv" | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20211113114307_init" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/testrig" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type MediaManagerStandardTestSuite struct { | type ManagerTestSuite struct { | ||||||
| 	suite.Suite | 	MediaStandardTestSuite | ||||||
| 
 |  | ||||||
| 	db      db.DB |  | ||||||
| 	storage *kv.KVStore |  | ||||||
| 	manager media.Manager |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *MediaManagerStandardTestSuite) SetupSuite() { | func (suite *ManagerTestSuite) TestSimpleJpegProcessBlocking() { | ||||||
| 	testrig.InitTestLog() | 	ctx := context.Background() | ||||||
| 	testrig.InitTestConfig() |  | ||||||
| 
 | 
 | ||||||
| 	suite.db = testrig.NewTestDB() | 	// load bytes from a test image | ||||||
| 	suite.storage = testrig.NewTestStorage() | 	testBytes, err := os.ReadFile("./test/test-jpeg.jpg") | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(testBytes) | ||||||
|  | 
 | ||||||
|  | 	accountID := "01FS1X72SK9ZPW0J1QQ68BD264" | ||||||
|  | 
 | ||||||
|  | 	// process the media with no additional info provided | ||||||
|  | 	processingMedia, err := suite.manager.ProcessMedia(ctx, testBytes, accountID, nil) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	// fetch the attachment id from the processing media | ||||||
|  | 	attachmentID := processingMedia.AttachmentID() | ||||||
|  | 
 | ||||||
|  | 	// do a blocking call to fetch the attachment | ||||||
|  | 	attachment, err := processingMedia.Load(ctx) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotNil(attachment) | ||||||
|  | 
 | ||||||
|  | 	// make sure it's got the stuff set on it that we expect | ||||||
|  | 	// the attachment ID and accountID we expect | ||||||
|  | 	suite.Equal(attachmentID, attachment.ID) | ||||||
|  | 	suite.Equal(accountID, attachment.AccountID) | ||||||
|  | 
 | ||||||
|  | 	// file meta should be correctly derived from the image | ||||||
|  | 	suite.EqualValues(gtsmodel.Original{ | ||||||
|  | 		Width: 1920, Height: 1080, Size: 2073600, Aspect: 1.7777777777777777, | ||||||
|  | 	}, attachment.FileMeta.Original) | ||||||
|  | 	suite.EqualValues(gtsmodel.Small{ | ||||||
|  | 		Width: 512, Height: 288, Size: 147456, Aspect: 1.7777777777777777, | ||||||
|  | 	}, attachment.FileMeta.Small) | ||||||
|  | 	suite.Equal("image/jpeg", attachment.File.ContentType) | ||||||
|  | 	suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachment.Blurhash) | ||||||
|  | 
 | ||||||
|  | 	// now make sure the attachment is in the database | ||||||
|  | 	dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotNil(dbAttachment) | ||||||
|  | 
 | ||||||
|  | 	// make sure the processed file is in storage | ||||||
|  | 	processedFullBytes, err := suite.storage.Get(attachment.File.Path) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(processedFullBytes) | ||||||
|  | 
 | ||||||
|  | 	// load the processed bytes from our test folder, to compare | ||||||
|  | 	processedFullBytesExpected, err := os.ReadFile("./test/test-jpeg-processed.jpg") | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(processedFullBytesExpected) | ||||||
|  | 
 | ||||||
|  | 	// the bytes in storage should be what we expected | ||||||
|  | 	suite.Equal(processedFullBytesExpected, processedFullBytes) | ||||||
|  | 
 | ||||||
|  | 	// now do the same for the thumbnail and make sure it's what we expected | ||||||
|  | 	processedThumbnailBytes, err := suite.storage.Get(attachment.Thumbnail.Path) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(processedThumbnailBytes) | ||||||
|  | 
 | ||||||
|  | 	processedThumbnailBytesExpected, err := os.ReadFile("./test/test-jpeg-thumbnail.jpg") | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(processedThumbnailBytesExpected) | ||||||
|  | 
 | ||||||
|  | 	suite.Equal(processedThumbnailBytesExpected, processedThumbnailBytes) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *MediaManagerStandardTestSuite) SetupTest() { | func (suite *ManagerTestSuite) TestSimpleJpegProcessAsync() { | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../testrig/media") | 	ctx := context.Background() | ||||||
| 	testrig.StandardDBSetup(suite.db, nil) | 
 | ||||||
| 	suite.manager = testrig.NewTestMediaManager(suite.db, suite.storage) | 	// load bytes from a test image | ||||||
|  | 	testBytes, err := os.ReadFile("./test/test-jpeg.jpg") | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(testBytes) | ||||||
|  | 
 | ||||||
|  | 	accountID := "01FS1X72SK9ZPW0J1QQ68BD264" | ||||||
|  | 
 | ||||||
|  | 	// process the media with no additional info provided | ||||||
|  | 	processingMedia, err := suite.manager.ProcessMedia(ctx, testBytes, accountID, nil) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	// fetch the attachment id from the processing media | ||||||
|  | 	attachmentID := processingMedia.AttachmentID() | ||||||
|  | 
 | ||||||
|  | 	// wait for the media to finish processing | ||||||
|  | 	for finished := processingMedia.Finished(); !finished; finished = processingMedia.Finished() { | ||||||
|  | 		time.Sleep(10 * time.Millisecond) | ||||||
|  | 		fmt.Printf("\n\nnot finished yet...\n\n") | ||||||
|  | 	} | ||||||
|  | 	fmt.Printf("\n\nfinished!\n\n") | ||||||
|  | 
 | ||||||
|  | 	// fetch the attachment from the database | ||||||
|  | 	attachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotNil(attachment) | ||||||
|  | 
 | ||||||
|  | 	// make sure it's got the stuff set on it that we expect | ||||||
|  | 	// the attachment ID and accountID we expect | ||||||
|  | 	suite.Equal(attachmentID, attachment.ID) | ||||||
|  | 	suite.Equal(accountID, attachment.AccountID) | ||||||
|  | 
 | ||||||
|  | 	// file meta should be correctly derived from the image | ||||||
|  | 	suite.EqualValues(gtsmodel.Original{ | ||||||
|  | 		Width: 1920, Height: 1080, Size: 2073600, Aspect: 1.7777777777777777, | ||||||
|  | 	}, attachment.FileMeta.Original) | ||||||
|  | 	suite.EqualValues(gtsmodel.Small{ | ||||||
|  | 		Width: 512, Height: 288, Size: 147456, Aspect: 1.7777777777777777, | ||||||
|  | 	}, attachment.FileMeta.Small) | ||||||
|  | 	suite.Equal("image/jpeg", attachment.File.ContentType) | ||||||
|  | 	suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachment.Blurhash) | ||||||
|  | 
 | ||||||
|  | 	// now make sure the attachment is in the database | ||||||
|  | 	dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotNil(dbAttachment) | ||||||
|  | 
 | ||||||
|  | 	// make sure the processed file is in storage | ||||||
|  | 	processedFullBytes, err := suite.storage.Get(attachment.File.Path) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(processedFullBytes) | ||||||
|  | 
 | ||||||
|  | 	// load the processed bytes from our test folder, to compare | ||||||
|  | 	processedFullBytesExpected, err := os.ReadFile("./test/test-jpeg-processed.jpg") | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(processedFullBytesExpected) | ||||||
|  | 
 | ||||||
|  | 	// the bytes in storage should be what we expected | ||||||
|  | 	suite.Equal(processedFullBytesExpected, processedFullBytes) | ||||||
|  | 
 | ||||||
|  | 	// now do the same for the thumbnail and make sure it's what we expected | ||||||
|  | 	processedThumbnailBytes, err := suite.storage.Get(attachment.Thumbnail.Path) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(processedThumbnailBytes) | ||||||
|  | 
 | ||||||
|  | 	processedThumbnailBytesExpected, err := os.ReadFile("./test/test-jpeg-thumbnail.jpg") | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(processedThumbnailBytesExpected) | ||||||
|  | 
 | ||||||
|  | 	suite.Equal(processedThumbnailBytesExpected, processedThumbnailBytes) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *MediaManagerStandardTestSuite) TearDownTest() { | func (suite *ManagerTestSuite) TestSimpleJpegQueueSpamming() { | ||||||
| 	testrig.StandardDBTeardown(suite.db) | 	// in this test, we spam the manager queue with 50 new media requests, just to see how it holds up | ||||||
| 	testrig.StandardStorageTeardown(suite.storage) | 
 | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 
 | ||||||
|  | 	// load bytes from a test image | ||||||
|  | 	testBytes, err := os.ReadFile("./test/test-jpeg.jpg") | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotEmpty(testBytes) | ||||||
|  | 
 | ||||||
|  | 	accountID := "01FS1X72SK9ZPW0J1QQ68BD264" | ||||||
|  | 
 | ||||||
|  | 	spam := 50 | ||||||
|  | 	inProcess := []*media.Processing{} | ||||||
|  | 	for i := 0; i < spam; i++ { | ||||||
|  | 		// process the media with no additional info provided | ||||||
|  | 		processingMedia, err := suite.manager.ProcessMedia(ctx, testBytes, accountID, nil) | ||||||
|  | 		suite.NoError(err) | ||||||
|  | 		inProcess = append(inProcess, processingMedia) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, processingMedia := range inProcess { | ||||||
|  | 		fmt.Printf("\n\n\nactive workers: %d, queue length: %d\n\n\n", suite.manager.ActiveWorkers(), suite.manager.JobsQueued()) | ||||||
|  | 
 | ||||||
|  | 		// fetch the attachment id from the processing media | ||||||
|  | 		attachmentID := processingMedia.AttachmentID() | ||||||
|  | 
 | ||||||
|  | 		// do a blocking call to fetch the attachment | ||||||
|  | 		attachment, err := processingMedia.Load(ctx) | ||||||
|  | 		suite.NoError(err) | ||||||
|  | 		suite.NotNil(attachment) | ||||||
|  | 
 | ||||||
|  | 		// make sure it's got the stuff set on it that we expect | ||||||
|  | 		// the attachment ID and accountID we expect | ||||||
|  | 		suite.Equal(attachmentID, attachment.ID) | ||||||
|  | 		suite.Equal(accountID, attachment.AccountID) | ||||||
|  | 
 | ||||||
|  | 		// file meta should be correctly derived from the image | ||||||
|  | 		suite.EqualValues(gtsmodel.Original{ | ||||||
|  | 			Width: 1920, Height: 1080, Size: 2073600, Aspect: 1.7777777777777777, | ||||||
|  | 		}, attachment.FileMeta.Original) | ||||||
|  | 		suite.EqualValues(gtsmodel.Small{ | ||||||
|  | 			Width: 512, Height: 288, Size: 147456, Aspect: 1.7777777777777777, | ||||||
|  | 		}, attachment.FileMeta.Small) | ||||||
|  | 		suite.Equal("image/jpeg", attachment.File.ContentType) | ||||||
|  | 		suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachment.Blurhash) | ||||||
|  | 
 | ||||||
|  | 		// now make sure the attachment is in the database | ||||||
|  | 		dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) | ||||||
|  | 		suite.NoError(err) | ||||||
|  | 		suite.NotNil(dbAttachment) | ||||||
|  | 
 | ||||||
|  | 		// make sure the processed file is in storage | ||||||
|  | 		processedFullBytes, err := suite.storage.Get(attachment.File.Path) | ||||||
|  | 		suite.NoError(err) | ||||||
|  | 		suite.NotEmpty(processedFullBytes) | ||||||
|  | 
 | ||||||
|  | 		// load the processed bytes from our test folder, to compare | ||||||
|  | 		processedFullBytesExpected, err := os.ReadFile("./test/test-jpeg-processed.jpg") | ||||||
|  | 		suite.NoError(err) | ||||||
|  | 		suite.NotEmpty(processedFullBytesExpected) | ||||||
|  | 
 | ||||||
|  | 		// the bytes in storage should be what we expected | ||||||
|  | 		suite.Equal(processedFullBytesExpected, processedFullBytes) | ||||||
|  | 
 | ||||||
|  | 		// now do the same for the thumbnail and make sure it's what we expected | ||||||
|  | 		processedThumbnailBytes, err := suite.storage.Get(attachment.Thumbnail.Path) | ||||||
|  | 		suite.NoError(err) | ||||||
|  | 		suite.NotEmpty(processedThumbnailBytes) | ||||||
|  | 
 | ||||||
|  | 		processedThumbnailBytesExpected, err := os.ReadFile("./test/test-jpeg-thumbnail.jpg") | ||||||
|  | 		suite.NoError(err) | ||||||
|  | 		suite.NotEmpty(processedThumbnailBytesExpected) | ||||||
|  | 
 | ||||||
|  | 		suite.Equal(processedThumbnailBytesExpected, processedThumbnailBytes) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestManagerTestSuite(t *testing.T) { | ||||||
|  | 	suite.Run(t, &ManagerTestSuite{}) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										54
									
								
								internal/media/media_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								internal/media/media_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | ||||||
|  | /* | ||||||
|  |    GoToSocial | ||||||
|  |    Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org | ||||||
|  | 
 | ||||||
|  |    This program is free software: you can redistribute it and/or modify | ||||||
|  |    it under the terms of the GNU Affero General Public License as published by | ||||||
|  |    the Free Software Foundation, either version 3 of the License, or | ||||||
|  |    (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |    This program is distributed in the hope that it will be useful, | ||||||
|  |    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |    GNU Affero General Public License for more details. | ||||||
|  | 
 | ||||||
|  |    You should have received a copy of the GNU Affero General Public License | ||||||
|  |    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package media_test | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"codeberg.org/gruf/go-store/kv" | ||||||
|  | 	"github.com/stretchr/testify/suite" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/testrig" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type MediaStandardTestSuite struct { | ||||||
|  | 	suite.Suite | ||||||
|  | 
 | ||||||
|  | 	db      db.DB | ||||||
|  | 	storage *kv.KVStore | ||||||
|  | 	manager media.Manager | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *MediaStandardTestSuite) SetupSuite() { | ||||||
|  | 	testrig.InitTestConfig() | ||||||
|  | 	testrig.InitTestLog() | ||||||
|  | 
 | ||||||
|  | 	suite.db = testrig.NewTestDB() | ||||||
|  | 	suite.storage = testrig.NewTestStorage() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *MediaStandardTestSuite) SetupTest() { | ||||||
|  | 	testrig.StandardStorageSetup(suite.storage, "../../testrig/media") | ||||||
|  | 	testrig.StandardDBSetup(suite.db, nil) | ||||||
|  | 	suite.manager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *MediaStandardTestSuite) TearDownTest() { | ||||||
|  | 	testrig.StandardDBTeardown(suite.db) | ||||||
|  | 	testrig.StandardStorageTeardown(suite.storage) | ||||||
|  | } | ||||||
|  | @ -37,7 +37,9 @@ const ( | ||||||
| 	errored                      // processing order has been completed with an error | 	errored                      // processing order has been completed with an error | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Media struct { | // Processing represents a piece of media that is currently being processed. It exposes | ||||||
|  | // various functions for retrieving data from the process. | ||||||
|  | type Processing struct { | ||||||
| 	mu sync.Mutex | 	mu sync.Mutex | ||||||
| 
 | 
 | ||||||
| 	/* | 	/* | ||||||
|  | @ -75,173 +77,169 @@ type Media struct { | ||||||
| 	err error // error created during processing, if any | 	err error // error created during processing, if any | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *Media) Thumb(ctx context.Context) (*ImageMeta, error) { | func (p *Processing) Thumb(ctx context.Context) (*ImageMeta, error) { | ||||||
| 	m.mu.Lock() | 	p.mu.Lock() | ||||||
| 	defer m.mu.Unlock() | 	defer p.mu.Unlock() | ||||||
| 
 | 
 | ||||||
| 	switch m.thumbstate { | 	switch p.thumbstate { | ||||||
| 	case received: | 	case received: | ||||||
| 		// we haven't processed a thumbnail for this media yet so do it now | 		// we haven't processed a thumbnail for this media yet so do it now | ||||||
| 
 | 
 | ||||||
| 		// check if we need to create a blurhash or if there's already one set | 		// check if we need to create a blurhash or if there's already one set | ||||||
| 		var createBlurhash bool | 		var createBlurhash bool | ||||||
| 		if m.attachment.Blurhash == "" { | 		if p.attachment.Blurhash == "" { | ||||||
| 			// no blurhash created yet | 			// no blurhash created yet | ||||||
| 			createBlurhash = true | 			createBlurhash = true | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		thumb, err := deriveThumbnail(m.rawData, m.attachment.File.ContentType, createBlurhash) | 		thumb, err := deriveThumbnail(p.rawData, p.attachment.File.ContentType, createBlurhash) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			m.err = fmt.Errorf("error deriving thumbnail: %s", err) | 			p.err = fmt.Errorf("error deriving thumbnail: %s", err) | ||||||
| 			m.thumbstate = errored | 			p.thumbstate = errored | ||||||
| 			return nil, m.err | 			return nil, p.err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// put the thumbnail in storage | 		// put the thumbnail in storage | ||||||
| 		if err := m.storage.Put(m.attachment.Thumbnail.Path, thumb.image); err != nil { | 		if err := p.storage.Put(p.attachment.Thumbnail.Path, thumb.image); err != nil { | ||||||
| 			m.err = fmt.Errorf("error storing thumbnail: %s", err) | 			p.err = fmt.Errorf("error storing thumbnail: %s", err) | ||||||
| 			m.thumbstate = errored | 			p.thumbstate = errored | ||||||
| 			return nil, m.err | 			return nil, p.err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// set appropriate fields on the attachment based on the thumbnail we derived | 		// set appropriate fields on the attachment based on the thumbnail we derived | ||||||
| 		if createBlurhash { | 		if createBlurhash { | ||||||
| 			m.attachment.Blurhash = thumb.blurhash | 			p.attachment.Blurhash = thumb.blurhash | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		m.attachment.FileMeta.Small = gtsmodel.Small{ | 		p.attachment.FileMeta.Small = gtsmodel.Small{ | ||||||
| 			Width:  thumb.width, | 			Width:  thumb.width, | ||||||
| 			Height: thumb.height, | 			Height: thumb.height, | ||||||
| 			Size:   thumb.size, | 			Size:   thumb.size, | ||||||
| 			Aspect: thumb.aspect, | 			Aspect: thumb.aspect, | ||||||
| 		} | 		} | ||||||
| 		m.attachment.Thumbnail.FileSize = thumb.size | 		p.attachment.Thumbnail.FileSize = thumb.size | ||||||
| 
 | 
 | ||||||
| 		if err := putOrUpdateAttachment(ctx, m.database, m.attachment); err != nil { | 		if err := putOrUpdateAttachment(ctx, p.database, p.attachment); err != nil { | ||||||
| 			m.err = err | 			p.err = err | ||||||
| 			m.thumbstate = errored | 			p.thumbstate = errored | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// set the thumbnail of this media | 		// set the thumbnail of this media | ||||||
| 		m.thumb = thumb | 		p.thumb = thumb | ||||||
| 
 | 
 | ||||||
| 		// we're done processing the thumbnail! | 		// we're done processing the thumbnail! | ||||||
| 		m.thumbstate = complete | 		p.thumbstate = complete | ||||||
| 		fallthrough | 		fallthrough | ||||||
| 	case complete: | 	case complete: | ||||||
| 		return m.thumb, nil | 		return p.thumb, nil | ||||||
| 	case errored: | 	case errored: | ||||||
| 		return nil, m.err | 		return nil, p.err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil, fmt.Errorf("thumbnail processing status %d unknown", m.thumbstate) | 	return nil, fmt.Errorf("thumbnail processing status %d unknown", p.thumbstate) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *Media) FullSize(ctx context.Context) (*ImageMeta, error) { | func (p *Processing) FullSize(ctx context.Context) (*ImageMeta, error) { | ||||||
| 	m.mu.Lock() | 	p.mu.Lock() | ||||||
| 	defer m.mu.Unlock() | 	defer p.mu.Unlock() | ||||||
| 
 | 
 | ||||||
| 	switch m.fullSizeState { | 	switch p.fullSizeState { | ||||||
| 	case received: | 	case received: | ||||||
| 		var clean []byte | 		var clean []byte | ||||||
| 		var err error | 		var err error | ||||||
| 		var decoded *ImageMeta | 		var decoded *ImageMeta | ||||||
| 
 | 
 | ||||||
| 		ct := m.attachment.File.ContentType | 		ct := p.attachment.File.ContentType | ||||||
| 		switch ct { | 		switch ct { | ||||||
| 		case mimeImageJpeg, mimeImagePng: | 		case mimeImageJpeg, mimeImagePng: | ||||||
| 			// first 'clean' image by purging exif data from it | 			// first 'clean' image by purging exif data from it | ||||||
| 			var exifErr error | 			var exifErr error | ||||||
| 			if clean, exifErr = purgeExif(m.rawData); exifErr != nil { | 			if clean, exifErr = purgeExif(p.rawData); exifErr != nil { | ||||||
| 				err = exifErr | 				err = exifErr | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 			decoded, err = decodeImage(clean, ct) | 			decoded, err = decodeImage(clean, ct) | ||||||
| 		case mimeImageGif: | 		case mimeImageGif: | ||||||
| 			// gifs are already clean - no exif data to remove | 			// gifs are already clean - no exif data to remove | ||||||
| 			clean = m.rawData | 			clean = p.rawData | ||||||
| 			decoded, err = decodeGif(clean) | 			decoded, err = decodeGif(clean) | ||||||
| 		default: | 		default: | ||||||
| 			err = fmt.Errorf("content type %s not a processible image type", ct) | 			err = fmt.Errorf("content type %s not a processible image type", ct) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			m.err = err | 			p.err = err | ||||||
| 			m.fullSizeState = errored | 			p.fullSizeState = errored | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// put the full size in storage | 		// put the full size in storage | ||||||
| 		if err := m.storage.Put(m.attachment.File.Path, decoded.image); err != nil { | 		if err := p.storage.Put(p.attachment.File.Path, decoded.image); err != nil { | ||||||
| 			m.err = fmt.Errorf("error storing full size image: %s", err) | 			p.err = fmt.Errorf("error storing full size image: %s", err) | ||||||
| 			m.fullSizeState = errored | 			p.fullSizeState = errored | ||||||
| 			return nil, m.err | 			return nil, p.err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// set appropriate fields on the attachment based on the image we derived | 		// set appropriate fields on the attachment based on the image we derived | ||||||
| 		m.attachment.FileMeta.Original = gtsmodel.Original{ | 		p.attachment.FileMeta.Original = gtsmodel.Original{ | ||||||
| 			Width:  decoded.width, | 			Width:  decoded.width, | ||||||
| 			Height: decoded.height, | 			Height: decoded.height, | ||||||
| 			Size:   decoded.size, | 			Size:   decoded.size, | ||||||
| 			Aspect: decoded.aspect, | 			Aspect: decoded.aspect, | ||||||
| 		} | 		} | ||||||
| 		m.attachment.File.FileSize = decoded.size | 		p.attachment.File.FileSize = decoded.size | ||||||
| 		m.attachment.File.UpdatedAt = time.Now() | 		p.attachment.File.UpdatedAt = time.Now() | ||||||
| 		m.attachment.Processing = gtsmodel.ProcessingStatusProcessed | 		p.attachment.Processing = gtsmodel.ProcessingStatusProcessed | ||||||
| 
 | 
 | ||||||
| 		if err := putOrUpdateAttachment(ctx, m.database, m.attachment); err != nil { | 		if err := putOrUpdateAttachment(ctx, p.database, p.attachment); err != nil { | ||||||
| 			m.err = err | 			p.err = err | ||||||
| 			m.fullSizeState = errored | 			p.fullSizeState = errored | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// set the fullsize of this media | 		// set the fullsize of this media | ||||||
| 		m.fullSize = decoded | 		p.fullSize = decoded | ||||||
| 
 | 
 | ||||||
| 		// we're done processing the full-size image | 		// we're done processing the full-size image | ||||||
| 		m.fullSizeState = complete | 		p.fullSizeState = complete | ||||||
| 		fallthrough | 		fallthrough | ||||||
| 	case complete: | 	case complete: | ||||||
| 		return m.fullSize, nil | 		return p.fullSize, nil | ||||||
| 	case errored: | 	case errored: | ||||||
| 		return nil, m.err | 		return nil, p.err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil, fmt.Errorf("full size processing status %d unknown", m.fullSizeState) | 	return nil, fmt.Errorf("full size processing status %d unknown", p.fullSizeState) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AttachmentID returns the ID of the underlying media attachment without blocking processing. | // AttachmentID returns the ID of the underlying media attachment without blocking processing. | ||||||
| func (m *Media) AttachmentID() string { | func (p *Processing) AttachmentID() string { | ||||||
| 	return m.attachment.ID | 	return p.attachment.ID | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // preLoad begins the process of deriving the thumbnail and encoding the full-size image. | // Load blocks until the thumbnail and fullsize content has been processed, and then | ||||||
| // It does this in a non-blocking way, so you can call it and then come back later and check | // returns the completed attachment. | ||||||
| // if it's finished. | func (p *Processing) Load(ctx context.Context) (*gtsmodel.MediaAttachment, error) { | ||||||
| func (m *Media) preLoad(ctx context.Context) { | 	if _, err := p.Thumb(ctx); err != nil { | ||||||
| 	go m.Thumb(ctx) |  | ||||||
| 	go m.FullSize(ctx) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Load is the blocking equivalent of pre-load. It makes sure the thumbnail and full-size |  | ||||||
| // image have been processed, then it returns the completed attachment. |  | ||||||
| func (m *Media) LoadAttachment(ctx context.Context) (*gtsmodel.MediaAttachment, error) { |  | ||||||
| 	if _, err := m.Thumb(ctx); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if _, err := m.FullSize(ctx); err != nil { | 	if _, err := p.FullSize(ctx); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return m.attachment, nil | 	return p.attachment, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *Media) LoadEmoji(ctx context.Context) (*gtsmodel.Emoji, error) { | func (p *Processing) LoadEmoji(ctx context.Context) (*gtsmodel.Emoji, error) { | ||||||
| 	return nil, nil | 	return nil, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (p *Processing) Finished() bool { | ||||||
|  | 	return p.thumbstate == complete && p.fullSizeState == complete | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // putOrUpdateAttachment is just a convenience function for first trying to PUT the attachment in the database, | // putOrUpdateAttachment is just a convenience function for first trying to PUT the attachment in the database, | ||||||
| // and then if that doesn't work because the attachment already exists, updating it instead. | // and then if that doesn't work because the attachment already exists, updating it instead. | ||||||
| func putOrUpdateAttachment(ctx context.Context, database db.DB, attachment *gtsmodel.MediaAttachment) error { | func putOrUpdateAttachment(ctx context.Context, database db.DB, attachment *gtsmodel.MediaAttachment) error { | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 8.6 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.2 MiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.2 MiB | 
|  | @ -85,7 +85,7 @@ func (suite *AccountStandardTestSuite) SetupTest() { | ||||||
| 	suite.fromClientAPIChan = make(chan messages.FromClientAPI, 100) | 	suite.fromClientAPIChan = make(chan messages.FromClientAPI, 100) | ||||||
| 	suite.httpClient = testrig.NewMockHTTPClient(nil) | 	suite.httpClient = testrig.NewMockHTTPClient(nil) | ||||||
| 	suite.transportController = testrig.NewTestTransportController(suite.httpClient, suite.db) | 	suite.transportController = testrig.NewTestTransportController(suite.httpClient, suite.db) | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, suite.transportController, suite.storage) | 	suite.federator = testrig.NewTestFederator(suite.db, suite.transportController, suite.storage, suite.mediaManager) | ||||||
| 	suite.sentEmails = make(map[string]string) | 	suite.sentEmails = make(map[string]string) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../../web/template/", suite.sentEmails) | 	suite.emailSender = testrig.NewEmailSender("../../../web/template/", suite.sentEmails) | ||||||
| 	suite.accountProcessor = account.New(suite.db, suite.tc, suite.mediaManager, suite.oauthServer, suite.fromClientAPIChan, suite.federator) | 	suite.accountProcessor = account.New(suite.db, suite.tc, suite.mediaManager, suite.oauthServer, suite.fromClientAPIChan, suite.federator) | ||||||
|  |  | ||||||
|  | @ -172,7 +172,7 @@ func (p *processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHead | ||||||
| 		return nil, fmt.Errorf("UpdateAvatar: error processing avatar: %s", err) | 		return nil, fmt.Errorf("UpdateAvatar: error processing avatar: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return processingMedia.LoadAttachment(ctx) | 	return processingMedia.Load(ctx) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // UpdateHeader does the dirty work of checking the header part of an account update form, | // UpdateHeader does the dirty work of checking the header part of an account update form, | ||||||
|  | @ -214,7 +214,7 @@ func (p *processor) UpdateHeader(ctx context.Context, header *multipart.FileHead | ||||||
| 		return nil, fmt.Errorf("UpdateHeader: error processing header: %s", err) | 		return nil, fmt.Errorf("UpdateHeader: error processing header: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return processingMedia.LoadAttachment(ctx) | 	return processingMedia.Load(ctx) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *processor) processNote(ctx context.Context, note string, accountID string) (string, error) { | func (p *processor) processNote(ctx context.Context, note string, accountID string) (string, error) { | ||||||
|  |  | ||||||
|  | @ -60,7 +60,7 @@ func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, form | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	attachment, err := media.LoadAttachment(ctx) | 	attachment, err := media.Load(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -47,11 +47,11 @@ type ProcessingStandardTestSuite struct { | ||||||
| 	suite.Suite | 	suite.Suite | ||||||
| 	db                  db.DB | 	db                  db.DB | ||||||
| 	storage             *kv.KVStore | 	storage             *kv.KVStore | ||||||
|  | 	mediaManager        media.Manager | ||||||
| 	typeconverter       typeutils.TypeConverter | 	typeconverter       typeutils.TypeConverter | ||||||
| 	transportController transport.Controller | 	transportController transport.Controller | ||||||
| 	federator           federation.Federator | 	federator           federation.Federator | ||||||
| 	oauthServer         oauth.Server | 	oauthServer         oauth.Server | ||||||
| 	mediaManager        media.Manager |  | ||||||
| 	timelineManager     timeline.Manager | 	timelineManager     timeline.Manager | ||||||
| 	emailSender         email.Sender | 	emailSender         email.Sender | ||||||
| 
 | 
 | ||||||
|  | @ -216,9 +216,9 @@ func (suite *ProcessingStandardTestSuite) SetupTest() { | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	suite.transportController = testrig.NewTestTransportController(httpClient, suite.db) | 	suite.transportController = testrig.NewTestTransportController(httpClient, suite.db) | ||||||
| 	suite.federator = testrig.NewTestFederator(suite.db, suite.transportController, suite.storage) |  | ||||||
| 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) |  | ||||||
| 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | 	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) | ||||||
|  | 	suite.federator = testrig.NewTestFederator(suite.db, suite.transportController, suite.storage, suite.mediaManager) | ||||||
|  | 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | ||||||
| 	suite.timelineManager = testrig.NewTestTimelineManager(suite.db) | 	suite.timelineManager = testrig.NewTestTimelineManager(suite.db) | ||||||
| 	suite.emailSender = testrig.NewEmailSender("../../web/template/", nil) | 	suite.emailSender = testrig.NewEmailSender("../../web/template/", nil) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -22,10 +22,11 @@ import ( | ||||||
| 	"codeberg.org/gruf/go-store/kv" | 	"codeberg.org/gruf/go-store/kv" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/transport" | 	"github.com/superseriousbusiness/gotosocial/internal/transport" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewTestFederator returns a federator with the given database and (mock!!) transport controller. | // NewTestFederator returns a federator with the given database and (mock!!) transport controller. | ||||||
| func NewTestFederator(db db.DB, tc transport.Controller, storage *kv.KVStore) federation.Federator { | func NewTestFederator(db db.DB, tc transport.Controller, storage *kv.KVStore, mediaManager media.Manager) federation.Federator { | ||||||
| 	return federation.NewFederator(db, NewTestFederatingDB(db), tc, NewTestTypeConverter(db), NewTestMediaManager(db, storage)) | 	return federation.NewFederator(db, NewTestFederatingDB(db), tc, NewTestTypeConverter(db), mediaManager) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| // NewTestMediaManager returns a media handler with the default test config, and the given db and storage. | // NewTestMediaManager returns a media handler with the default test config, and the given db and storage. | ||||||
| func NewTestMediaManager(db db.DB, storage *kv.KVStore) media.Manager { | func NewTestMediaManager(db db.DB, storage *kv.KVStore) media.Manager { | ||||||
| 	m, err := media.New(db, storage) | 	m, err := media.NewManager(db, storage) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -23,10 +23,11 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/email" | 	"github.com/superseriousbusiness/gotosocial/internal/email" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // NewTestProcessor returns a Processor suitable for testing purposes | // NewTestProcessor returns a Processor suitable for testing purposes | ||||||
| func NewTestProcessor(db db.DB, storage *kv.KVStore, federator federation.Federator, emailSender email.Sender) processing.Processor { | func NewTestProcessor(db db.DB, storage *kv.KVStore, federator federation.Federator, emailSender email.Sender, mediaManager media.Manager) processing.Processor { | ||||||
| 	return processing.NewProcessor(NewTestTypeConverter(db), federator, NewTestOauthServer(db), NewTestMediaManager(db, storage), storage, NewTestTimelineManager(db), db, emailSender) | 	return processing.NewProcessor(NewTestTypeConverter(db), federator, NewTestOauthServer(db), mediaManager, storage, NewTestTimelineManager(db), db, emailSender) | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue