-
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #76 from isZumpo/thumbnails
Add thumbnail support
- Loading branch information
Showing
11 changed files
with
602 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package imageprovider | ||
|
||
import ( | ||
"slices" | ||
"sync" | ||
) | ||
|
||
type ImageProvider interface { | ||
fetch(scientificName string) (BirdImage, error) | ||
} | ||
|
||
type BirdImage struct { | ||
Url string | ||
LicenseName string | ||
LicenseUrl string | ||
AuthorName string | ||
AuthorUrl string | ||
} | ||
|
||
type BirdImageCache struct { | ||
dataMap sync.Map | ||
dataMutexMap sync.Map | ||
birdImageProvider ImageProvider | ||
nonBirdImageProvider ImageProvider | ||
} | ||
|
||
type emptyImageProvider struct { | ||
} | ||
|
||
func (l *emptyImageProvider) fetch(scientificName string) (BirdImage, error) { | ||
return BirdImage{}, nil | ||
} | ||
|
||
func initCache(e ImageProvider) *BirdImageCache { | ||
return &BirdImageCache{ | ||
birdImageProvider: e, | ||
nonBirdImageProvider: &emptyImageProvider{}, // TODO: Use a real image provider for non-birds | ||
} | ||
} | ||
|
||
func CreateDefaultCache() (*BirdImageCache, error) { | ||
provider, err := NewWikiMediaProvider() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return initCache(provider), nil | ||
} | ||
|
||
func (c *BirdImageCache) Get(scientificName string) (info BirdImage, err error) { | ||
// Check if the bird image is already in the cache | ||
birdImage, ok := c.dataMap.Load(scientificName) | ||
if ok { | ||
return birdImage.(BirdImage), nil | ||
} | ||
|
||
// Use a per-item mutex to ensure only one query is performed per item | ||
mu, _ := c.dataMutexMap.LoadOrStore(scientificName, &sync.Mutex{}) | ||
mutex := mu.(*sync.Mutex) | ||
|
||
mutex.Lock() | ||
defer mutex.Unlock() | ||
|
||
// Check again if bird image is cached after acquiring the lock | ||
birdImage, ok = c.dataMap.Load(scientificName) | ||
if ok { | ||
return birdImage.(BirdImage), nil | ||
} | ||
|
||
// Fetch the bird image from the image provider | ||
fetchedBirdImage, err := c.fetch(scientificName) | ||
if err != nil { | ||
// TODO for now store a empty result in the cache to avoid future queries that would fail. | ||
// In the future, look at the error and decide if it was caused by networking and is recoverable. | ||
// And if it was, do not store the empty result in the cache. | ||
c.dataMap.Store(scientificName, BirdImage{}) | ||
return BirdImage{}, err | ||
} | ||
|
||
// Store the fetched image information in the cache | ||
c.dataMap.Store(scientificName, fetchedBirdImage) | ||
|
||
return fetchedBirdImage, nil | ||
} | ||
|
||
func (c *BirdImageCache) fetch(scientificName string) (info BirdImage, err error) { | ||
var imageProviderToUse ImageProvider | ||
|
||
// Determine the image provider based on the scientific name | ||
if slices.Contains([]string{ | ||
"Dog", | ||
"Engine", | ||
"Environmental", | ||
"Fireworks", | ||
"Gun", | ||
"Human non-vocal", | ||
"Human vocal", | ||
"Human whistle", | ||
"Noise", | ||
"Power tools", | ||
"Siren", | ||
}, scientificName) { | ||
imageProviderToUse = c.nonBirdImageProvider | ||
} else { | ||
imageProviderToUse = c.birdImageProvider | ||
} | ||
|
||
// Fetch the image from the image provider | ||
return imageProviderToUse.fetch(scientificName) | ||
} |
54 changes: 54 additions & 0 deletions
54
internal/httpcontroller/imageprovider/imageprovider_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package imageprovider | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
type mockProvider struct { | ||
fetchCounter int | ||
} | ||
|
||
func (l *mockProvider) fetch(scientificName string) (BirdImage, error) { | ||
l.fetchCounter++ | ||
return BirdImage{}, nil | ||
} | ||
|
||
// TestBirdImageCache ensures the bird image cache behaves as expected. | ||
// It tests whether the cache correctly handles duplicate requests. | ||
func TestBirdImageCache(t *testing.T) { | ||
// Create a mock image provider and initialize the cache. | ||
mockBirdProvider := &mockProvider{} | ||
mockNonBirdProvider := &mockProvider{} | ||
cache := &BirdImageCache{ | ||
birdImageProvider: mockBirdProvider, | ||
nonBirdImageProvider: mockNonBirdProvider, | ||
} | ||
|
||
entriesToTest := []string{ | ||
"a", | ||
"b", | ||
"a", // Duplicate request | ||
"Human non-vocal", // Non-bird request | ||
} | ||
|
||
for _, entry := range entriesToTest { | ||
_, err := cache.Get(entry) | ||
if err != nil { | ||
t.Errorf("Unexpected error for entry %s: %v", entry, err) | ||
} | ||
} | ||
|
||
// Verify that the bird provider's fetch method was called exactly twice. | ||
expectedBirdFetchCalls := 2 | ||
if mockBirdProvider.fetchCounter != expectedBirdFetchCalls { | ||
t.Errorf("Expected %d calls to bird provider, got %d", | ||
expectedBirdFetchCalls, mockBirdProvider.fetchCounter) | ||
} | ||
|
||
// Verify that the non-bird provider's fetch method was called exactly once. | ||
expectedNonBirdFetchCalls := 1 | ||
if mockNonBirdProvider.fetchCounter != expectedNonBirdFetchCalls { | ||
t.Errorf("Expected %d calls to non-bird provider, got %d", | ||
expectedNonBirdFetchCalls, mockNonBirdProvider.fetchCounter) | ||
} | ||
} |
Oops, something went wrong.