Split up code into modules.
This commit is contained in:
parent
77d143dbf4
commit
5b24b3380b
100
source/app.d
100
source/app.d
|
@ -8,9 +8,11 @@ import std.getopt;
|
||||||
import filesizes;
|
import filesizes;
|
||||||
import progress;
|
import progress;
|
||||||
|
|
||||||
|
import utils;
|
||||||
|
import ingest;
|
||||||
|
|
||||||
const DEFAULT_OUTPUT_DIR = "raw";
|
const DEFAULT_OUTPUT_DIR = "raw";
|
||||||
const DEFAULT_MEDIA_DIR = "/media";
|
const DEFAULT_MEDIA_DIR = "/media";
|
||||||
const GOPRO_CONTENT_DIR = "DCIM/100GOPRO";
|
|
||||||
const DEFAULT_BUFFER_SIZE = 1024 * 1024;
|
const DEFAULT_BUFFER_SIZE = 1024 * 1024;
|
||||||
|
|
||||||
int main(string[] args) {
|
int main(string[] args) {
|
||||||
|
@ -63,99 +65,3 @@ int main(string[] args) {
|
||||||
writefln!"Found GoPro media at %s."(goProDir);
|
writefln!"Found GoPro media at %s."(goProDir);
|
||||||
return copyFiles(goProDir, outputDir, bufferSize, force, dryRun);
|
return copyFiles(goProDir, outputDir, bufferSize, force, dryRun);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to find a GoPro's media directory.
|
|
||||||
* Params:
|
|
||||||
* baseDir = The base directory to start the search from.
|
|
||||||
* Returns: A nullable string that, if present, refers to the GoPro's media
|
|
||||||
* directory.
|
|
||||||
*/
|
|
||||||
Nullable!string getGoProDir(string baseDir) {
|
|
||||||
if (!exists(baseDir) || !isDir(baseDir)) return Nullable!string.init;
|
|
||||||
foreach (dir; std.file.dirEntries(baseDir, SpanMode.breadth)) {
|
|
||||||
string mediaPath = buildPath(dir.name, GOPRO_CONTENT_DIR);
|
|
||||||
if (exists(mediaPath) && isDir(mediaPath)) {
|
|
||||||
return nullable(mediaPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Nullable!string.init;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if we should copy a given file from the media card to the local
|
|
||||||
* directory.
|
|
||||||
* Params:
|
|
||||||
* entry = The entry for the file at its source.
|
|
||||||
* targetFile = The place to copy the file to.
|
|
||||||
* force = Whether the force flag has been set.
|
|
||||||
* Returns: True if we should copy the file, or false otherwise.
|
|
||||||
*/
|
|
||||||
bool shouldCopyFile(DirEntry entry, string targetFile, bool force) {
|
|
||||||
return entry.isFile() &&
|
|
||||||
(entry.name.endsWith(".MP4") || entry.name.endsWith(".WAV")) &&
|
|
||||||
(!exists(targetFile) || getSize(targetFile) != entry.size || force);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies files from a source to a target directory.
|
|
||||||
* Params:
|
|
||||||
* sourceDir = The source directory.
|
|
||||||
* targetDir = The target directory.
|
|
||||||
* bufferSize = The buffer size to use when copying.
|
|
||||||
* force = Whether to overwrite existing files.
|
|
||||||
* dryRun = Whether to perform a dry-run.
|
|
||||||
* Returns: An exit code.
|
|
||||||
*/
|
|
||||||
int copyFiles(string sourceDir, string targetDir, size_t bufferSize, bool force, bool dryRun) {
|
|
||||||
if (!exists(targetDir) && !dryRun) mkdirRecurse(targetDir);
|
|
||||||
DirEntry[] filesToCopy;
|
|
||||||
ulong totalFileSize = 0;
|
|
||||||
foreach (DirEntry entry; dirEntries(sourceDir, SpanMode.shallow)) {
|
|
||||||
string targetFile = buildPath(targetDir, baseName(entry.name));
|
|
||||||
if (shouldCopyFile(entry, targetFile, force)) {
|
|
||||||
filesToCopy ~= entry;
|
|
||||||
totalFileSize += entry.size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filesToCopy.length == 0) {
|
|
||||||
writeln("No new files to copy.");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getAvailableDiskSpace(targetDir) < totalFileSize) {
|
|
||||||
writefln!"Not enough disk space to copy all files: %s available, %s needed."(
|
|
||||||
formatFilesize(getAvailableDiskSpace(targetDir)),
|
|
||||||
formatFilesize(totalFileSize)
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
writefln!"Copying %d files (%s) to %s."(filesToCopy.length, formatFilesize(totalFileSize), targetDir);
|
|
||||||
if (dryRun) writeln("(Dry Run)");
|
|
||||||
Bar progressBar = new FillingSquaresBar();
|
|
||||||
progressBar.width = 80;
|
|
||||||
progressBar.max = totalFileSize;
|
|
||||||
progressBar.start();
|
|
||||||
ubyte[] buffer = new ubyte[bufferSize];
|
|
||||||
foreach (DirEntry entry; filesToCopy) {
|
|
||||||
string filename = baseName(entry.name);
|
|
||||||
string targetFile = buildPath(targetDir, filename);
|
|
||||||
string verb = exists(targetFile) ? "Overwriting" : "Copying";
|
|
||||||
progressBar.message = { return std.string.format!"%s %s (%s)"(verb, filename, formatFilesize(entry.size)); };
|
|
||||||
if (!dryRun) {
|
|
||||||
File inputFile = File(entry.name, "rb");
|
|
||||||
File outputFile = File(targetFile, "wb");
|
|
||||||
foreach (ubyte[] localBuffer; inputFile.byChunk(buffer)) {
|
|
||||||
outputFile.rawWrite(localBuffer);
|
|
||||||
progressBar.next(localBuffer.length);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
progressBar.next(getSize(entry.name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
progressBar.finish();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
module ingest;
|
||||||
|
|
||||||
|
import std.file;
|
||||||
|
import std.stdio;
|
||||||
|
import std.path;
|
||||||
|
import std.algorithm;
|
||||||
|
import std.string;
|
||||||
|
import std.typecons;
|
||||||
|
import filesizes;
|
||||||
|
import progress;
|
||||||
|
|
||||||
|
private struct IngestData {
|
||||||
|
DirEntry[] filesToCopy;
|
||||||
|
|
||||||
|
ulong totalFileSize() {
|
||||||
|
return filesToCopy.map!(f => f.size).sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fileCount() {
|
||||||
|
return filesToCopy.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if we should copy a given file from the media card to the local
|
||||||
|
* directory.
|
||||||
|
* Params:
|
||||||
|
* entry = The entry for the file at its source.
|
||||||
|
* targetFile = The place to copy the file to.
|
||||||
|
* force = Whether the force flag has been set.
|
||||||
|
* Returns: True if we should copy the file, or false otherwise.
|
||||||
|
*/
|
||||||
|
private bool shouldCopyFile(DirEntry entry, string targetFile, bool force) {
|
||||||
|
return entry.isFile() &&
|
||||||
|
(entry.name.endsWith(".MP4") || entry.name.endsWith(".WAV")) &&
|
||||||
|
(!exists(targetFile) || getSize(targetFile) != entry.size || force);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IngestData discoverIngestData(string sourceDir, string targetDir, bool force) {
|
||||||
|
IngestData data;
|
||||||
|
foreach (DirEntry entry; dirEntries(sourceDir, SpanMode.shallow)) {
|
||||||
|
string targetFile = buildPath(targetDir, baseName(entry.name));
|
||||||
|
if (shouldCopyFile(entry, targetFile, force)) {
|
||||||
|
data.filesToCopy ~= entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies files from a source to a target directory.
|
||||||
|
* Params:
|
||||||
|
* sourceDir = The source directory.
|
||||||
|
* targetDir = The target directory.
|
||||||
|
* bufferSize = The buffer size to use when copying.
|
||||||
|
* force = Whether to overwrite existing files.
|
||||||
|
* dryRun = Whether to perform a dry-run.
|
||||||
|
* Returns: An exit code.
|
||||||
|
*/
|
||||||
|
public int copyFiles(string sourceDir, string targetDir, size_t bufferSize, bool force, bool dryRun) {
|
||||||
|
IngestData ingestData = discoverIngestData(sourceDir, targetDir, force);
|
||||||
|
if (ingestData.fileCount == 0) {
|
||||||
|
writeln("No new files to copy.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (getAvailableDiskSpace(targetDir) < ingestData.totalFileSize) {
|
||||||
|
writefln!"Not enough disk space to copy all files: %s available, %s needed."(
|
||||||
|
formatFilesize(getAvailableDiskSpace(targetDir)),
|
||||||
|
formatFilesize(ingestData.totalFileSize)
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
writefln!"Copying %d files (%s) to %s."(ingestData.fileCount, formatFilesize(ingestData.totalFileSize), targetDir);
|
||||||
|
if (dryRun) writeln("(Dry Run)");
|
||||||
|
|
||||||
|
if (!exists(targetDir) && !dryRun) mkdirRecurse(targetDir);
|
||||||
|
|
||||||
|
Bar progressBar = new FillingSquaresBar();
|
||||||
|
progressBar.width = 80;
|
||||||
|
progressBar.max = ingestData.totalFileSize;
|
||||||
|
progressBar.start();
|
||||||
|
ubyte[] buffer = new ubyte[bufferSize];
|
||||||
|
foreach (DirEntry entry; ingestData.filesToCopy) {
|
||||||
|
string filename = baseName(entry.name);
|
||||||
|
string targetFile = buildPath(targetDir, filename);
|
||||||
|
string verb = exists(targetFile) ? "Overwriting" : "Copying";
|
||||||
|
progressBar.message = { return std.string.format!"%s %s (%s)"(verb, filename, formatFilesize(entry.size)); };
|
||||||
|
if (!dryRun) {
|
||||||
|
File inputFile = File(entry.name, "rb");
|
||||||
|
File outputFile = File(targetFile, "wb");
|
||||||
|
foreach (ubyte[] localBuffer; inputFile.byChunk(buffer)) {
|
||||||
|
outputFile.rawWrite(localBuffer);
|
||||||
|
progressBar.next(localBuffer.length);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
progressBar.next(getSize(entry.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progressBar.finish();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
module utils;
|
||||||
|
|
||||||
|
import std.typecons;
|
||||||
|
import std.file;
|
||||||
|
import std.path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to find a GoPro's media directory.
|
||||||
|
* Params:
|
||||||
|
* baseDir = The base directory to start the search from.
|
||||||
|
* Returns: A nullable string that, if present, refers to the GoPro's media
|
||||||
|
* directory.
|
||||||
|
*/
|
||||||
|
public Nullable!string getGoProDir(string baseDir) {
|
||||||
|
if (!exists(baseDir) || !isDir(baseDir)) return Nullable!string.init;
|
||||||
|
foreach (dir; std.file.dirEntries(baseDir, SpanMode.breadth)) {
|
||||||
|
// We know that a GoPro contains DCIM/100GOPRO in it.
|
||||||
|
string mediaPath = buildPath(dir.name, "DCIM", "100GOPRO");
|
||||||
|
if (exists(mediaPath) && isDir(mediaPath)) {
|
||||||
|
return nullable(mediaPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Nullable!string.init;
|
||||||
|
}
|
Loading…
Reference in New Issue