parent
842ef05ee6
commit
007137939e
@ -1,235 +0,0 @@
|
|||||||
/*
|
|
||||||
* This code implements the MD5 message-digest algorithm.
|
|
||||||
* The algorithm is due to Ron Rivest. This code was
|
|
||||||
* written by Colin Plumb in 1993, no copyright is claimed.
|
|
||||||
* This code is in the public domain; do with it what you wish.
|
|
||||||
*
|
|
||||||
* Equivalent code is available from RSA Data Security, Inc.
|
|
||||||
* This code has been tested against that, and is equivalent,
|
|
||||||
* except that you don't need to include two pages of legalese
|
|
||||||
* with every copy.
|
|
||||||
*
|
|
||||||
* To compute the message digest of a chunk of bytes, declare an
|
|
||||||
* MD5_CTX structure, pass it to MD5Init, call MD5Update as
|
|
||||||
* needed on buffers full of bytes, and then call MD5Final, which
|
|
||||||
* will fill a supplied 16-byte array with the digest.
|
|
||||||
*
|
|
||||||
* Changed so as no longer to depend on Colin Plumb's `usual.h' header
|
|
||||||
* definitions; now uses stuff from dpkg's config.h.
|
|
||||||
* - Ian Jackson <ian@chiark.greenend.org.uk>.
|
|
||||||
* Still in the public domain.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "MD5.h"
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
static void
|
|
||||||
byteSwap(UWORD32 *buf, unsigned words)
|
|
||||||
{
|
|
||||||
const uint32_t byteOrderTest = 0x1;
|
|
||||||
if (((char *)&byteOrderTest)[0] == 0) {
|
|
||||||
md5byte *p = (md5byte *)buf;
|
|
||||||
|
|
||||||
do {
|
|
||||||
*buf++ = (UWORD32)((unsigned)p[3] << 8 | p[2]) << 16 |
|
|
||||||
((unsigned)p[1] << 8 | p[0]);
|
|
||||||
p += 4;
|
|
||||||
} while (--words);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
|
|
||||||
* initialization constants.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
MD5Init(struct MD5_CTX *ctx)
|
|
||||||
{
|
|
||||||
ctx->buf[0] = 0x67452301;
|
|
||||||
ctx->buf[1] = 0xefcdab89;
|
|
||||||
ctx->buf[2] = 0x98badcfe;
|
|
||||||
ctx->buf[3] = 0x10325476;
|
|
||||||
|
|
||||||
ctx->bytes[0] = 0;
|
|
||||||
ctx->bytes[1] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Update context to reflect the concatenation of another buffer full
|
|
||||||
* of bytes.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
MD5Update(struct MD5_CTX *ctx, md5byte const *buf, unsigned len)
|
|
||||||
{
|
|
||||||
UWORD32 t;
|
|
||||||
|
|
||||||
/* Update byte count */
|
|
||||||
|
|
||||||
t = ctx->bytes[0];
|
|
||||||
if ((ctx->bytes[0] = t + len) < t)
|
|
||||||
ctx->bytes[1]++; /* Carry from low to high */
|
|
||||||
|
|
||||||
t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */
|
|
||||||
if (t > len) {
|
|
||||||
memcpy((md5byte *)ctx->in + 64 - t, buf, len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* First chunk is an odd size */
|
|
||||||
memcpy((md5byte *)ctx->in + 64 - t, buf, t);
|
|
||||||
byteSwap(ctx->in, 16);
|
|
||||||
MD5Transform(ctx->buf, ctx->in);
|
|
||||||
buf += t;
|
|
||||||
len -= t;
|
|
||||||
|
|
||||||
/* Process data in 64-byte chunks */
|
|
||||||
while (len >= 64) {
|
|
||||||
memcpy(ctx->in, buf, 64);
|
|
||||||
byteSwap(ctx->in, 16);
|
|
||||||
MD5Transform(ctx->buf, ctx->in);
|
|
||||||
buf += 64;
|
|
||||||
len -= 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handle any remaining bytes of data. */
|
|
||||||
memcpy(ctx->in, buf, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Final wrapup - pad to 64-byte boundary with the bit pattern
|
|
||||||
* 1 0* (64-bit count of bits processed, MSB-first)
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
MD5Final(md5byte digest[16], struct MD5_CTX *ctx)
|
|
||||||
{
|
|
||||||
int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */
|
|
||||||
md5byte *p = (md5byte *)ctx->in + count;
|
|
||||||
|
|
||||||
/* Set the first char of padding to 0x80. There is always room. */
|
|
||||||
*p++ = 0x80;
|
|
||||||
|
|
||||||
/* Bytes of padding needed to make 56 bytes (-8..55) */
|
|
||||||
count = 56 - 1 - count;
|
|
||||||
|
|
||||||
if (count < 0) { /* Padding forces an extra block */
|
|
||||||
memset(p, 0, count + 8);
|
|
||||||
byteSwap(ctx->in, 16);
|
|
||||||
MD5Transform(ctx->buf, ctx->in);
|
|
||||||
p = (md5byte *)ctx->in;
|
|
||||||
count = 56;
|
|
||||||
}
|
|
||||||
memset(p, 0, count);
|
|
||||||
byteSwap(ctx->in, 14);
|
|
||||||
|
|
||||||
/* Append length in bits and transform */
|
|
||||||
ctx->in[14] = ctx->bytes[0] << 3;
|
|
||||||
ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
|
|
||||||
MD5Transform(ctx->buf, ctx->in);
|
|
||||||
|
|
||||||
byteSwap(ctx->buf, 4);
|
|
||||||
memcpy(digest, ctx->buf, 16);
|
|
||||||
memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The four core functions - F1 is optimized somewhat */
|
|
||||||
|
|
||||||
/* #define F1(x, y, z) (x & y | ~x & z) */
|
|
||||||
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
|
||||||
#define F2(x, y, z) F1(z, x, y)
|
|
||||||
#define F3(x, y, z) (x ^ y ^ z)
|
|
||||||
#define F4(x, y, z) (y ^ (x | ~z))
|
|
||||||
|
|
||||||
/* This is the central step in the MD5 algorithm. */
|
|
||||||
#define MD5STEP(f,w,x,y,z,in,s) \
|
|
||||||
(w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The core of the MD5 algorithm, this alters an existing MD5 hash to
|
|
||||||
* reflect the addition of 16 longwords of new data. MD5Update blocks
|
|
||||||
* the data and converts bytes into longwords for this routine.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
MD5Transform(UWORD32 buf[4], UWORD32 const in[16])
|
|
||||||
{
|
|
||||||
register UWORD32 a, b, c, d;
|
|
||||||
|
|
||||||
a = buf[0];
|
|
||||||
b = buf[1];
|
|
||||||
c = buf[2];
|
|
||||||
d = buf[3];
|
|
||||||
|
|
||||||
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
|
|
||||||
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
|
|
||||||
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
|
|
||||||
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
|
|
||||||
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
|
|
||||||
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
|
|
||||||
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
|
|
||||||
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
|
|
||||||
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
|
|
||||||
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
|
|
||||||
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
|
|
||||||
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
|
|
||||||
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
|
|
||||||
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
|
|
||||||
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
|
|
||||||
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
|
|
||||||
|
|
||||||
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
|
|
||||||
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
|
|
||||||
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
|
|
||||||
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
|
|
||||||
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
|
|
||||||
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
|
|
||||||
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
|
|
||||||
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
|
|
||||||
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
|
|
||||||
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
|
|
||||||
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
|
|
||||||
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
|
|
||||||
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
|
|
||||||
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
|
|
||||||
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
|
|
||||||
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
|
|
||||||
|
|
||||||
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
|
|
||||||
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
|
|
||||||
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
|
|
||||||
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
|
|
||||||
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
|
|
||||||
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
|
|
||||||
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
|
|
||||||
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
|
|
||||||
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
|
|
||||||
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
|
|
||||||
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
|
|
||||||
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
|
|
||||||
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
|
|
||||||
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
|
|
||||||
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
|
|
||||||
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
|
|
||||||
|
|
||||||
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
|
|
||||||
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
|
|
||||||
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
|
|
||||||
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
|
|
||||||
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
|
|
||||||
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
|
|
||||||
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
|
|
||||||
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
|
|
||||||
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
|
|
||||||
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
|
|
||||||
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
|
|
||||||
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
|
|
||||||
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
|
|
||||||
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
|
|
||||||
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
|
|
||||||
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
|
|
||||||
|
|
||||||
buf[0] += a;
|
|
||||||
buf[1] += b;
|
|
||||||
buf[2] += c;
|
|
||||||
buf[3] += d;
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
#ifndef __MD5_h__
|
|
||||||
#define __MD5_h__
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is the header file for the MD5 message-digest algorithm.
|
|
||||||
* The algorithm is due to Ron Rivest. This code was
|
|
||||||
* written by Colin Plumb in 1993, no copyright is claimed.
|
|
||||||
* This code is in the public domain; do with it what you wish.
|
|
||||||
*
|
|
||||||
* Equivalent code is available from RSA Data Security, Inc.
|
|
||||||
* This code has been tested against that, and is equivalent,
|
|
||||||
* except that you don't need to include two pages of legalese
|
|
||||||
* with every copy.
|
|
||||||
*
|
|
||||||
* To compute the message digest of a chunk of bytes, declare an
|
|
||||||
* MD5_CTX structure, pass it to MD5Init, call MD5Update as
|
|
||||||
* needed on buffers full of bytes, and then call MD5Final, which
|
|
||||||
* will fill a supplied 16-byte array with the digest.
|
|
||||||
*
|
|
||||||
* Changed so as no longer to depend on Colin Plumb's `usual.h'
|
|
||||||
* header definitions; now uses stuff from dpkg's config.h
|
|
||||||
* - Ian Jackson <ian@chiark.greenend.org.uk>.
|
|
||||||
* Still in the public domain.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#ifdef EXV_HAVE_STDINT_H
|
|
||||||
# include <stdint.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* MSVC doesn't provide C99 types, but it has MS specific variants */
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
typedef unsigned __int32 uint32_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef unsigned char md5byte;
|
|
||||||
typedef uint32_t UWORD32;
|
|
||||||
|
|
||||||
struct MD5_CTX {
|
|
||||||
UWORD32 buf[4];
|
|
||||||
UWORD32 bytes[2];
|
|
||||||
UWORD32 in[16];
|
|
||||||
};
|
|
||||||
|
|
||||||
extern void MD5Init(struct MD5_CTX *context);
|
|
||||||
extern void MD5Update(struct MD5_CTX *context, md5byte const *buf, unsigned len);
|
|
||||||
extern void MD5Final(unsigned char digest[16], struct MD5_CTX *context);
|
|
||||||
extern void MD5Transform(UWORD32 buf[4], UWORD32 const in[16]);
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,144 +0,0 @@
|
|||||||
# ************************************************************* -*- Makefile -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2004-2015 Andreas Huggel <ahuggel@gmx.net>
|
|
||||||
#
|
|
||||||
# This Makefile is part of the Exiv2 distribution.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions
|
|
||||||
# are met:
|
|
||||||
#
|
|
||||||
# 1. Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# 2. Redistributions in binary form must reproduce the above
|
|
||||||
# copyright notice, this list of conditions and the following
|
|
||||||
# disclaimer in the documentation and/or other materials provided
|
|
||||||
# with the distribution.
|
|
||||||
# 3. The name of the author may not be used to endorse or promote
|
|
||||||
# products derived from this software without specific prior
|
|
||||||
# written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
||||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
||||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
||||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
||||||
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
||||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
||||||
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
# File: Makefile
|
|
||||||
# Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
|
|
||||||
# History: 31-Jan-09, ahu: created
|
|
||||||
#
|
|
||||||
# Description:
|
|
||||||
# Simple Makefile to build the organize application. Requires installed
|
|
||||||
# exiv2 library and headers. Adapted from samples/Makefile.
|
|
||||||
#
|
|
||||||
# Restrictions:
|
|
||||||
# Requires GNU make.
|
|
||||||
#
|
|
||||||
|
|
||||||
# ******************************************************************************
|
|
||||||
# Default make target
|
|
||||||
all: ozbin
|
|
||||||
|
|
||||||
# Include system configuration
|
|
||||||
top_srcdir = ../..
|
|
||||||
include $(top_srcdir)/config/config.mk
|
|
||||||
include boost.mk
|
|
||||||
|
|
||||||
# ******************************************************************************
|
|
||||||
# Source files
|
|
||||||
|
|
||||||
# Source files for the organize application
|
|
||||||
OZMAIN = organize.cpp
|
|
||||||
OZSRC = helpers.cpp MD5.cpp
|
|
||||||
|
|
||||||
# ******************************************************************************
|
|
||||||
# Initialisations
|
|
||||||
SHELL = /bin/sh
|
|
||||||
|
|
||||||
.SUFFIXES:
|
|
||||||
.SUFFIXES: .c .cpp .o .so
|
|
||||||
|
|
||||||
.PRECIOUS: %.cpp
|
|
||||||
|
|
||||||
CPPFLAGS := -I$(BOOST_INC_DIR) `pkg-config exiv2 --cflags`
|
|
||||||
ifdef HAVE_STDINT
|
|
||||||
CPPFLAGS += -DEXV_HAVE_STDINT_H=1
|
|
||||||
endif
|
|
||||||
|
|
||||||
LDFLAGS := $(BOOST_LIBS) `pkg-config exiv2 --libs`
|
|
||||||
|
|
||||||
OZOBJ = $(OZSRC:.cpp=.o) $(OZMAIN:.cpp=.o)
|
|
||||||
OZBIN = $(OZMAIN:.cpp=)
|
|
||||||
OZEXE = $(OZMAIN:.cpp=$(EXEEXT))
|
|
||||||
|
|
||||||
ifdef DEP_TRACKING
|
|
||||||
DEP = $(OZMAIN:%.cpp=$(DEPDIR)/%.d) $(OZSRC:%.cpp=$(DEPDIR)/%.d)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# ******************************************************************************
|
|
||||||
# Rules
|
|
||||||
ozbin: $(OZBIN)
|
|
||||||
|
|
||||||
$(OZOBJ): %.o: %.cpp
|
|
||||||
$(COMPILE.cc) -o $@ $<
|
|
||||||
@$(MAKEDEPEND)
|
|
||||||
@$(POSTDEPEND)
|
|
||||||
|
|
||||||
%.ii: %.cpp
|
|
||||||
set -e; \
|
|
||||||
$(CXXCPP) -E $(CPPFLAGS) $< | sed '/^[ ]*$$/d' > $@
|
|
||||||
|
|
||||||
# ******************************************************************************
|
|
||||||
# Targets
|
|
||||||
.PHONY: all ozbin relink binclean install uninstall mostlyclean clean distclean maintainer-clean
|
|
||||||
|
|
||||||
ifdef DEP_TRACKING
|
|
||||||
# Include targets from dependency files
|
|
||||||
-include $(DEP)
|
|
||||||
endif
|
|
||||||
|
|
||||||
$(OZBIN): $(OZOBJ)
|
|
||||||
$(LIBTOOL) --mode=link $(LINK.cc) -o $@ $(OZOBJ)
|
|
||||||
|
|
||||||
relink: binclean organize
|
|
||||||
|
|
||||||
install:
|
|
||||||
$(INSTALL_DIRS) $(DESTDIR)$(bindir)
|
|
||||||
@$(LIBTOOL) --mode=install $(INSTALL_PROGRAM) $(OZEXE) $(DESTDIR)$(bindir)/$(OZEXE)
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
@$(LIBTOOL) --mode=uninstall $(RM) $(DESTDIR)$(bindir)/$(OZEXE)
|
|
||||||
-rmdir $(DESTDIR)$(bindir)
|
|
||||||
|
|
||||||
# Remove binaries, e.g., to relink them
|
|
||||||
binclean:
|
|
||||||
$(RM) $(OZEXE)
|
|
||||||
|
|
||||||
mostlyclean:
|
|
||||||
$(RM) core
|
|
||||||
$(RM) $(OZMAIN:.cpp=.ii) $(OZSRC:.cpp=.ii)
|
|
||||||
$(RM) $(OZMAIN:%.cpp=.libs/%.d) $(OZSRC:%.cpp=.libs/%.d)
|
|
||||||
-rmdir .libs
|
|
||||||
$(RM) $(OZOBJ)
|
|
||||||
|
|
||||||
clean: binclean mostlyclean
|
|
||||||
|
|
||||||
# Run `make distclean' from the top source directory to also remove
|
|
||||||
# files created by configuring the program.
|
|
||||||
distclean: clean
|
|
||||||
ifdef DEP_TRACKING
|
|
||||||
$(RM) $(DEP)
|
|
||||||
-rmdir $(DEPDIR)
|
|
||||||
endif
|
|
||||||
$(RM) *~ *.bak *#
|
|
||||||
|
|
||||||
# This command is intended for maintainers to use; it deletes files
|
|
||||||
# that may need special tools to rebuild.
|
|
||||||
maintainer-clean: uninstall distclean
|
|
@ -1,3 +0,0 @@
|
|||||||
organize uses the Boost library (http://www.boost.org).
|
|
||||||
Configuration settings for Boost are in the file boost.mk
|
|
||||||
in this directory and should be changed as required.
|
|
@ -1,3 +0,0 @@
|
|||||||
# Boost configuration for organize - change paths and library names as needed
|
|
||||||
BOOST_INC_DIR = /usr/local/include/boost-1_37
|
|
||||||
BOOST_LIBS = /usr/local/lib/libboost_system-gcc43-mt-1_37.a /usr/local/lib/libboost_filesystem-gcc43-mt-1_37.a /usr/local/lib/libboost_regex-gcc43-mt-1_37.a /usr/local/lib/libboost_program_options-gcc43-mt-1_37.a
|
|
@ -1,635 +0,0 @@
|
|||||||
// ***************************************************************** -*- C++ -*-
|
|
||||||
/*
|
|
||||||
* Copyright (C) 2009 Brad Schick <schickb@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of the organize tool.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
// *****************************************************************************
|
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
#include <boost/regex.hpp>
|
|
||||||
#include <boost/format.hpp>
|
|
||||||
#include <boost/lexical_cast.hpp>
|
|
||||||
#include <exiv2/image.hpp>
|
|
||||||
#include <exiv2/easyaccess.hpp>
|
|
||||||
#include <exiv2/exif.hpp>
|
|
||||||
#include <exiv2/iptc.hpp>
|
|
||||||
#include <exiv2/tags.hpp>
|
|
||||||
//#include <exiv2/xmp.hpp>
|
|
||||||
#include <cassert>
|
|
||||||
#include <sstream>
|
|
||||||
#include <ctime>
|
|
||||||
#include "helpers.hpp"
|
|
||||||
|
|
||||||
#define BOOST_FILESYSTEM_NO_DEPRECATED
|
|
||||||
|
|
||||||
namespace fs = boost::filesystem;
|
|
||||||
typedef Exiv2::ExifData::const_iterator (*EasyAccessFct)(const Exiv2::ExifData& ed);
|
|
||||||
|
|
||||||
|
|
||||||
std::string scrub(const std::string &dirty, bool strip_space = false)
|
|
||||||
{
|
|
||||||
std::string scrub = boost::trim_copy(dirty);
|
|
||||||
if(strip_space) {
|
|
||||||
boost::regex space("\\s");
|
|
||||||
scrub = boost::regex_replace(scrub, space, "");
|
|
||||||
}
|
|
||||||
boost::regex dash("[:/\\\\|<>]");
|
|
||||||
boost::regex under("[\"'\\[\\]\\{\\}#=%\\$\\?,\\+\\*]");
|
|
||||||
scrub = boost::regex_replace(scrub, dash, "-");
|
|
||||||
|
|
||||||
return boost::regex_replace(scrub, under, "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool exif_data(const Exiv2::Image *image, const char *key, Exiv2::ExifData::const_iterator &md)
|
|
||||||
{
|
|
||||||
assert(image && key);
|
|
||||||
bool ok = false;
|
|
||||||
try {
|
|
||||||
const Exiv2::ExifData &exifData = image->exifData();
|
|
||||||
Exiv2::ExifKey exifKey(key);
|
|
||||||
md = exifData.findKey(exifKey);
|
|
||||||
if(md != exifData.end() && md->typeId() != Exiv2::undefined)
|
|
||||||
ok = true;
|
|
||||||
}
|
|
||||||
catch(const Exiv2::AnyError&) {
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool exif_data_easy(const Exiv2::Image *image, EasyAccessFct easy, Exiv2::ExifData::const_iterator &md)
|
|
||||||
{
|
|
||||||
assert(image && easy);
|
|
||||||
bool ok = false;
|
|
||||||
try {
|
|
||||||
const Exiv2::ExifData &exifData = image->exifData();
|
|
||||||
md = easy(exifData);
|
|
||||||
if(md != exifData.end() && md->typeId() != Exiv2::undefined)
|
|
||||||
ok = true;
|
|
||||||
}
|
|
||||||
catch(const Exiv2::AnyError&) {
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool iptc_data(const Exiv2::Image *image, const char *key, Exiv2::IptcData::const_iterator &md)
|
|
||||||
{
|
|
||||||
bool ok = false;
|
|
||||||
assert(image && key);
|
|
||||||
try {
|
|
||||||
const Exiv2::IptcData &iptcData = image->iptcData();
|
|
||||||
Exiv2::IptcKey iptcKey(key);
|
|
||||||
md = iptcData.findKey(iptcKey);
|
|
||||||
if(md != iptcData.end() && md->typeId() != Exiv2::undefined)
|
|
||||||
ok = true;
|
|
||||||
}
|
|
||||||
catch(const Exiv2::AnyError&) {
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_date(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.DateTimeDigitized", md);
|
|
||||||
if(!done)
|
|
||||||
done = exif_data(image, "Exif.Photo.DateTimeOriginal", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
std::string date = scrub(md->print().substr(0,10));
|
|
||||||
// Some files have zeros for dates, just fail in that case
|
|
||||||
if(boost::lexical_cast<int>(date.substr(0,4))==0)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_year(const Exiv2::Image *image, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::string date = exif_date(image, path);
|
|
||||||
if(date.length())
|
|
||||||
return date.substr(0,4);
|
|
||||||
else
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_month(const Exiv2::Image *image, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::string date = exif_date(image, path);
|
|
||||||
if(date.length())
|
|
||||||
return date.substr(5,2);
|
|
||||||
else
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_day(const Exiv2::Image *image, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::string date = exif_date(image, path);
|
|
||||||
if(date.length())
|
|
||||||
return date.substr(8,2);
|
|
||||||
else
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool iptc_get_date(const Exiv2::Image *image, Exiv2::DateValue::Date &date)
|
|
||||||
{
|
|
||||||
Exiv2::IptcData::const_iterator md;
|
|
||||||
bool done = iptc_data(image, "Iptc.Application2.DigitizationDate", md);
|
|
||||||
if(!done)
|
|
||||||
done = iptc_data(image, "Iptc.Application2.DateCreated", md);
|
|
||||||
if(!done)
|
|
||||||
return false;
|
|
||||||
date = ((Exiv2::DateValue*)md->getValue().get())->getDate();
|
|
||||||
return date.year > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string iptc_date(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::DateValue::Date date;
|
|
||||||
if(iptc_get_date(image, date))
|
|
||||||
return str(boost::format("%4d-%02d-%02d") % date.year % date.month % date.day);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string iptc_year(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::DateValue::Date date;
|
|
||||||
if(iptc_get_date(image, date))
|
|
||||||
return str(boost::format("%4d") % date.year);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string iptc_month(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::DateValue::Date date;
|
|
||||||
if(iptc_get_date(image, date))
|
|
||||||
return str(boost::format("%02d") % date.month);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string iptc_day(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::DateValue::Date date;
|
|
||||||
if(iptc_get_date(image, date))
|
|
||||||
return str(boost::format("%02d") % date.day);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool file_get_tm(const fs::path &path, std::tm &tm)
|
|
||||||
{
|
|
||||||
std::time_t timer = fs::last_write_time(path);
|
|
||||||
if(time > 0) {
|
|
||||||
tm = *localtime(&timer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_date(const Exiv2::Image *, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::tm tm;
|
|
||||||
if(file_get_tm(path, tm))
|
|
||||||
return str(boost::format("%4d-%02d-%02d") % (tm.tm_year + 1900) % (tm.tm_mon + 1) % tm.tm_mday);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_year(const Exiv2::Image *, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::tm tm;
|
|
||||||
if(file_get_tm(path, tm))
|
|
||||||
return str(boost::format("%4d") % (tm.tm_year + 1900));
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_month(const Exiv2::Image *, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::tm tm;
|
|
||||||
if(file_get_tm(path, tm))
|
|
||||||
return str(boost::format("%02d") % (tm.tm_mon + 1));
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_day(const Exiv2::Image *, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::tm tm;
|
|
||||||
if(file_get_tm(path, tm))
|
|
||||||
return str(boost::format("%02d") % tm.tm_mday);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
std::string xmp_date(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string xmp_year(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string xmp_month(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string xmp_day(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::string exif_time(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.DateTimeDigitized", md);
|
|
||||||
if(!done)
|
|
||||||
done = exif_data(image, "Exif.Photo.DateTimeOriginal", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
std::string datetime = md->print();
|
|
||||||
// Some files have zeros for dates, just fail in that case
|
|
||||||
if(boost::lexical_cast<int>(datetime.substr(0,4)) == 0)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
return scrub(datetime.substr(11));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_hour(const Exiv2::Image *image, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::string time = exif_time(image, path);
|
|
||||||
if(time.length())
|
|
||||||
return time.substr(0,2);
|
|
||||||
else
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_minute(const Exiv2::Image *image, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::string time = exif_time(image, path);
|
|
||||||
if(time.length())
|
|
||||||
return time.substr(3,2);
|
|
||||||
else
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_second(const Exiv2::Image *image, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::string time = exif_time(image, path);
|
|
||||||
if(time.length())
|
|
||||||
return time.substr(6,2);
|
|
||||||
else
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool iptc_get_time(const Exiv2::Image *image, Exiv2::TimeValue::Time &time)
|
|
||||||
{
|
|
||||||
Exiv2::IptcData::const_iterator md;
|
|
||||||
bool done = iptc_data(image, "Iptc.Application2.DigitizationTime", md);
|
|
||||||
if(!done)
|
|
||||||
done = iptc_data(image, "Iptc.Application2.TimeCreated", md);
|
|
||||||
if(!done)
|
|
||||||
return false;
|
|
||||||
time = ((Exiv2::TimeValue*)md->getValue().get())->getTime();
|
|
||||||
// Zero is a valid time, so this one is hard to check.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string iptc_time(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::TimeValue::Time time;
|
|
||||||
if(iptc_get_time(image, time))
|
|
||||||
return str(boost::format("%02d-%02d-%02d") % time.hour % time.minute % time.second);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string iptc_hour(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::TimeValue::Time time;
|
|
||||||
if(iptc_get_time(image, time))
|
|
||||||
return str(boost::format("%02d") % time.hour);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string iptc_minute(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::TimeValue::Time time;
|
|
||||||
if(iptc_get_time(image, time))
|
|
||||||
return str(boost::format("%02d") % time.minute);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string iptc_second(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::TimeValue::Time time;
|
|
||||||
if(iptc_get_time(image, time))
|
|
||||||
return str(boost::format("%02d") % time.second);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_time(const Exiv2::Image *, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::tm tm;
|
|
||||||
if(file_get_tm(path, tm))
|
|
||||||
return str(boost::format("%02d-%02d-%02d") % tm.tm_hour % tm.tm_min % tm.tm_sec);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_hour(const Exiv2::Image *, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::tm tm;
|
|
||||||
if(file_get_tm(path, tm))
|
|
||||||
return str(boost::format("%02d") % tm.tm_hour);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_minute(const Exiv2::Image *, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::tm tm;
|
|
||||||
if(file_get_tm(path, tm))
|
|
||||||
return str(boost::format("%02d") % tm.tm_min);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_second(const Exiv2::Image *, const fs::path &path)
|
|
||||||
{
|
|
||||||
std::tm tm;
|
|
||||||
if(file_get_tm(path, tm))
|
|
||||||
return str(boost::format("%02d") % tm.tm_sec);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*std::string xmp_time(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string xmp_hour(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string xmp_minute(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string xmp_second(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::string exif_dimension(const Exiv2::Image *image, const fs::path &path)
|
|
||||||
{
|
|
||||||
return exif_width(image, path) + "-" + exif_height(image, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_width(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.PixelXDimension", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_height(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.PixelYDimension", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_dimension(const Exiv2::Image *image, const fs::path &path)
|
|
||||||
{
|
|
||||||
if(image)
|
|
||||||
return file_width(image, path) + "-" + file_height(image, path);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_width(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
if(image)
|
|
||||||
return str(boost::format("%02d") % image->pixelWidth());
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file_height(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
if(image)
|
|
||||||
return str(boost::format("%02d") % image->pixelHeight());
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
std::string xmp_dimension(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string xmp_width(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string xmp_height(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::string exif_model(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Image.Model", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_make(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Image.Make", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*std::string xmp_model(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::string exif_speed(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.ShutterSpeedValue", md);
|
|
||||||
if(!done)
|
|
||||||
done = exif_data(image, "Exif.Photo.ExposureTime", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*std::string xmp_speed(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::string exif_aperture(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.ApertureValue", md);
|
|
||||||
if(!done)
|
|
||||||
done = exif_data(image, "Exif.Photo.FNumber", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*std::string xmp_aperture(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::string exif_focal(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.FocalLength", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*std::string xmp_focal(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::string exif_distance(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.SubjectDistance", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*std::string xmp_distance(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::string exif_meter(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.MeteringMode", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_macro(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data_easy(image, Exiv2::macroMode, md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_orientation(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data_easy(image, Exiv2::orientation, md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exif_lens(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data_easy(image, Exiv2::lensName, md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::string exif_iso(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data_easy(image, Exiv2::isoSpeed, md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*std::string xmp_meter(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
std::string exif_keyword(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::ExifData::const_iterator md;
|
|
||||||
bool done = exif_data(image, "Exif.Photo.UserComment", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string iptc_keyword(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
Exiv2::IptcData::const_iterator md;
|
|
||||||
bool done = iptc_data(image, "Iptc.Application2.Keywords", md);
|
|
||||||
if(!done)
|
|
||||||
return "";
|
|
||||||
return scrub(md->print());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*std::string xmp_keyword(const Exiv2::Image *image, const fs::path &)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}*/
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
|||||||
// ***************************************************************** -*- C++ -*-
|
|
||||||
/*
|
|
||||||
* Copyright (C) 2004-2021 Exiv2 authors
|
|
||||||
* This program is part of the Exiv2 distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
// *****************************************************************************
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef HELPERS_HPP_
|
|
||||||
#define HELPERS_HPP_
|
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
|
|
||||||
#define BOOST_FILESYSTEM_NO_DEPRECATED
|
|
||||||
namespace fs = boost::filesystem;
|
|
||||||
|
|
||||||
|
|
||||||
typedef std::string (*pfunc)(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
|
|
||||||
// This would be a lot smaller if Exiv2 had support
|
|
||||||
// for unified metadata
|
|
||||||
|
|
||||||
std::string exif_date(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_year(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_month(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_day(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string iptc_date(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string iptc_year(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string iptc_month(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string iptc_day(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_date(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_year(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_month(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_day(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
/*std::string xmp_date(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string xmp_year(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string xmp_month(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string xmp_day(const Exiv2::Image *image, const fs::path &path);*/
|
|
||||||
std::string exif_time(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_hour(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_minute(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_second(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string iptc_time(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string iptc_hour(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string iptc_minute(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string iptc_second(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_time(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_hour(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_minute(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_second(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
/*std::string xmp_time(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string xmp_hour(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string xmp_minute(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string xmp_second(const Exiv2::Image *image, const fs::path &path);*/
|
|
||||||
std::string exif_dimension(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_width(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_height(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_dimension(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_width(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string file_height(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
/*std::string xmp_dimension(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string xmp_width(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string xmp_height(const Exiv2::Image *image, const fs::path &path);*/
|
|
||||||
std::string exif_model(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_make(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
/*std::string xmp_model(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string xmp_make(const Exiv2::Image *image, const fs::path &path);*/
|
|
||||||
std::string exif_speed(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
//std::string xmp_speed(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_aperture(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
//std::string xmp_aperture(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_focal(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
//std::string xmp_focal(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_distance(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
//std::string xmp_distance(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_meter(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
//std::string xmp_meter(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_macro(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_orientation(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_lens(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_keyword(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string iptc_keyword(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
//std::string xmp_keyword(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
std::string exif_iso(const Exiv2::Image *image, const fs::path &path);
|
|
||||||
|
|
||||||
#endif //HELPERS_HPP_
|
|
||||||
|
|
@ -1,759 +0,0 @@
|
|||||||
// ***************************************************************** -*- C++ -*-
|
|
||||||
/*
|
|
||||||
* Copyright (C) 2004-2021 Exiv2 authors
|
|
||||||
* This program is part of the Exiv2 distribution.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
// *****************************************************************************
|
|
||||||
|
|
||||||
#include <boost/program_options.hpp>
|
|
||||||
#include <boost/regex.hpp>
|
|
||||||
#include <boost/array.hpp>
|
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
#include <boost/lexical_cast.hpp>
|
|
||||||
#include <exiv2/image.hpp>
|
|
||||||
#include <exiv2/error.hpp>
|
|
||||||
#include <exiv2/basicio.hpp>
|
|
||||||
#include <iostream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <cassert>
|
|
||||||
#include <limits>
|
|
||||||
#include "MD5.h"
|
|
||||||
#include "helpers.hpp"
|
|
||||||
|
|
||||||
typedef Exiv2::byte md5digest[16];
|
|
||||||
|
|
||||||
namespace po = boost::program_options;
|
|
||||||
|
|
||||||
bool g_verbose = false;
|
|
||||||
bool g_neednewline = false;
|
|
||||||
|
|
||||||
// Array size should match number of SLOTs
|
|
||||||
boost::array<int,4> g_run_order = {{-1, -1, -1, -1}};
|
|
||||||
const int EXIF_SLOT = 0;
|
|
||||||
const int IPTC_SLOT = 1;
|
|
||||||
const int XMP_SLOT = 2;
|
|
||||||
const int FILE_SLOT = 3;
|
|
||||||
|
|
||||||
const unsigned DOT_EVERY = 55;
|
|
||||||
|
|
||||||
struct Pattern {
|
|
||||||
std::string pat;
|
|
||||||
std::string desc;
|
|
||||||
pfunc funcs[4]; // order should always be exif, iptc, xmp, file
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PathPart {
|
|
||||||
std::string pre;
|
|
||||||
const Pattern *pat;
|
|
||||||
std::string post;
|
|
||||||
PathPart(std::string pre_, const Pattern *pat_, std::string post_)
|
|
||||||
: pre(pre_), pat(pat_), post(post_) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<PathPart> g_path_parts;
|
|
||||||
|
|
||||||
// Instead of making these all global
|
|
||||||
struct ProcessParams {
|
|
||||||
const fs::path &dest_dir;
|
|
||||||
const bool dry_run;
|
|
||||||
const bool ignore_dups;
|
|
||||||
const bool ignore_unsorted;
|
|
||||||
const bool force;
|
|
||||||
const bool rename;
|
|
||||||
const bool symlink;
|
|
||||||
const bool verify;
|
|
||||||
const bool move;
|
|
||||||
const long limit_depth;
|
|
||||||
const fs::path &dups_dir;
|
|
||||||
const fs::path &unsorted_dir;
|
|
||||||
const std::vector<std::string> &excludes;
|
|
||||||
unsigned dups_count;
|
|
||||||
unsigned unsorted_count;
|
|
||||||
unsigned dir_err_count;
|
|
||||||
unsigned file_err_count;
|
|
||||||
unsigned ok_count;
|
|
||||||
unsigned dups_ignored_count;
|
|
||||||
unsigned unsorted_ignored_count;
|
|
||||||
unsigned dir_ex_count;
|
|
||||||
unsigned file_ex_count;
|
|
||||||
};
|
|
||||||
|
|
||||||
void process_directory(const fs::path &directory, const long depth,
|
|
||||||
ProcessParams ¶ms);
|
|
||||||
|
|
||||||
const Pattern g_patterns[] = {
|
|
||||||
{"@date", "date captured (2009-01-19)",
|
|
||||||
{exif_date, iptc_date, NULL, file_date} },
|
|
||||||
{"@year", "year captured (2009)",
|
|
||||||
{exif_year, iptc_year, NULL, file_year} },
|
|
||||||
{"@month", "month captured (01)",
|
|
||||||
{exif_month, iptc_month, NULL, file_month} },
|
|
||||||
{"@day", "day captured (19)",
|
|
||||||
{exif_day, iptc_day, NULL, file_day} },
|
|
||||||
{"@time", "time captured (14-35-27)",
|
|
||||||
{exif_time, iptc_time, NULL, file_time} },
|
|
||||||
{"@hour", "hour captured (14)",
|
|
||||||
{exif_hour, iptc_hour, NULL, file_hour} },
|
|
||||||
{"@min", "minute captured (35)",
|
|
||||||
{exif_minute, iptc_minute, NULL, file_minute} },
|
|
||||||
{"@sec", "second captured (27)",
|
|
||||||
{exif_second, iptc_second, NULL, file_second} },
|
|
||||||
{"@dim", "pixel dimension (2272-1704)",
|
|
||||||
{exif_dimension, NULL, NULL, file_dimension} },
|
|
||||||
{"@x", "pixel width (2272)",
|
|
||||||
{exif_width, NULL, NULL, file_width} },
|
|
||||||
{"@y", "pixel height (1704)",
|
|
||||||
{exif_height, NULL, NULL, file_height} },
|
|
||||||
{"@make", "device make (Canon)",
|
|
||||||
{exif_make, NULL, NULL, NULL} },
|
|
||||||
{"@model", "device model (Canon PowerShot S40)",
|
|
||||||
{exif_model, NULL, NULL, NULL} },
|
|
||||||
{"@speed", "shutter speed (1-60)",
|
|
||||||
{exif_speed, NULL, NULL, NULL} },
|
|
||||||
{"@aper", "aperture (F3.2)",
|
|
||||||
{exif_aperture, NULL, NULL, NULL} },
|
|
||||||
{"@iso", "iso speed (400)",
|
|
||||||
{exif_iso, NULL, NULL, NULL} },
|
|
||||||
{"@focal", "focal length (8.6 mm)",
|
|
||||||
{exif_focal, NULL, NULL, NULL} },
|
|
||||||
{"@dist", "subject distance (1.03 m)",
|
|
||||||
{exif_distance, NULL, NULL, NULL} },
|
|
||||||
{"@meter", "meter mode (multi-segment)",
|
|
||||||
{exif_meter, NULL, NULL, NULL} },
|
|
||||||
{"@macro", "macro mode (Off)",
|
|
||||||
{exif_macro, NULL, NULL, NULL} },
|
|
||||||
{"@orient", "orientation (top_left)",
|
|
||||||
{exif_orientation, NULL, NULL, NULL} },
|
|
||||||
{"@lens", "lens name (Tamron 90mm f-2.8)",
|
|
||||||
{exif_lens, NULL, NULL, NULL} },
|
|
||||||
{"@key", "first keyword (Family)",
|
|
||||||
{exif_keyword, iptc_keyword, NULL, NULL} },
|
|
||||||
|
|
||||||
{"", "", {NULL, NULL, NULL, NULL} }
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Check that 'opt1' and 'opt2' are not specified at the same time.
|
|
||||||
void conflicting(const po::variables_map& vm,
|
|
||||||
const char* opt1, const char* opt2)
|
|
||||||
{
|
|
||||||
if (vm.count(opt1) && !vm[opt1].defaulted()
|
|
||||||
&& vm.count(opt2) && !vm[opt2].defaulted()) {
|
|
||||||
throw std::logic_error(std::string("conflicting options '")
|
|
||||||
+ opt1 + "' and '" + opt2 + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that 'required' is present
|
|
||||||
void required(const po::variables_map& vm, const char* required)
|
|
||||||
{
|
|
||||||
if (!vm.count(required) || vm[required].defaulted()) {
|
|
||||||
throw std::logic_error(std::string("required parameter '") + required
|
|
||||||
+ "' is missing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void info(const std::string &msg)
|
|
||||||
{
|
|
||||||
if(g_verbose) {
|
|
||||||
std::cout << msg << "\n";
|
|
||||||
g_neednewline = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void error(const std::exception &e, const std::string &msg)
|
|
||||||
{
|
|
||||||
if(g_neednewline) {
|
|
||||||
std::cout << "\n";
|
|
||||||
g_neednewline = false;
|
|
||||||
}
|
|
||||||
std::cerr << e.what() << "\n";
|
|
||||||
std::cerr << msg << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void usage_header(const char* exname)
|
|
||||||
{
|
|
||||||
std::cout << "Usage: " << exname << " [options] source-dir dest-dir pattern\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
void usage_full(const po::options_description &options, const char* exname)
|
|
||||||
{
|
|
||||||
usage_header(exname);
|
|
||||||
std::cout << "\n Creates groups of files in new directories defined by a metadata 'pattern'.\n" <<
|
|
||||||
" Files are copied, moved, or linked from 'source-dir' to 'dest-dir'.\n" <<
|
|
||||||
" The destination directory should not be within the source directory.\n\n";
|
|
||||||
std::cout << options;
|
|
||||||
|
|
||||||
std::cout << "\nPattern values:\n";
|
|
||||||
for( const Pattern *pattern = g_patterns; pattern->pat.length(); ++pattern) {
|
|
||||||
std::cout << " " << std::setw(8) << std::left << pattern->pat;
|
|
||||||
std::cout << pattern->desc << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "\nExamples:\n";
|
|
||||||
std::cout << " `" << exname << " -m mess clean @year-@month'\n";
|
|
||||||
std::cout << " Moves files from 'mess' into directories of 'clean' according to\n" <<
|
|
||||||
" year-month the file was captured (clean/2006-11/...)\n\n";
|
|
||||||
std::cout << " `" << exname << " -o ie source find width-@x/height-@y'\n";
|
|
||||||
std::cout << " Copies files into directories according first to pixel width then pixel\n" <<
|
|
||||||
" height. Check iptc then exif metadata (find/width-2272/height-1704/...)\n\n";
|
|
||||||
std::cout << " `" << exname << " -lf source find @aper/@hour'\n";
|
|
||||||
std::cout << " Force create symlinks in directories according first to aperture then\n" <<
|
|
||||||
" hour captured (find/F3.2/15/...)\n";
|
|
||||||
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void version()
|
|
||||||
{
|
|
||||||
std::cout << "organized 0.1\n" <<
|
|
||||||
"Copyright (C) 2009 Brad Schick. <schickb@gmail.com>\n\n" <<
|
|
||||||
"This program is free software; you can redistribute it and/or\n"
|
|
||||||
"modify it under the terms of the GNU General Public License\n"
|
|
||||||
"as published by the Free Software Foundation; either version 2\n"
|
|
||||||
"of the License, or (at your option) any later version.\n"
|
|
||||||
"\n"
|
|
||||||
"This program is distributed in the hope that it will be useful,\n"
|
|
||||||
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
|
|
||||||
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
|
|
||||||
"GNU General Public License for more details.\n"
|
|
||||||
"\n"
|
|
||||||
"You should have received a copy of the GNU General Public\n"
|
|
||||||
"License along with this program; if not, write to the Free\n"
|
|
||||||
"Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n"
|
|
||||||
"Boston, MA 02110-1301 USA" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns empty string if the destination subdirectory could not be determined
|
|
||||||
// for the supplied source file.
|
|
||||||
std::string build_dest(const fs::path &source_file)
|
|
||||||
{
|
|
||||||
std::string dest;
|
|
||||||
|
|
||||||
Exiv2::Image::AutoPtr image;
|
|
||||||
try {
|
|
||||||
image = Exiv2::ImageFactory::open(source_file.string());
|
|
||||||
image->readMetadata();
|
|
||||||
}
|
|
||||||
catch(const Exiv2::AnyError&) {
|
|
||||||
// No metadata, let things continue to try file info
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<PathPart>::iterator iter = g_path_parts.begin();
|
|
||||||
std::vector<PathPart>::iterator end = g_path_parts.end();
|
|
||||||
for( ; iter != end; ++iter) {
|
|
||||||
dest += iter->pre;
|
|
||||||
std::string result;
|
|
||||||
|
|
||||||
const Pattern *pat = iter->pat;
|
|
||||||
for(unsigned fx = 0; fx < g_run_order.size(); ++fx) {
|
|
||||||
if(g_run_order[fx] != -1 && pat->funcs[g_run_order[fx]]) {
|
|
||||||
if(g_run_order[fx] == FILE_SLOT) {
|
|
||||||
// Always run file operations
|
|
||||||
result = pat->funcs[g_run_order[fx]](image.get(), source_file);
|
|
||||||
}
|
|
||||||
else if(image.get()) {
|
|
||||||
// No point in running metadata operations without an image
|
|
||||||
result = pat->funcs[g_run_order[fx]](image.get(), source_file);
|
|
||||||
}
|
|
||||||
if(result.length())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we found no data, even for part of pattern, give up and
|
|
||||||
// return no destination
|
|
||||||
if(!result.length())
|
|
||||||
return result;
|
|
||||||
|
|
||||||
dest += (result + iter->post);
|
|
||||||
}
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool md5sum(const fs::path &path, md5digest &digest)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Exiv2::FileIo io(path.string());
|
|
||||||
if (io.open() != 0)
|
|
||||||
return false;
|
|
||||||
Exiv2::IoCloser closer(io);
|
|
||||||
|
|
||||||
Exiv2::byte buff[4096];
|
|
||||||
MD5_CTX context;
|
|
||||||
MD5Init(&context);
|
|
||||||
|
|
||||||
long read_count = io.read(buff, 4096);
|
|
||||||
while(read_count) {
|
|
||||||
MD5Update(&context, buff, read_count);
|
|
||||||
read_count = io.read(buff, 4096);
|
|
||||||
}
|
|
||||||
MD5Final(digest, &context);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (std::exception& ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
|
||||||
{
|
|
||||||
po::options_description options("Options");
|
|
||||||
// Don't use default values because the help print it ugly and too wide
|
|
||||||
options.add_options()
|
|
||||||
("move,m", "move files rather than copy")
|
|
||||||
("symlink,s", "symlink files rather than copy (posix only)")
|
|
||||||
("order,o", po::value<std::string>(),
|
|
||||||
"order and types of metadata to read\ne=exif, i=iptc, f=file (default: eif)")
|
|
||||||
("unsorted,u", po::value<std::string>(),
|
|
||||||
"special directory to store unsorted files (default: unsorted)")
|
|
||||||
("dups,d", po::value<std::string>(),
|
|
||||||
"special directory to store files with duplicate names (default: duplicates)")
|
|
||||||
("force,f", "overwrite duplicate files instead of using special directory")
|
|
||||||
("rename,r", "rename duplicate files instead of using special directory")
|
|
||||||
("ignore,i", "ignore both unsorted and duplicate files instead of using special directories")
|
|
||||||
("ignore-unsorted", "ignore unsorted files instead of using special directory")
|
|
||||||
("ignore-dups", "ignore duplicate files instead of using special directory")
|
|
||||||
("verify", "verify copied or moved files and exit if incorrect")
|
|
||||||
("exclude,x", po::value< std::vector<std::string> >(),
|
|
||||||
"exclude directories and files that contain arg (case sensitive on all platforms)")
|
|
||||||
("limit-depth,l", po::value<long>(),
|
|
||||||
"limit recursion to specified depth (0 disables recursion)")
|
|
||||||
("verbose,v", "prints operations as they happen")
|
|
||||||
("dry-run,n", "do not make actual changes (implies verbose)")
|
|
||||||
("help,h", "show this help message then exit")
|
|
||||||
("version,V", "show program version then exit")
|
|
||||||
;
|
|
||||||
|
|
||||||
po::options_description hidden("Hidden Options");
|
|
||||||
hidden.add_options()
|
|
||||||
("source-dir", po::value< std::string >(), "directory of files to organize, may end in file wildcard")
|
|
||||||
("dest-dir", po::value< std::string >(), "designation directory for files, may not be within source-dir")
|
|
||||||
("pattern", po::value< std::string >(), "subdirectory pattern for grouping files within dest-dir")
|
|
||||||
;
|
|
||||||
|
|
||||||
po::options_description cmdline;
|
|
||||||
cmdline.add(options).add(hidden);
|
|
||||||
|
|
||||||
po::positional_options_description positional;
|
|
||||||
positional.add("source-dir", 1);
|
|
||||||
positional.add("dest-dir", 1);
|
|
||||||
positional.add("pattern", 1);
|
|
||||||
|
|
||||||
try {
|
|
||||||
po::variables_map vm;
|
|
||||||
po::store(po::command_line_parser(argc, argv).
|
|
||||||
options(cmdline).positional(positional).run(), vm);
|
|
||||||
po::notify(vm);
|
|
||||||
|
|
||||||
if (vm.count("help")) {
|
|
||||||
usage_full(options, argv[0]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vm.count("version")) {
|
|
||||||
version();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
conflicting(vm, "verify", "symlink");
|
|
||||||
conflicting(vm, "move", "symlink");
|
|
||||||
conflicting(vm, "unsorted", "ignore");
|
|
||||||
conflicting(vm, "unsorted", "ignore-unsorted");
|
|
||||||
conflicting(vm, "dups", "ignore");
|
|
||||||
conflicting(vm, "dups", "ignore-dups");
|
|
||||||
conflicting(vm, "force", "ignore");
|
|
||||||
conflicting(vm, "force", "ignore-dups");
|
|
||||||
conflicting(vm, "force", "rename");
|
|
||||||
conflicting(vm, "rename", "ignore");
|
|
||||||
conflicting(vm, "rename", "ignore-dups");
|
|
||||||
required(vm, "source-dir");
|
|
||||||
required(vm, "dest-dir");
|
|
||||||
required(vm, "pattern");
|
|
||||||
|
|
||||||
const bool dry_run = vm.count("dry-run") != 0;
|
|
||||||
g_verbose = (vm.count("verbose") != 0 || dry_run);
|
|
||||||
|
|
||||||
std::string order = "eif";
|
|
||||||
if(vm.count("order")) {
|
|
||||||
order = vm["order"].as<std::string>();
|
|
||||||
|
|
||||||
boost::to_lower(order);
|
|
||||||
if(order.length() > 3) {
|
|
||||||
throw std::logic_error(std::string("order is longer than 4 characters"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned i = 0;
|
|
||||||
std::string::iterator end = order.end();
|
|
||||||
for(std::string::iterator iter = order.begin(); iter != end && i < 4; ++iter, ++i) {
|
|
||||||
switch(*iter) {
|
|
||||||
case 'e':
|
|
||||||
g_run_order[i] = EXIF_SLOT;
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
g_run_order[i] = IPTC_SLOT;
|
|
||||||
break;
|
|
||||||
case 'x':
|
|
||||||
throw std::logic_error(std::string("xmp not implemented yet '") +
|
|
||||||
*iter + "'");
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
g_run_order[i] = FILE_SLOT;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw std::logic_error(std::string("unknown order character '") +
|
|
||||||
*iter + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fs::path source_dir( vm["source-dir"].as<std::string>() );
|
|
||||||
if( !exists(source_dir) || !is_directory(source_dir) ) {
|
|
||||||
throw std::logic_error(std::string("source '") +
|
|
||||||
source_dir.string() + "' must exist and be a directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
const fs::path dest_dir( vm["dest-dir"].as<std::string>() );
|
|
||||||
if( exists(dest_dir) && !is_directory(dest_dir) ) {
|
|
||||||
throw std::logic_error(std::string("destination '") +
|
|
||||||
dest_dir.string() + "' must be a directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boost doesn't seem to have a way to get a canonical path, so this
|
|
||||||
// simple test is easy to confuse with some ../../'s in the paths. Oh
|
|
||||||
// well, this is good enough for now.
|
|
||||||
fs::path test_dest(dest_dir);
|
|
||||||
for(; !test_dest.empty(); test_dest = test_dest.parent_path()) {
|
|
||||||
if(fs::equivalent(source_dir, test_dest)) {
|
|
||||||
throw std::logic_error(std::string("dest-dir must not be within source-dir"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disect the pattern
|
|
||||||
std::string pattern = vm["pattern"].as<std::string>();
|
|
||||||
boost::regex regex( "([^@]*)(@[[:alpha:]]+)([^@]*)");
|
|
||||||
boost::sregex_iterator m_iter = make_regex_iterator(pattern, regex);
|
|
||||||
boost::sregex_iterator m_end;
|
|
||||||
for( ; m_iter != m_end; ++m_iter) {
|
|
||||||
const boost::smatch &match = *m_iter;
|
|
||||||
const std::string &pre = match[1];
|
|
||||||
const std::string &pat = match[2];
|
|
||||||
const std::string &post = match[3];
|
|
||||||
|
|
||||||
// Should put this in a map, but there aren't that many options now
|
|
||||||
bool found = false;
|
|
||||||
for( const Pattern *pattern = g_patterns; pattern->pat.length(); ++pattern) {
|
|
||||||
if(pattern->pat == pat) {
|
|
||||||
PathPart part(pre, pattern, post);
|
|
||||||
g_path_parts.push_back(part);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!found) {
|
|
||||||
throw std::logic_error(std::string("unknown pattern '") + pat + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign defaults to params that need them
|
|
||||||
const bool ignore = vm.count("ignore") != 0;
|
|
||||||
std::vector<std::string> excludes;
|
|
||||||
if(vm.count("exclude"))
|
|
||||||
excludes = vm["exclude"].as< std::vector<std::string> >();
|
|
||||||
long limit_depth = LONG_MAX;
|
|
||||||
if(vm.count("limit-depth")) {
|
|
||||||
limit_depth = vm["limit-depth"].as<long>();
|
|
||||||
// Boost program_options doesn't work with unsigned, so do it manually
|
|
||||||
if( limit_depth < 0 )
|
|
||||||
throw std::logic_error(std::string("recursion depth limit must be positive"));
|
|
||||||
}
|
|
||||||
std::string dups = "duplicates";
|
|
||||||
if(vm.count("dups"))
|
|
||||||
dups = vm["dups"].as<std::string>();
|
|
||||||
const fs::path dups_dir = dest_dir / dups;
|
|
||||||
|
|
||||||
std::string unsorted = "unsorted";
|
|
||||||
if(vm.count("unsorted"))
|
|
||||||
unsorted = vm["unsorted"].as<std::string>();
|
|
||||||
const fs::path unsorted_dir = dest_dir / unsorted;
|
|
||||||
|
|
||||||
ProcessParams params = {
|
|
||||||
dest_dir,
|
|
||||||
dry_run,
|
|
||||||
(vm.count("ignore-dups") != 0 || ignore),
|
|
||||||
(vm.count("ignore-unsorted") != 0 || ignore),
|
|
||||||
vm.count("force") != 0,
|
|
||||||
vm.count("rename") != 0,
|
|
||||||
vm.count("symlink") != 0,
|
|
||||||
vm.count("verify") != 0,
|
|
||||||
vm.count("move") != 0,
|
|
||||||
limit_depth,
|
|
||||||
dups_dir,
|
|
||||||
unsorted_dir,
|
|
||||||
excludes,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
};
|
|
||||||
|
|
||||||
process_directory(source_dir, 0, params);
|
|
||||||
|
|
||||||
std::string op = "copied";
|
|
||||||
if(params.symlink)
|
|
||||||
op = "linked";
|
|
||||||
else if(params.move)
|
|
||||||
op = "moved";
|
|
||||||
|
|
||||||
if(dry_run)
|
|
||||||
op = std::string("would be ") + op;
|
|
||||||
|
|
||||||
if(g_neednewline)
|
|
||||||
std::cout << "\n";
|
|
||||||
|
|
||||||
std::cout << "\n" << params.ok_count << " files " << op << "\n";
|
|
||||||
std::cout << " " << params.dups_count << " duplicates\n";
|
|
||||||
std::cout << " " << params.unsorted_count << " unsorted\n";
|
|
||||||
if(params.dups_ignored_count)
|
|
||||||
std::cout << params.dups_ignored_count << " duplicates ignored\n";
|
|
||||||
if(params.unsorted_ignored_count)
|
|
||||||
std::cout << params.unsorted_ignored_count << " unsorted ignored\n";
|
|
||||||
if(params.dir_ex_count)
|
|
||||||
std::cout << params.dir_ex_count << " directories excluded\n";
|
|
||||||
if(params.file_ex_count)
|
|
||||||
std::cout << params.file_ex_count << " files excluded\n";
|
|
||||||
if(params.dir_err_count)
|
|
||||||
std::cout << params.dir_err_count << " directory errors\n";
|
|
||||||
if(params.file_err_count)
|
|
||||||
std::cout << params.file_err_count << " file errors\n";
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
catch (Exiv2::AnyError& e) {
|
|
||||||
error(e, std::string("Aborting"));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
catch(std::logic_error& e) {
|
|
||||||
error(e, "");
|
|
||||||
usage_header(argv[0]);
|
|
||||||
std::cout << argv[0] << " -h for more help" << std::endl;
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
catch(std::exception& e) {
|
|
||||||
error(e, "Aborting");
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::regex uregex("(.*?)\\(([[:digit:]]{1,2})\\)$");
|
|
||||||
|
|
||||||
fs::path uniquify(const fs::path &dest)
|
|
||||||
{
|
|
||||||
std::string ext = dest.extension().string();
|
|
||||||
std::string fname = dest.stem().string();
|
|
||||||
fs::path parent = dest.parent_path();
|
|
||||||
|
|
||||||
unsigned number = 1;
|
|
||||||
std::string newfname;
|
|
||||||
fs::path newdest;
|
|
||||||
|
|
||||||
boost::smatch match;
|
|
||||||
if(boost::regex_search(fname, match, uregex)) {
|
|
||||||
// Matches are indexes into fname, so don't change it while reading values
|
|
||||||
newfname = match[1];
|
|
||||||
number = boost::lexical_cast<short>(match[2]);
|
|
||||||
fname = newfname;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
newfname = fname + "(" + boost::lexical_cast<std::string>(++number) + ")" + ext;
|
|
||||||
newdest = parent / newfname;
|
|
||||||
} while(fs::exists(newdest));
|
|
||||||
|
|
||||||
return newdest;
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_directory(const fs::path &directory, const long depth,
|
|
||||||
ProcessParams ¶ms)
|
|
||||||
{
|
|
||||||
// Exclude entire directories
|
|
||||||
bool exclude = false;
|
|
||||||
std::vector<std::string>::const_iterator x_iter = params.excludes.begin();
|
|
||||||
std::vector<std::string>::const_iterator x_end = params.excludes.end();
|
|
||||||
for( ; x_iter != x_end; ++x_iter ) {
|
|
||||||
if(boost::contains(directory.string(), *x_iter)) {
|
|
||||||
exclude = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(exclude) {
|
|
||||||
info(std::string("excluding directory: ") + directory.string() +
|
|
||||||
" matched: " + *x_iter);
|
|
||||||
++params.dir_ex_count;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs::directory_iterator p_iter(directory), p_end;
|
|
||||||
for( ; p_iter != p_end; ++p_iter) {
|
|
||||||
if( is_directory(*p_iter) ) {
|
|
||||||
// recurse if we haven't hit the limit
|
|
||||||
if(depth < params.limit_depth)
|
|
||||||
process_directory(p_iter->path(), depth + 1, params);
|
|
||||||
else {
|
|
||||||
info(std::string("depth reached, skipping: ") +
|
|
||||||
p_iter->path().string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if( is_regular_file(*p_iter) ) {
|
|
||||||
|
|
||||||
// Check again for excluding file names
|
|
||||||
exclude = false;
|
|
||||||
x_iter = params.excludes.begin();
|
|
||||||
for( ; x_iter != x_end; ++x_iter ) {
|
|
||||||
if(boost::contains(p_iter->path().string(), *x_iter)) {
|
|
||||||
exclude = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(exclude) {
|
|
||||||
info(std::string("excluding file: ") + p_iter->path().string() +
|
|
||||||
" matched: " + *x_iter);
|
|
||||||
++params.file_ex_count;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fs::path dest_subdir = build_dest(*p_iter);
|
|
||||||
fs::path dest_file;
|
|
||||||
if(!dest_subdir.empty())
|
|
||||||
dest_file = params.dest_dir / dest_subdir;
|
|
||||||
else if(params.ignore_unsorted) {
|
|
||||||
info(std::string("ignoring unsorted: ") + p_iter->path().string());
|
|
||||||
++params.unsorted_ignored_count;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
info(std::string("unsorted file (missing metadata): ") + p_iter->path().string());
|
|
||||||
dest_file = params.unsorted_dir;
|
|
||||||
++params.unsorted_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
dest_file /= p_iter->path().filename();
|
|
||||||
|
|
||||||
if(fs::exists(dest_file)) {
|
|
||||||
if(params.ignore_dups) {
|
|
||||||
info(std::string("ignoring: ") + p_iter->path().string() +
|
|
||||||
" duplicates: " + dest_file.string());
|
|
||||||
++params.dups_ignored_count;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(params.force) {
|
|
||||||
info(std::string("force removing: ") + dest_file.string() + " for: "
|
|
||||||
+ p_iter->path().string());
|
|
||||||
if(!params.dry_run)
|
|
||||||
fs::remove(dest_file);
|
|
||||||
}
|
|
||||||
else if(params.rename) {
|
|
||||||
info(std::string("renaming: ") + p_iter->path().string() +
|
|
||||||
" duplicates: " + dest_file.string());
|
|
||||||
dest_file = uniquify(dest_file);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
info(std::string("duplicate file: ") + p_iter->path().string() +
|
|
||||||
" of: " + dest_file.string());
|
|
||||||
dest_file = params.dups_dir / dest_subdir / p_iter->path().filename();
|
|
||||||
// Ugh, more dup possibilities
|
|
||||||
if(fs::exists(dest_file)) {
|
|
||||||
info(std::string("renaming: ") + p_iter->path().string() +
|
|
||||||
" duplicates: " + dest_file.string());
|
|
||||||
dest_file = uniquify(dest_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++params.dups_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!params.dry_run)
|
|
||||||
fs::create_directories(dest_file.parent_path());
|
|
||||||
|
|
||||||
if(params.symlink) {
|
|
||||||
info(std::string("linking from: ") + p_iter->path().string() +
|
|
||||||
" to: " + dest_file.string());
|
|
||||||
if(!params.dry_run) {
|
|
||||||
// The target of a symlink must be either absolute (aka complete) or
|
|
||||||
// relative to the location of the link. Easiest solution is to make
|
|
||||||
// a complete path.
|
|
||||||
fs::path target;
|
|
||||||
if(p_iter->path().is_complete())
|
|
||||||
target = p_iter->path();
|
|
||||||
else
|
|
||||||
target = fs::initial_path() / p_iter->path();
|
|
||||||
fs::create_symlink(target, dest_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
info(std::string("copying from: ") + p_iter->path().string() +
|
|
||||||
" to: " + dest_file.string());
|
|
||||||
if(!params.dry_run) {
|
|
||||||
// Copy the file and restore its write time (needed for posix)
|
|
||||||
std::time_t time = fs::last_write_time(*p_iter);
|
|
||||||
fs::copy_file(*p_iter, dest_file);
|
|
||||||
fs::last_write_time(dest_file, time);
|
|
||||||
if(params.verify) {
|
|
||||||
md5digest src_digest, dst_digest;
|
|
||||||
bool ok = md5sum(p_iter->path(), src_digest);
|
|
||||||
if(ok)
|
|
||||||
ok = md5sum(dest_file, dst_digest);
|
|
||||||
if(ok)
|
|
||||||
ok = (memcmp(src_digest,dst_digest, sizeof(md5digest))==0);
|
|
||||||
if(!ok) {
|
|
||||||
// Should probably find a more appropriate exception for this
|
|
||||||
throw std::runtime_error(std::string("File verification failed: '")
|
|
||||||
+ p_iter->path().string() + "' differs from '" +
|
|
||||||
dest_file.string() + "'");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
info(std::string("verification passed"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(params.move) {
|
|
||||||
info(std::string("removing: ") + p_iter->path().string());
|
|
||||||
if(!params.dry_run)
|
|
||||||
fs::remove(*p_iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!g_verbose && (params.ok_count % DOT_EVERY)==0) {
|
|
||||||
std::cout << "." << std::flush;
|
|
||||||
g_neednewline = true;
|
|
||||||
}
|
|
||||||
++params.ok_count;
|
|
||||||
}
|
|
||||||
catch(fs::filesystem_error& e) {
|
|
||||||
error(e, std::string("skipping file: " + p_iter->path().string()));
|
|
||||||
++params.file_err_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(fs::filesystem_error& e) {
|
|
||||||
error(e, std::string("skipping directory: " + directory.string()));
|
|
||||||
++params.dir_err_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue