#pragma comment(lib, "winmm")
#include <fcntl.h> // _O_WTEXT
#include <io.h> // _setmode
#include <stdio.h> // _fileno
#include <string.h> // memcmp
#include <tchar.h>
#include <Windows.h>
#include <map>
#include <set>
#include <string>
#define BUF_SIZE (10 * 1024 * 1024)
// 型定義
typedef struct { // ファイル情報構造体
std::wstring strPath;
DWORD offset;
int iDelFlag; // 削除フラグ
int iDupFlag; // 重複フラグ
} FileInfo;
typedef std::multimap<DWORD, FileInfo> mmfsfi;
typedef std::set<std::wstring> setstr;
// 関数プロトタイプ宣言
int files(const _TCHAR *ptcDir);
int filecmp(LPCTSTR ptcFile1, LPCTSTR ptcFile2, DWORD offset1, DWORD offset2, DWORD size);
int filedel(void);
BOOL LoadMP3(LPCTSTR path, DWORD &dataOffset, DWORD &dataSize);
// グローバル変数
mmfsfi g_mmap;
setstr g_set;
int g_iDelFlag = 0;
//==============================================================================
int _tmain(int argc, _TCHAR *argv[])
{
mmfsfi::iterator it;
mmfsfi::iterator itTarget;
FileInfo *pfi;
FileInfo *pfiTarget;
_TCHAR atcDir[_MAX_PATH];
size_t size;
int iCount;
int i;
// BOMなしUTF-16LE
_setmode(_fileno(stdout), _O_WTEXT);
_setmode(_fileno(stderr), _O_WTEXT);
if (argc < 2) {
_ftprintf(stderr, _T("usage: fdmp3 [-d] dir [...]\n"));
return 1;
}
for (i = 1; i < argc; i++) {
if (argv[i][0] == _T('-')) {
if (argv[i][1] == _T('d')) {
g_iDelFlag = 1;
}
} else {
_tcscpy_s(atcDir, argv[i]);
size = _tcslen(atcDir);
if (0 < size && atcDir[size - 1] == _T('\\')) {
atcDir[size - 1] = _T('\0');
}
files(atcDir);
}
}
for (it = g_mmap.begin(); it != g_mmap.end(); it++) {
pfi = &(it->second);
if (pfi->iDupFlag) continue;
iCount = 0;
for (itTarget = it; ++itTarget != g_mmap.end(); ) {
if (itTarget->first != it->first) break;
pfiTarget = &(itTarget->second);
if (pfiTarget->iDupFlag) continue;
int ret = filecmp(pfi->strPath.c_str(), pfiTarget->strPath.c_str(),
pfi->offset, pfiTarget->offset, it->first);
if (ret == 0) {
pfiTarget->iDupFlag = 1;
if (iCount == 0) {
_tprintf(_T("\n%u KB (%u B)\n"),
(it->first + 1023) / 1024, it->first);
_tprintf(_T("%s\n"), pfi->strPath.c_str());
}
_tprintf(_T("%s\n"), pfiTarget->strPath.c_str());
iCount++;
}
}
}
if (g_iDelFlag) filedel();
return 0;
}
//------------------------------------------------------------------------------
int files(const TCHAR *ptcDir)
{
size_t sizeDir = _tcslen(ptcDir);
if (_MAX_PATH <= sizeDir + 4) {
_ftprintf(stderr, _T("error: パスが長過ぎます。%u[%s]\n"), sizeDir, ptcDir);
return -1;
}
TCHAR atcPath[_MAX_PATH];
_stprintf_s(atcPath, _T("%s\\*.*"), ptcDir);
FileInfo fi;
fi.iDelFlag = g_iDelFlag;
fi.iDupFlag = 0;
_wfinddata_t fd;
intptr_t handle = _tfindfirst(atcPath, &fd);
if (handle == -1) {
_ftprintf(stderr, _T("error: _tfindfirst[%s]\n"), ptcDir);
return -1;
}
bool empty = true;
do {
if (_MAX_PATH <= sizeDir + 1 + _tcslen(fd.name)) {
_ftprintf(stderr, _T("error: パスが長過ぎます。[%s][%s]\n"),
ptcDir, fd.name);
continue;
}
_stprintf_s(atcPath, _T("%s\\%s"), ptcDir, fd.name);
if (fd.attrib & _A_SUBDIR) {
if (_tcscmp(fd.name, _T(".")) && _tcscmp(fd.name, _T(".."))) {
files(atcPath);
empty = false;
}
} else {
size_t len = _tcslen(fd.name);
if (len <= 4 || _tcsicmp(fd.name + len - 4, _T(".mp3"))) continue;
std::pair<setstr::iterator, bool> pair = g_set.insert(atcPath);
if (pair.second == true) {
DWORD size;
if (LoadMP3(atcPath, fi.offset, size)) {
fi.strPath = atcPath;
g_mmap.insert(mmfsfi::value_type(size, fi));
//_tprintf(_T("%u %u %s\n"), fi.offset, size, atcPath);
}
}
empty = false;
}
} while (_tfindnext(handle, &fd) == 0);
_findclose(handle);
if (empty) {
_ftprintf(stderr, _T("empty: [%s]\n"), ptcDir);
}
return 0;
}
//------------------------------------------------------------------------------
int filecmp(LPCTSTR ptcFile1, LPCTSTR ptcFile2, DWORD offset1, DWORD offset2, DWORD size)
{
static char acBuf1[BUF_SIZE];
static char acBuf2[BUF_SIZE];
FILE *pFile1 = NULL;
FILE *pFile2 = NULL;
int iRetVal = -1;
if (_tfopen_s(&pFile1, ptcFile1, _T("rb")) != 0) {
_ftprintf(stderr, _T("%s を開けません\n"), ptcFile1);
goto Exit;
}
if (_tfopen_s(&pFile2, ptcFile2, _T("rb")) != 0) {
_ftprintf(stderr, _T("%s を開けません\n"), ptcFile2);
goto Exit;
}
// offset分読み飛ばす
fseek(pFile1, offset1, SEEK_SET);
fseek(pFile2, offset2, SEEK_SET);
for (size_t read = 0; ; ) {
size_t count = min(size - read, BUF_SIZE);
size_t sizeRead1 = fread(acBuf1, 1, count, pFile1);
if (ferror(pFile1) != 0 || sizeRead1 != count) {
_ftprintf(stderr, _T("error: fread[%s]\n"), ptcFile1);
goto Exit;
}
size_t sizeRead2 = fread(acBuf2, 1, count, pFile2);
if (ferror(pFile2) != 0 || sizeRead2 != count) {
_ftprintf(stderr, _T("error: fread[%s]\n"), ptcFile2);
goto Exit;
}
if (memcmp(acBuf1, acBuf2, sizeRead1) != 0) {
iRetVal = 1;
break;
}
read += count;
if (size <= read) {
iRetVal = 0;
break;
}
}
Exit:
if (pFile2) fclose(pFile2);
if (pFile1) fclose(pFile1);
return iRetVal;
}
//------------------------------------------------------------------------------
int filedel(void)
{
mmfsfi::iterator it;
FileInfo *pfi;
FILE *pFile;
if (_tfopen_s(&pFile, _T("finddup.log"), _T("wt,ccs=UNICODE"))) {
_ftprintf(stderr, _T("ログファイルの作成に失敗しました\n"));
return -1;
}
for(it = g_mmap.begin(); it != g_mmap.end(); it++) {
pfi = &(it->second);
if (pfi->iDupFlag && pfi->iDelFlag) {
_ftprintf(pFile, _T("del \"%s\"\n"), pfi->strPath.c_str());
}
}
fclose(pFile);
return 0;
}
BOOL LoadMP3(LPCTSTR path, DWORD &dataOffset, DWORD &dataSize)
{
// MP3ファイル部
HMMIO hmmio = mmioOpen((LPTSTR)path, NULL, MMIO_READ);
if (hmmio == NULL) {
_ftprintf(stderr, _T("%s を開けません\n"), path);
return FALSE;
}
DWORD dwFile = mmioSeek(hmmio, 0, SEEK_END);
LPBYTE lpFile = new BYTE[dwFile];
mmioSeek(hmmio, 0, SEEK_SET);
mmioRead(hmmio, (HPSTR)lpFile, dwFile);
mmioClose(hmmio, 0);
// MP3データ部
dataOffset = 0;
dataSize = dwFile;
// ID3v1
if (128 <= dwFile && memcmp(lpFile + dwFile - 128, "TAG", 3) == 0) {
dataSize -= 128;
}
// ID3v2
if (10 <= dataSize && memcmp(lpFile, "ID3", 3) == 0) {
LPBYTE size = lpFile + 6;
DWORD dwTagSize = ((size[0]<<21)|(size[1]<<14)|(size[2]<<7)|size[3]) + 10;
dataOffset = dwTagSize;
dataSize -= dwTagSize;
}
delete[] lpFile;
return TRUE;
}