refactor(driver,dbfs): share GPS parsing and tx-following, name DBFS deps

- Five storage drivers copy-pasted the same parseGpsInfo skeleton, DMS
  arithmetic, and rational-element parser with only format deltas.
  Shared helpers now live in pkg/mediameta (GpsMeta, DMSDecimal,
  GpsRationalElem); each driver keeps only its vendor-specific element
  parser.
- Four dbfs navigators repeated the same FollowTx boilerplate (tx check,
  WithTx swap, revert closure). A generic followTxClients helper now
  owns the mechanics; navigators declare which clients to swap.
- NewDatabaseFS took 14 positional parameters of overlapping types; the
  single call site now populates a named DBFSDependencies struct.
- SettingClient.Gets doc corrected: missing names are omitted, not an
  error.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3582/head
Tomas Dvorak 2 weeks ago
parent ded86c1617
commit 79d2ece4e0

@ -26,7 +26,7 @@ type (
Get(ctx context.Context, name string) (string, error) Get(ctx context.Context, name string) (string, error)
// Set sets a setting value to DB. // Set sets a setting value to DB.
Set(ctx context.Context, settings map[string]string) error Set(ctx context.Context, settings map[string]string) error
// Gets gets multiple setting values from DB, returns error if any setting cannot be found. // Gets gets multiple setting values from DB, returns a map of found settings; names with no matching setting are omitted.
Gets(ctx context.Context, names []string) (map[string]string, error) Gets(ctx context.Context, names []string) (map[string]string, error)
} }
) )

