-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmediascanner.go
185 lines (149 loc) · 4.61 KB
/
mediascanner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package main
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/bson/imgimporter/workset"
"github.com/rwcarlsen/goexif/exif"
)
/// mediaScanner - the part that scans media and creates a copy work list
type mediaScanner struct {
workset.WorkSet
destDir string
list []copyItem
dirs sync.Map
filesScanned uint32
newFiles uint32
}
// Add path to dir if it doesn't exist
func (m *mediaScanner) checkDir(path string) {
if _, found := m.dirs.Load(path); !found {
if !DirExists(path) {
m.dirs.Store(path, true)
}
}
}
// because path.Base() doesn't work correctly on Windows
func basename(file string) string {
s := strings.Split(file, string(filepath.Separator))
return s[len(s)-1]
}
// Scan media and collect files to copy
func (m *mediaScanner) scan(fileList []string, destDir string, nConc int) ([]copyItem, sync.Map) {
m.destDir = destDir
m.dirs = sync.Map{}
m.list = []copyItem{}
m.filesScanned = 0
m.newFiles = 0
// Convert []string to []interface{}
list := make([]interface{}, len(fileList))
for k, v := range fileList {
list[k] = v
}
m.Work(list, scanConc,
fmt.Sprintf("Scanning %d media files", len(fileList)),
func(fileList []interface{}, start int, len int) {
var localCopyList []copyItem
for i := start; i < start+len; i++ {
atomic.AddUint32(&m.filesScanned, 1)
file := filepath.FromSlash(fileList[i].(string))
// First check using file modification date. If it already exists, we consider it copied.
// This is just a quick check to skip previously copied files. If the file modification date
// differs from the Exif creation date, then we consider the latter authoritative, so
// in this peculiar case we experience a slower media scan rate for these particular files.
fileCreated, err := GetFileModDate(file)
if err != nil {
continue
}
if _, _, copied := m.alreadyCopied(file, fileCreated); copied {
m.Progress()
continue
}
created, err := GetExifCreateDate(file)
if err != nil {
// No valid EXIF, so not a tagged file format
continue
}
if dir, to, copied := m.alreadyCopied(file, created); !copied {
toCopy := copyItem{
from: file,
to: to,
}
localCopyList = append(localCopyList, toCopy)
// See if we need to create the parent directories also
m.checkDir(dir)
atomic.AddUint32(&m.newFiles, 1)
}
// Update progress, if needed
m.Progress()
}
// Finalize by saving results
m.Finalize(func() {
m.list = append(m.list, localCopyList...)
})
},
func() string {
if m.newFiles != 0 {
return fmt.Sprintf("%d/%d - %d new - in %.1fs", m.filesScanned, len(fileList),
m.newFiles, m.Runtime().Seconds())
} else {
return fmt.Sprintf("%d/%d in %.1fs", m.filesScanned, len(fileList),
m.Runtime().Seconds())
}
})
return m.list, m.dirs
}
// Check if file exists given a specific creation date
// Returns destination directory path, destination file name, and whether it exists already
func (m* mediaScanner) alreadyCopied(file string, created time.Time) (string, string, bool) {
dir := fmt.Sprintf("%s/%04d/%04d-%02d-%02d", m.destDir, created.Year(),
created.Year(), created.Month(), created.Day())
ext := strings.ToLower(path.Ext(file))
if subDir, found := subDirByType[ext]; found {
dir += "/" + subDir
}
to := filepath.FromSlash(fmt.Sprintf("%s/%s", dir, basename(file)))
dir = filepath.FromSlash(dir)
return dir, to, FileExists(to)
}
// Get fle creation time of a media file
func GetFileModDate(fname string) (time.Time, error) {
info, err := os.Stat(fname)
if err != nil {
return time.Now(), errors.New(fmt.Sprintf("Unable to stat file: %s", err.Error()))
}
return info.ModTime(), nil
}
// Get creation time of a media file from its EXIF info
func GetExifCreateDate(fname string) (time.Time, error) {
f, err := os.Open(fname)
defer f.Close()
if err != nil {
return time.Now(), errors.New(fmt.Sprintf("Unable to open file: %s", err.Error()))
}
ex, err := exif.Decode(f)
if err != nil {
return time.Now(), errors.New(fmt.Sprintf("Unable to decode EXIF: %s", err.Error()))
}
t, err := ex.DateTime()
if err != nil {
return time.Now(), errors.New(fmt.Sprintf("Unable to obtain EXIF origin time: %s", err.Error()))
}
return t, nil
}
// Check if path exists and is directory
func DirExists(path string) bool {
info, err := os.Stat(path)
return err == nil && info.Mode().IsDir()
}
// Check if path exists and is file
func FileExists(path string) bool {
info, err := os.Stat(path)
return err == nil && (info.Mode()&os.ModeType) == 0
}