|
open System |
|
open System.IO |
|
|
|
let cd = __SOURCE_DIRECTORY__ |
|
let path parts = Path.Combine(parts) |
|
let archive = path [|cd; "Archive"|] |
|
let best = path [|cd; "Best"|] |
|
|
|
let isJpg filename = |
|
let ext = Path.GetExtension(filename) |
|
match ext.ToLowerInvariant() with |
|
| ".jpg" | ".jpeg" -> true |
|
| _ -> false |
|
|
|
let isMeta filename = |
|
let ext = Path.GetExtension(filename) |
|
match ext.ToLowerInvariant() with |
|
| ".xmp" | ".bib" -> true |
|
| _ -> false |
|
|
|
let isPhotoRelated filename = |
|
let ext = Path.GetExtension(filename) |
|
match ext.ToLowerInvariant() with |
|
| ".jpg" | ".jpeg" |
|
| ".nef" | ".xmp" | ".bib" -> true |
|
| _ -> false |
|
|
|
let getPhotosToMove folder = |
|
Directory.EnumerateFiles(folder, "*", SearchOption.AllDirectories) |
|
|> Seq.groupBy(Path.GetFileNameWithoutExtension) |
|
|> Seq.map(fun (key, files) -> |
|
key, (files |> Seq.filter isPhotoRelated |> Seq.toList) |
|
) |
|
|> Seq.filter(fun (withoutExt, files) -> |
|
(not (files |> List.forall isJpg)) && |
|
(files |> List.exists isJpg) |
|
) |
|
|> Seq.collect snd |
|
|> Seq.toArray |
|
|
|
type PhotoFolder = |
|
{ FolderName: string |
|
Files: string[] } |
|
|
|
let describePhotoFolder path = |
|
{ FolderName = Path.GetFileName(path) |
|
Files = getPhotosToMove path } |
|
|
|
let describeArchive () = |
|
Directory.GetDirectories(archive) |
|
|> Array.collect ( |
|
Directory.GetDirectories |
|
>> Array.map describePhotoFolder |
|
) |
|
|
|
type FileCopy = |
|
{ Source: string |
|
Destination: string } |
|
|
|
type Duplicate = |
|
{ Filename: string |
|
Paths: string[] } |
|
|
|
type Operation = |
|
| NewDirectory of folderName:string * path:string * files:FileCopy[] |
|
| NewFilesInDirectory of folderName:string * newFiles:FileCopy[] * skippedFiles:FileCopy[] |
|
| NoChangeDirectory of folderName:string |
|
| UnprocessedDirectory of folderName:string |
|
| DuplicateFiles of folderName:string * duplicates:Duplicate[] |
|
|
|
let planFolderOperation destination folder = |
|
let fileCount = folder.Files.Length |
|
let duplicates = |
|
folder.Files |
|
|> Seq.groupBy Path.GetFileName |
|
|> Seq.choose (fun (key, files) -> |
|
let filesArray = Seq.toArray files |
|
if filesArray.Length > 1 then |
|
Some { Filename = key; Paths = filesArray } |
|
else None |
|
) |
|
|> Seq.toArray |
|
if fileCount = 0 then |
|
UnprocessedDirectory folder.FolderName |
|
elif duplicates.Length > 0 then |
|
DuplicateFiles(folder.FolderName, duplicates) |
|
else |
|
let destFolder = path [| destination; folder.FolderName |] |
|
let potentialFileCopies = |
|
folder.Files |
|
|> Array.map (fun file -> |
|
{ Source = file |
|
Destination = path [| destFolder; Path.GetFileName file |] } |
|
) |
|
if not <| Directory.Exists destFolder then |
|
NewDirectory(folder.FolderName, destFolder, potentialFileCopies) |
|
else |
|
let existing, newFileCopies = |
|
potentialFileCopies |
|
|> Array.partition (fun fileCopy -> File.Exists(fileCopy.Destination)) |
|
if newFileCopies.Length = 0 then |
|
NoChangeDirectory folder.FolderName |
|
else |
|
NewFilesInDirectory(folder.FolderName, newFileCopies, existing) |
|
|
|
let planOperations destination folders = |
|
folders |> Array.map (planFolderOperation destination) |
|
|
|
let copyFiles files = |
|
for file in files do |
|
printfn "%s\t-> %s" file.Source file.Destination |
|
File.Copy (file.Source, file.Destination) |
|
|
|
let run () = |
|
let archive = describeArchive () |
|
let operations = planOperations best archive |
|
|
|
for operation in operations do |
|
match operation with |
|
| NewDirectory(folderName, path,fileCopies) -> |
|
printfn "+ %s" folderName |
|
Directory.CreateDirectory path |> ignore |
|
copyFiles fileCopies |
|
| NewFilesInDirectory(folderName, newFiles, existing) -> |
|
printfn "~ %s (%i existing files)" folderName existing.Length |
|
copyFiles newFiles |
|
| NoChangeDirectory(_) -> () |
|
| UnprocessedDirectory(folderName) -> |
|
printfn "TODO: %s" folderName |
|
| DuplicateFiles(folder, files) -> |
|
printfn "Duplicates in %s" folder |
|
|
|
// Not covered: |
|
// Folders with only .NEF files |
|
// Folders with renamed .jpg files |