@ -9,9 +9,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/pkg/mediameta" "github.com/cloudreve/Cloudreve/v4/pkg/mediameta"
"github.com/cloudreve/Cloudreve/v4/pkg/request" "github.com/cloudreve/Cloudreve/v4/pkg/request"
"github.com/samber/lo" "github.com/samber/lo"
"math"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -206,78 +204,25 @@ func (handler *Driver) extractMediaInfo(ctx context.Context, path string, opt *u
} }
func parseGpsInfo(imageInfo ImageInfo) []driver.MediaMeta { func parseGpsInfo(imageInfo ImageInfo) []driver.MediaMeta {
latitude := imageInfo["GPSLatitude"] // 31deg 16.26808' return mediameta.GpsMeta(imageInfo["GPSLatitude"].Value, imageInfo["GPSLongitude"].Value,
longitude := imageInfo["GPSLongitude"] // 120deg 42.91039' imageInfo["GPSLatitudeRef"].Value, imageInfo["GPSLongitudeRef"].Value, parseRawGPS)
latRef := imageInfo["GPSLatitudeRef"] // North
lonRef := imageInfo["GPSLongitudeRef"] // East
// Make sure all value exist in map
if latitude.Value == "" || longitude.Value == "" || latRef.Value == "" || lonRef.Value == "" {
return nil
}
lat := parseRawGPS(latitude.Value, latRef.Value)
lon := parseRawGPS(longitude.Value, lonRef.Value)
if !math.IsNaN(lat) && !math.IsNaN(lon) {
lat, lng := mediameta.NormalizeGPS(lat, lon)
return []driver.MediaMeta{{
Key: mediameta.GpsLat,
Value: fmt.Sprintf("%f", lat),
}, {
Key: mediameta.GpsLng,
Value: fmt.Sprintf("%f", lng),
}}
}
return nil
} }
func parseRawGPS(gpsStr string, ref string) float64 { func parseRawGPS(gpsStr string, ref string) float64 {
elem := strings.Split(gpsStr, " ") elem := strings.Split(gpsStr, " ")
if len(elem) < 1 {
return 0
}
var ( var deg, minutes, seconds float64
deg float64 if len(elem) >= 1 {
minutes float64 deg = mediameta.GpsRationalElem(elem[0])
seconds float64 }
)
deg = getGpsElemValue(elem[0])
if len(elem) >= 2 { if len(elem) >= 2 {
minutes = getGpsElemValue(elem[1]) minutes = mediameta.GpsRationalElem(elem[1])
} }
if len(elem) >= 3 { if len(elem) >= 3 {
seconds = getGpsElemValue(elem[2]) seconds = mediameta.GpsRationalElem(elem[2])
}
decimal := deg + minutes/60.0 + seconds/3600.0
if ref == "S" || ref == "W" {
return -decimal
}
return decimal
}
func getGpsElemValue(elm string) float64 {
elements := strings.Split(elm, "/")
if len(elements) != 2 {
return 0
}
numerator, err := strconv.ParseFloat(elements[0], 64)
if err != nil {
return 0
}
denominator, err := strconv.ParseFloat(elements[1], 64)
if err != nil || denominator == 0 {
return 0
} }
return numerator / denominator return mediameta.DMSDecimal(deg, minutes, seconds, ref == "S" || ref == "W")
} }
func handleCosError(resp string, originErr error) error { func handleCosError(resp string, originErr error) error {

@ -4,9 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
@ -62,76 +60,23 @@ func (d *Driver) MediaMeta(ctx context.Context, path, ext, language string) ([]d
} }
func parseGpsInfo(imageInfo map[string]string) []driver.MediaMeta { func parseGpsInfo(imageInfo map[string]string) []driver.MediaMeta {
latitude := imageInfo["GPSLatitude"] // 31/1, 162680820/10000000, 0/1 return mediameta.GpsMeta(imageInfo["GPSLatitude"], imageInfo["GPSLongitude"],
longitude := imageInfo["GPSLongitude"] // 120/1, 429103939/10000000, 0/1 imageInfo["GPSLatitudeRef"], imageInfo["GPSLongitudeRef"], parseRawGPS)
latRef := imageInfo["GPSLatitudeRef"] // N
lonRef := imageInfo["GPSLongitudeRef"] // E
// Make sure all value exist in map
if latitude == "" || longitude == "" || latRef == "" || lonRef == "" {
return nil
}
lat := parseRawGPS(latitude, latRef)
lon := parseRawGPS(longitude, lonRef)
if !math.IsNaN(lat) && !math.IsNaN(lon) {
lat, lng := mediameta.NormalizeGPS(lat, lon)
return []driver.MediaMeta{{
Key: mediameta.GpsLat,
Value: fmt.Sprintf("%f", lat),
}, {
Key: mediameta.GpsLng,
Value: fmt.Sprintf("%f", lng),
}}
}
return nil
} }
func parseRawGPS(gpsStr string, ref string) float64 { func parseRawGPS(gpsStr string, ref string) float64 {
elem := strings.Split(gpsStr, ", ") elem := strings.Split(gpsStr, ", ")
if len(elem) < 1 {
return 0
}
var (
deg float64
minutes float64
seconds float64
)
deg = getGpsElemValue(elem[0]) var deg, minutes, seconds float64
if len(elem) >= 1 {
deg = mediameta.GpsRationalElem(elem[0])
}
if len(elem) >= 2 { if len(elem) >= 2 {
minutes = getGpsElemValue(elem[1]) minutes = mediameta.GpsRationalElem(elem[1])
} }
if len(elem) >= 3 { if len(elem) >= 3 {
seconds = getGpsElemValue(elem[2]) seconds = mediameta.GpsRationalElem(elem[2])
}
decimal := deg + minutes/60.0 + seconds/3600.0
if ref == "S" || ref == "W" {
return -decimal
}
return decimal
}
func getGpsElemValue(elm string) float64 {
elements := strings.Split(elm, "/")
if len(elements) != 2 {
return 0
}
numerator, err := strconv.ParseFloat(elements[0], 64)
if err != nil {
return 0
}
denominator, err := strconv.ParseFloat(elements[1], 64)
if err != nil || denominator == 0 {
return 0
} }
return numerator / denominator return mediameta.DMSDecimal(deg, minutes, seconds, ref == "S" || ref == "W")
} }

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"math"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -292,45 +291,17 @@ func (handler *Driver) extractMediaInfo(ctx context.Context, path string, catego
} }
func parseGpsInfo(imageInfo ImageInfo) []driver.MediaMeta { func parseGpsInfo(imageInfo ImageInfo) []driver.MediaMeta {
latitude := imageInfo["GPSLatitude"] // 31deg 16.26808' return mediameta.GpsMeta(imageInfo["GPSLatitude"].Value, imageInfo["GPSLongitude"].Value,
longitude := imageInfo["GPSLongitude"] // 120deg 42.91039' imageInfo["GPSLatitudeRef"].Value, imageInfo["GPSLongitudeRef"].Value, parseRawGPS)
latRef := imageInfo["GPSLatitudeRef"] // North
lonRef := imageInfo["GPSLongitudeRef"] // East
// Make sure all value exist in map
if latitude.Value == "" || longitude.Value == "" || latRef.Value == "" || lonRef.Value == "" {
return nil
}
lat := parseRawGPS(latitude.Value, latRef.Value)
lon := parseRawGPS(longitude.Value, lonRef.Value)
if !math.IsNaN(lat) && !math.IsNaN(lon) {
lat, lng := mediameta.NormalizeGPS(lat, lon)
return []driver.MediaMeta{{
Key: mediameta.GpsLat,
Value: fmt.Sprintf("%f", lat),
}, {
Key: mediameta.GpsLng,
Value: fmt.Sprintf("%f", lng),
}}
}
return nil
} }
func parseRawGPS(gpsStr string, ref string) float64 { func parseRawGPS(gpsStr string, ref string) float64 {
elem := strings.Split(gpsStr, " ") elem := strings.Split(gpsStr, " ")
if len(elem) < 1 {
return 0
}
var ( var deg, minutes, seconds float64
deg float64 if len(elem) >= 1 {
minutes float64 deg, _ = strconv.ParseFloat(strings.TrimSuffix(elem[0], "deg"), 64)
seconds float64 }
)
deg, _ = strconv.ParseFloat(strings.TrimSuffix(elem[0], "deg"), 64)
if len(elem) >= 2 { if len(elem) >= 2 {
minutes, _ = strconv.ParseFloat(strings.TrimSuffix(elem[1], "'"), 64) minutes, _ = strconv.ParseFloat(strings.TrimSuffix(elem[1], "'"), 64)
} }
@ -338,13 +309,7 @@ func parseRawGPS(gpsStr string, ref string) float64 {
seconds, _ = strconv.ParseFloat(strings.TrimSuffix(elem[2], "\""), 64) seconds, _ = strconv.ParseFloat(strings.TrimSuffix(elem[2], "\""), 64)
} }
decimal := deg + minutes/60.0 + seconds/3600.0 return mediameta.DMSDecimal(deg, minutes, seconds, ref == "South" || ref == "West")
if ref == "South" || ref == "West" {
return -decimal
}
return decimal
} }
func handleOssError(resp string, originErr error) error { func handleOssError(resp string, originErr error) error {

@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -131,45 +130,17 @@ func unmarshalError(resp string, originErr error) error {
} }
func parseGpsInfo(imageInfo ImageInfo) []driver.MediaMeta { func parseGpsInfo(imageInfo ImageInfo) []driver.MediaMeta {
latitude := imageInfo["GPSLatitude"] // 31, 16.2680820, 0 return mediameta.GpsMeta(imageInfo["GPSLatitude"].Value, imageInfo["GPSLongitude"].Value,
longitude := imageInfo["GPSLongitude"] // 120, 42.9103939, 0 imageInfo["GPSLatitudeRef"].Value, imageInfo["GPSLongitudeRef"].Value, parseRawGPS)
latRef := imageInfo["GPSLatitudeRef"] // N
lonRef := imageInfo["GPSLongitudeRef"] // E
// Make sure all value exist in map
if latitude.Value == "" || longitude.Value == "" || latRef.Value == "" || lonRef.Value == "" {
return nil
}
lat := parseRawGPS(latitude.Value, latRef.Value)
lon := parseRawGPS(longitude.Value, lonRef.Value)
if !math.IsNaN(lat) && !math.IsNaN(lon) {
lat, lng := mediameta.NormalizeGPS(lat, lon)
return []driver.MediaMeta{{
Key: mediameta.GpsLat,
Value: fmt.Sprintf("%f", lat),
}, {
Key: mediameta.GpsLng,
Value: fmt.Sprintf("%f", lng),
}}
}
return nil
} }
func parseRawGPS(gpsStr string, ref string) float64 { func parseRawGPS(gpsStr string, ref string) float64 {
elem := strings.Split(gpsStr, ", ") elem := strings.Split(gpsStr, ", ")
if len(elem) < 1 {
return 0
}
var (
deg float64
minutes float64
seconds float64
)
deg, _ = strconv.ParseFloat(elem[0], 64) var deg, minutes, seconds float64
if len(elem) >= 1 {
deg, _ = strconv.ParseFloat(elem[0], 64)
}
if len(elem) >= 2 { if len(elem) >= 2 {
minutes, _ = strconv.ParseFloat(elem[1], 64) minutes, _ = strconv.ParseFloat(elem[1], 64)
} }
@ -177,11 +148,5 @@ func parseRawGPS(gpsStr string, ref string) float64 {
seconds, _ = strconv.ParseFloat(elem[2], 64) seconds, _ = strconv.ParseFloat(elem[2], 64)
} }
decimal := deg + minutes/60.0 + seconds/3600.0 return mediameta.DMSDecimal(deg, minutes, seconds, ref == "S" || ref == "W")
if ref == "S" || ref == "W" {
return -decimal
}
return decimal
} }

@ -8,9 +8,7 @@ import (
"github.com/cloudreve/Cloudreve/v4/pkg/mediameta" "github.com/cloudreve/Cloudreve/v4/pkg/mediameta"
"github.com/cloudreve/Cloudreve/v4/pkg/request" "github.com/cloudreve/Cloudreve/v4/pkg/request"
"github.com/samber/lo" "github.com/samber/lo"
"math"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -77,76 +75,23 @@ func unmarshalError(resp string, err error) error {
} }
func parseGpsInfo(imageInfo map[string]string) []driver.MediaMeta { func parseGpsInfo(imageInfo map[string]string) []driver.MediaMeta {
latitude := imageInfo["GPSLatitude"] // 31/1, 162680820/10000000, 0/1 return mediameta.GpsMeta(imageInfo["GPSLatitude"], imageInfo["GPSLongitude"],
longitude := imageInfo["GPSLongitude"] // 120/1, 429103939/10000000, 0/1 imageInfo["GPSLatitudeRef"], imageInfo["GPSLongitudeRef"], parseRawGPS)
latRef := imageInfo["GPSLatitudeRef"] // N
lonRef := imageInfo["GPSLongitudeRef"] // E
// Make sure all value exist in map
if latitude == "" || longitude == "" || latRef == "" || lonRef == "" {
return nil
}
lat := parseRawGPS(latitude, latRef)
lon := parseRawGPS(longitude, lonRef)
if !math.IsNaN(lat) && !math.IsNaN(lon) {
lat, lng := mediameta.NormalizeGPS(lat, lon)
return []driver.MediaMeta{{
Key: mediameta.GpsLat,
Value: fmt.Sprintf("%f", lat),
}, {
Key: mediameta.GpsLng,
Value: fmt.Sprintf("%f", lng),
}}
}
return nil
} }
func parseRawGPS(gpsStr string, ref string) float64 { func parseRawGPS(gpsStr string, ref string) float64 {
elem := strings.Split(gpsStr, ",") elem := strings.Split(gpsStr, ",")
if len(elem) < 1 {
return 0
}
var ( var deg, minutes, seconds float64
deg float64 if len(elem) >= 1 {
minutes float64 deg = mediameta.GpsRationalElem(elem[0])
seconds float64 }
)
deg = getGpsElemValue(elem[0])
if len(elem) >= 2 { if len(elem) >= 2 {
minutes = getGpsElemValue(elem[1]) minutes = mediameta.GpsRationalElem(elem[1])
} }
if len(elem) >= 3 { if len(elem) >= 3 {
seconds = getGpsElemValue(elem[2]) seconds = mediameta.GpsRationalElem(elem[2])
}
decimal := deg + minutes/60.0 + seconds/3600.0
if ref == "S" || ref == "W" {
return -decimal
}
return decimal
}
func getGpsElemValue(elm string) float64 {
elements := strings.Split(elm, "/")
if len(elements) != 2 {
return 0
}
numerator, err := strconv.ParseFloat(elements[0], 64)
if err != nil {
return 0
}
denominator, err := strconv.ParseFloat(elements[1], 64)
if err != nil || denominator == 0 {
return 0
} }
return numerator / denominator return mediameta.DMSDecimal(deg, minutes, seconds, ref == "S" || ref == "W")
} }

@ -93,26 +93,41 @@ func (f *DBFS) writePermitted(file *File, capability NavigatorCapability) bool {
return caps != nil && caps.Enabled(int(capability)) return caps != nil && caps.Enabled(int(capability))
} }
func NewDatabaseFS(u *ent.User, fileClient inventory.FileClient, shareClient inventory.ShareClient, // DBFSDependencies groups the collaborators wired into a DBFS instance so the
l logging.Logger, ls lock.LockSystem, settingClient setting.Provider, // constructor call site names each dependency.
storagePolicyClient inventory.StoragePolicyClient, hasher hashid.Encoder, userClient inventory.UserClient, type DBFSDependencies struct {
cache, stateKv cache.Driver, directLinkClient inventory.DirectLinkClient, encryptorFactory encrypt.CryptorFactory, eventHub eventhub.EventHub) fs.FileSystem { FileClient inventory.FileClient
ShareClient inventory.ShareClient
UserClient inventory.UserClient
StoragePolicyClient inventory.StoragePolicyClient
DirectLinkClient inventory.DirectLinkClient
Logger logging.Logger
LockSystem lock.LockSystem
SettingProvider setting.Provider
Hasher hashid.Encoder
Cache cache.Driver
StateKV cache.Driver
EncryptorFactory encrypt.CryptorFactory
EventHub eventhub.EventHub
}
func NewDatabaseFS(u *ent.User, deps DBFSDependencies) fs.FileSystem {
return &DBFS{ return &DBFS{
user: u, user: u,
navigators: make(map[string]Navigator), navigators: make(map[string]Navigator),
fileClient: fileClient, fileClient: deps.FileClient,
shareClient: shareClient, shareClient: deps.ShareClient,
l: l, l: deps.Logger,
ls: ls, ls: deps.LockSystem,
settingClient: settingClient, settingClient: deps.SettingProvider,
storagePolicyClient: storagePolicyClient, storagePolicyClient: deps.StoragePolicyClient,
hasher: hasher, hasher: deps.Hasher,
userClient: userClient, userClient: deps.UserClient,
cache: cache, cache: deps.Cache,
stateKv: stateKv, stateKv: deps.StateKV,
directLinkClient: directLinkClient, directLinkClient: deps.DirectLinkClient,
encryptorFactory: encryptorFactory, encryptorFactory: deps.EncryptorFactory,
eventHub: eventHub, eventHub: deps.EventHub,
} }
} }

@ -153,27 +153,24 @@ func (n *myNavigator) Walk(ctx context.Context, levelFiles []*File, limit, depth
} }
func (n *myNavigator) FollowTx(ctx context.Context) (func(), error) { func (n *myNavigator) FollowTx(ctx context.Context) (func(), error) {
if _, ok := ctx.Value(inventory.TxCtx{}).(*inventory.Tx); !ok { oldBase := n.baseNavigator.fileClient
return nil, fmt.Errorf("navigator: no inherited transaction found in context") revertFile, err := followTxClients(ctx, &n.fileClient)
}
newFileClient, _, _, err := inventory.WithTx(ctx, n.fileClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }
newUserClient, _, _, err := inventory.WithTx(ctx, n.userClient) revertUser, err := followTxClients(ctx, &n.userClient)
if err != nil {
oldFileClient, oldUserClient := n.fileClient, n.userClient revertFile()
revert := func() { return nil, err
n.fileClient = oldFileClient
n.userClient = oldUserClient
n.baseNavigator.fileClient = oldFileClient
} }
n.fileClient = newFileClient n.baseNavigator.fileClient = n.fileClient
n.userClient = newUserClient return func() {
n.baseNavigator.fileClient = newFileClient revertUser()
return revert, nil revertFile()
n.baseNavigator.fileClient = oldBase
}, nil
} }
func (n *myNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error { func (n *myNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error {

@ -19,6 +19,31 @@ import (
"github.com/samber/lo" "github.com/samber/lo"
) )
// followTxClients swaps each client field to its tx-bound instance from the
// transaction in ctx, returning a revert closure that restores the originals.
func followTxClients[T inventory.TxOperator](ctx context.Context, fields ...*T) (func(), error) {
if _, ok := ctx.Value(inventory.TxCtx{}).(*inventory.Tx); !ok {
return nil, fmt.Errorf("navigator: no inherited transaction found in context")
}
olds := make([]T, len(fields))
for i, f := range fields {
newClient, _, _, err := inventory.WithTx(ctx, *f)
if err != nil {
return nil, err
}
olds[i] = *f
*f = newClient
}
return func() {
for i, f := range fields {
*f = olds[i]
}
}, nil
}
var ( var (
ErrFsNotInitialized = fmt.Errorf("fs not initialized") ErrFsNotInitialized = fmt.Errorf("fs not initialized")
ErrPermissionDenied = serializer.NewError(serializer.CodeNoPermissionErr, "Permission denied", nil) ErrPermissionDenied = serializer.NewError(serializer.CodeNoPermissionErr, "Permission denied", nil)

@ -351,27 +351,24 @@ func (n *shareNavigator) shareCapabilities() *boolset.BooleanSet {
} }
func (n *shareNavigator) FollowTx(ctx context.Context) (func(), error) { func (n *shareNavigator) FollowTx(ctx context.Context) (func(), error) {
if _, ok := ctx.Value(inventory.TxCtx{}).(*inventory.Tx); !ok { oldBase := n.baseNavigator.fileClient
return nil, fmt.Errorf("navigator: no inherited transaction found in context") revertFile, err := followTxClients(ctx, &n.fileClient)
}
newFileClient, _, _, err := inventory.WithTx(ctx, n.fileClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }
newSharClient, _, _, err := inventory.WithTx(ctx, n.shareClient) revertShare, err := followTxClients(ctx, &n.shareClient)
if err != nil {
oldFileClient, oldShareClient := n.fileClient, n.shareClient revertFile()
revert := func() { return nil, err
n.fileClient = oldFileClient
n.shareClient = oldShareClient
n.baseNavigator.fileClient = oldFileClient
} }
n.fileClient = newFileClient n.baseNavigator.fileClient = n.fileClient
n.shareClient = newSharClient return func() {
n.baseNavigator.fileClient = newFileClient revertShare()
return revert, nil revertFile()
n.baseNavigator.fileClient = oldBase
}, nil
} }
func (n *shareNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error { func (n *shareNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error {

@ -120,23 +120,17 @@ func (t *sharedWithMeNavigator) Walk(ctx context.Context, levelFiles []*File, li
} }
func (n *sharedWithMeNavigator) FollowTx(ctx context.Context) (func(), error) { func (n *sharedWithMeNavigator) FollowTx(ctx context.Context) (func(), error) {
if _, ok := ctx.Value(inventory.TxCtx{}).(*inventory.Tx); !ok { oldBase := n.baseNavigator.fileClient
return nil, fmt.Errorf("navigator: no inherited transaction found in context") revert, err := followTxClients(ctx, &n.fileClient)
}
newFileClient, _, _, err := inventory.WithTx(ctx, n.fileClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }
oldFileClient := n.fileClient n.baseNavigator.fileClient = n.fileClient
revert := func() { return func() {
n.fileClient = oldFileClient revert()
n.baseNavigator.fileClient = oldFileClient n.baseNavigator.fileClient = oldBase
} }, nil
n.fileClient = newFileClient
n.baseNavigator.fileClient = newFileClient
return revert, nil
} }
func (n *sharedWithMeNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error { func (n *sharedWithMeNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error {

@ -135,23 +135,17 @@ func (t *trashNavigator) Walk(ctx context.Context, levelFiles []*File, limit, de
} }
func (n *trashNavigator) FollowTx(ctx context.Context) (func(), error) { func (n *trashNavigator) FollowTx(ctx context.Context) (func(), error) {
if _, ok := ctx.Value(inventory.TxCtx{}).(*inventory.Tx); !ok { oldBase := n.baseNavigator.fileClient
return nil, fmt.Errorf("navigator: no inherited transaction found in context") revert, err := followTxClients(ctx, &n.fileClient)
}
newFileClient, _, _, err := inventory.WithTx(ctx, n.fileClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }
oldFileClient := n.fileClient n.baseNavigator.fileClient = n.fileClient
revert := func() { return func() {
n.fileClient = oldFileClient revert()
n.baseNavigator.fileClient = oldFileClient n.baseNavigator.fileClient = oldBase
} }, nil
n.fileClient = newFileClient
n.baseNavigator.fileClient = newFileClient
return revert, nil
} }
func (n *trashNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error { func (n *trashNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error {

@ -165,9 +165,21 @@ func NewFileManager(dep dependency.Dep, u *ent.User) FileManager {
l: dep.Logger(), l: dep.Logger(),
user: u, user: u,
settings: dep.SettingProvider(), settings: dep.SettingProvider(),
fs: dbfs.NewDatabaseFS(u, dep.FileClient(), dep.ShareClient(), dep.Logger(), dep.LockSystem(), fs: dbfs.NewDatabaseFS(u, dbfs.DBFSDependencies{
dep.SettingProvider(), dep.StoragePolicyClient(), dep.HashIDEncoder(), dep.UserClient(), dep.KV(), dep.NavigatorStateKV(), FileClient: dep.FileClient(),
dep.DirectLinkClient(), dep.EncryptorFactory(context.TODO()), dep.EventHub()), ShareClient: dep.ShareClient(),
UserClient: dep.UserClient(),
StoragePolicyClient: dep.StoragePolicyClient(),
DirectLinkClient: dep.DirectLinkClient(),
Logger: dep.Logger(),
LockSystem: dep.LockSystem(),
SettingProvider: dep.SettingProvider(),
Hasher: dep.HashIDEncoder(),
Cache: dep.KV(),
StateKV: dep.NavigatorStateKV(),
EncryptorFactory: dep.EncryptorFactory(context.TODO()),
EventHub: dep.EventHub(),
}),
kv: dep.KV(), kv: dep.KV(),
config: config, config: config,
auth: dep.GeneralAuth(), auth: dep.GeneralAuth(),

@ -0,0 +1,65 @@
package mediameta
import (
"fmt"
"math"
"strconv"
"strings"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver"
)
// DMSDecimal converts degree/minute/second GPS components to a signed
// decimal degree; negative flips the sign for south/west references.
func DMSDecimal(deg, minutes, seconds float64, negative bool) float64 {
decimal := deg + minutes/60.0 + seconds/3600.0
if negative {
return -decimal
}
return decimal
}
// GpsRationalElem parses one rational GPS element of the form "n/d" into a
// decimal value. Returns 0 for malformed or zero-denominator elements.
func GpsRationalElem(elm string) float64 {
elements := strings.Split(elm, "/")
if len(elements) != 2 {
return 0
}
numerator, err := strconv.ParseFloat(elements[0], 64)
if err != nil {
return 0
}
denominator, err := strconv.ParseFloat(elements[1], 64)
if err != nil || denominator == 0 {
return 0
}
return numerator / denominator
}
// GpsMeta builds the latitude/longitude MediaMeta pair from raw EXIF parts.
// parse converts a raw coordinate string and its hemisphere reference into
// decimal degrees; drivers pass their vendor-specific parser.
func GpsMeta(latitude, longitude, latRef, lonRef string, parse func(raw, ref string) float64) []driver.MediaMeta {
if latitude == "" || longitude == "" || latRef == "" || lonRef == "" {
return nil
}
lat := parse(latitude, latRef)
lon := parse(longitude, lonRef)
if math.IsNaN(lat) || math.IsNaN(lon) {
return nil
}
latN, lngN := NormalizeGPS(lat, lon)
return []driver.MediaMeta{{
Key: GpsLat,
Value: fmt.Sprintf("%f", latN),
}, {
Key: GpsLng,
Value: fmt.Sprintf("%f", lngN),
}}
}
Loading…
Cancel
Save