478 lines
14 KiB
C++
478 lines
14 KiB
C++
#include "main.h"
|
|
#include "ml_local.h"
|
|
#include "api_mldb.h"
|
|
#include <commctrl.h>
|
|
#include "resource.h"
|
|
#include "../replicant/nu/ns_wc.h"
|
|
#include "../nde/nde.h"
|
|
#include "../Agave/Language/api_language.h"
|
|
#include "..\..\General\gen_ml/config.h"
|
|
#include "..\..\General\gen_ml/gaystring.h"
|
|
#include "time.h"
|
|
#include "../winamp/in2.h"
|
|
#include "../Winamp/strutil.h"
|
|
#include <shlwapi.h>
|
|
#include <strsafe.h>
|
|
|
|
|
|
bool skipTitleInfo=false;
|
|
|
|
static int getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, size_t len)
|
|
{
|
|
dest[0]=0;
|
|
return AGAVE_API_METADATA->GetExtendedFileInfo(filename, metadata, dest, len);
|
|
}
|
|
|
|
static time_t FileTimeToUnixTime(FILETIME *ft)
|
|
{
|
|
ULARGE_INTEGER end;
|
|
memcpy(&end,ft,sizeof(end));
|
|
end.QuadPart -= 116444736000000000;
|
|
end.QuadPart /= 10000000; // 100ns -> seconds
|
|
return (time_t)end.QuadPart;
|
|
}
|
|
|
|
void makeFilename2W(const wchar_t *filename, wchar_t *filename2, int filename2_len)
|
|
{
|
|
filename2[0]=0;
|
|
GetLongPathNameW(filename, filename2, filename2_len);
|
|
if (!_wcsicmp(filename,filename2)) filename2[0]=0;
|
|
}
|
|
|
|
void makeFilename2(const char *filename, char *filename2, int filename2_len)
|
|
{
|
|
filename2[0]=0;
|
|
GetLongPathNameA(filename, filename2, filename2_len);
|
|
if (!stricmp(filename,filename2)) filename2[0]=0;
|
|
}
|
|
|
|
static __int64 FileSize64(HANDLE file)
|
|
{
|
|
LARGE_INTEGER position;
|
|
position.QuadPart=0;
|
|
position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart);
|
|
|
|
if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
|
|
return INVALID_FILE_SIZE;
|
|
else
|
|
return position.QuadPart;
|
|
}
|
|
|
|
static void GetFileSizeAndTime(const wchar_t *filename, __int64 *file_size, time_t *file_time)
|
|
{
|
|
WIN32_FILE_ATTRIBUTE_DATA file_data;
|
|
if (GetFileAttributesExW(filename, GetFileExInfoStandard, &file_data) == FALSE)
|
|
{
|
|
// GetFileAttributesEx failed. that sucks, let's try something else
|
|
HANDLE hFile=CreateFileW(filename,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
FILETIME lt = {0};
|
|
if (GetFileTime(hFile,NULL,NULL,<))
|
|
{
|
|
*file_time=FileTimeToUnixTime(<);
|
|
}
|
|
*file_size=FileSize64(hFile);
|
|
CloseHandle(hFile);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// success
|
|
*file_time = FileTimeToUnixTime(&file_data.ftLastWriteTime);
|
|
LARGE_INTEGER size64;
|
|
size64.LowPart = file_data.nFileSizeLow;
|
|
size64.HighPart = file_data.nFileSizeHigh;
|
|
*file_size = size64.QuadPart;
|
|
}
|
|
}
|
|
|
|
int FindFileInDatabase(nde_scanner_t s, int fieldId, const wchar_t *filename, wchar_t alternate[MAX_PATH])
|
|
{
|
|
alternate[0]=0;
|
|
|
|
makeFilename2W(filename,alternate,MAX_PATH);
|
|
if (alternate[0])
|
|
{
|
|
if (NDE_Scanner_LocateFilename(s, fieldId, FIRST_RECORD, alternate))
|
|
{
|
|
return 2;
|
|
}
|
|
}
|
|
if (NDE_Scanner_LocateFilename(s, fieldId, FIRST_RECORD, filename))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void GetOptionalField(nde_scanner_t s, const wchar_t *filename, const wchar_t *field, unsigned char field_id)
|
|
{
|
|
wchar_t tmp[1024]={0};
|
|
if (getFileInfoW(filename,field,tmp,sizeof(tmp)/sizeof(wchar_t)))
|
|
{
|
|
if(tmp[0])
|
|
{
|
|
tmp[1023]=0; // just in case
|
|
db_setFieldStringW(s, field_id,tmp);
|
|
}
|
|
else
|
|
{
|
|
db_removeField(s, field_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void GetOptionalFieldInt(nde_scanner_t s, const wchar_t *filename, const wchar_t *field, unsigned char field_id)
|
|
{
|
|
wchar_t tmp[128]={0};
|
|
if (getFileInfoW(filename,field,tmp,sizeof(tmp)/sizeof(wchar_t)))
|
|
{
|
|
if(tmp[0])
|
|
{
|
|
tmp[127]=0; // just in case
|
|
db_setFieldInt(s,field_id,_wtoi(tmp));
|
|
}
|
|
else
|
|
{
|
|
db_removeField(s, field_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void GetNonBlankFieldInt(nde_scanner_t s, const wchar_t *filename, const wchar_t *field, unsigned char field_id)
|
|
{
|
|
wchar_t tmp[128]={0};
|
|
if (getFileInfoW(filename,field,tmp,sizeof(tmp)/sizeof(wchar_t)))
|
|
{
|
|
if(tmp[0])
|
|
{
|
|
tmp[127]=0; // just in case
|
|
db_setFieldInt(s,field_id,_wtoi(tmp));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// return values
|
|
// 0 - error
|
|
// 1 - success
|
|
// -2 - record not found (in update only mode)
|
|
int addFileToDb(const wchar_t *filename, int onlyupdate, int use_metadata, int guess_mode, int playcnt, int lastplay, bool force)
|
|
{
|
|
if (!_wcsicmp(PathFindExtensionW(filename), L".cda"))
|
|
return 0;
|
|
|
|
__int64 file_size=INVALID_FILE_SIZE;
|
|
time_t file_time=0;
|
|
GetFileSizeAndTime(filename, &file_size, &file_time);
|
|
if (file_size == INVALID_FILE_SIZE || file_size == 0)
|
|
return 0;
|
|
|
|
openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
|
|
EnterCriticalSection(&g_db_cs);
|
|
|
|
nde_scanner_t s = NDE_Table_CreateScanner(g_table);
|
|
|
|
wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
|
|
int found = FindFileInDatabase(s, MAINTABLE_ID_FILENAME, filename, filename2);
|
|
|
|
if (found) // For updating
|
|
{
|
|
// if an update wasn't forced, see if the file's timestamp or filesize have changed
|
|
if (!force && NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILESIZE))
|
|
{
|
|
nde_field_t f=NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILETIME);
|
|
if (f && file_time <= NDE_IntegerField_GetValue(f))
|
|
{
|
|
f=NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_LASTUPDTIME);
|
|
if (f && file_time <= NDE_IntegerField_GetValue(f))
|
|
{
|
|
NDE_Table_DestroyScanner(g_table, s);
|
|
LeaveCriticalSection(&g_db_cs);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
NDE_Scanner_Edit(s);
|
|
if (found == 1 && filename2[0]) db_setFieldStringW(s,MAINTABLE_ID_FILENAME,filename2); // if we have a better filename, update it
|
|
nde_field_t f=NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_PLAYCOUNT);
|
|
int cnt = f?NDE_IntegerField_GetValue(f):0;
|
|
if (!cnt)
|
|
db_setFieldInt(s,MAINTABLE_ID_PLAYCOUNT,0);
|
|
}
|
|
else // Adding an entry from scratch
|
|
{
|
|
if (onlyupdate)
|
|
{
|
|
NDE_Table_DestroyScanner(g_table, s);
|
|
LeaveCriticalSection(&g_db_cs);
|
|
|
|
// Issue a wasabi system callback after we have successfully updated a file in the ml database
|
|
WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_UPDATED_EXTERNAL, (size_t)filename, 0);
|
|
return -2;
|
|
}
|
|
// new file
|
|
NDE_Scanner_New(s);
|
|
db_setFieldStringW(s,MAINTABLE_ID_FILENAME,filename2[0]?filename2:filename);
|
|
db_setFieldInt(s,MAINTABLE_ID_PLAYCOUNT,playcnt);
|
|
if (lastplay)
|
|
db_setFieldInt(s,MAINTABLE_ID_LASTPLAY,lastplay);
|
|
db_setFieldInt(s,MAINTABLE_ID_DATEADDED, (int)time(NULL));
|
|
}
|
|
|
|
int hasttitle=0, hastartist=0, hastalbum=0, hasttrack=0;
|
|
int hastyear=0, hastlength=0;
|
|
int hasdisc=0, hasalbumartist=0;
|
|
int hasttype=0, hasdiscs=0, hastracks=0, hasbitrate=0;
|
|
int the_length_sec=0;
|
|
wchar_t m_artist[1024] = {0}, tmp[1024] = {0};
|
|
|
|
if(getFileInfoW(filename, DB_FIELDNAME_type, tmp, sizeof(tmp) / sizeof(wchar_t)) )
|
|
{
|
|
if(tmp[0]) { int type=_wtoi(tmp); db_setFieldInt(s,MAINTABLE_ID_TYPE,type); hasttype++; }
|
|
}
|
|
|
|
if (getFileInfoW(filename, DB_FIELDNAME_length,tmp,sizeof(tmp)/sizeof(wchar_t)))
|
|
{
|
|
if(tmp[0]) { db_setFieldInt(s,MAINTABLE_ID_LENGTH,the_length_sec=(_wtoi(tmp)/1000)); hastlength++; }
|
|
}
|
|
|
|
if (getFileInfoW(filename, DB_FIELDNAME_bitrate,tmp,sizeof(tmp)/sizeof(wchar_t)))
|
|
{
|
|
if(tmp[0]) { db_setFieldInt(s,MAINTABLE_ID_BITRATE,_wtoi(tmp)); hasbitrate++; }
|
|
}
|
|
|
|
if(use_metadata && getFileInfoW(filename, DB_FIELDNAME_title,tmp,sizeof(tmp)/sizeof(wchar_t)))
|
|
{
|
|
if(tmp[0])
|
|
{
|
|
db_setFieldStringW(s,MAINTABLE_ID_TITLE,tmp);
|
|
hasttitle++;
|
|
}
|
|
|
|
getFileInfoW( filename, DB_FIELDNAME_artist, tmp, sizeof( tmp ) / sizeof( wchar_t ) );
|
|
|
|
if(tmp[0])
|
|
{
|
|
StringCchCopyW(m_artist, 1024, tmp);
|
|
db_setFieldStringW(s,MAINTABLE_ID_ARTIST,tmp);
|
|
hastartist++;
|
|
}
|
|
|
|
getFileInfoW(filename, DB_FIELDNAME_album,tmp,sizeof(tmp)/sizeof(wchar_t));
|
|
|
|
if(tmp[0])
|
|
{
|
|
db_setFieldStringW(s,MAINTABLE_ID_ALBUM,tmp);
|
|
hastalbum++;
|
|
}
|
|
|
|
GetOptionalField(s, filename, DB_FIELDNAME_comment, MAINTABLE_ID_COMMENT);
|
|
|
|
getFileInfoW(filename, DB_FIELDNAME_year,tmp,sizeof(tmp)/sizeof(wchar_t));
|
|
if(tmp[0] && !wcsstr(tmp,L"__") && !wcsstr(tmp,L"/") && !wcsstr(tmp,L"\\") && !wcsstr(tmp,L".")) {
|
|
wchar_t *p=tmp;
|
|
while (p && *p)
|
|
{
|
|
if (*p == L'_') *p=L'0';
|
|
p++;
|
|
}
|
|
int y=_wtoi(tmp);
|
|
if(y!=0) { db_setFieldInt(s,MAINTABLE_ID_YEAR,_wtoi(tmp)); hastyear++; }
|
|
}
|
|
GetOptionalField(s, filename, DB_FIELDNAME_genre, MAINTABLE_ID_GENRE);
|
|
|
|
getFileInfoW(filename, DB_FIELDNAME_track,tmp,sizeof(tmp)/sizeof(wchar_t));
|
|
if(tmp[0])
|
|
{
|
|
int track, tracks;
|
|
ParseIntSlashInt(tmp, &track, &tracks);
|
|
if (track > 0)
|
|
{
|
|
db_setFieldInt(s,MAINTABLE_ID_TRACKNB,track);
|
|
hasttrack++;
|
|
}
|
|
if (tracks > 0)
|
|
{
|
|
db_setFieldInt(s,MAINTABLE_ID_TRACKS,tracks);
|
|
hastracks++;
|
|
}
|
|
}
|
|
getFileInfoW(filename, DB_FIELDNAME_disc,tmp,sizeof(tmp)/sizeof(wchar_t));
|
|
if(tmp[0])
|
|
{
|
|
int disc, discs;
|
|
ParseIntSlashInt(tmp, &disc, &discs);
|
|
if (disc > 0)
|
|
{
|
|
db_setFieldInt(s,MAINTABLE_ID_DISC,disc);
|
|
hasdisc++;
|
|
}
|
|
if (discs > 0)
|
|
{
|
|
db_setFieldInt(s,MAINTABLE_ID_DISCS,discs);
|
|
hasdiscs++;
|
|
}
|
|
}
|
|
getFileInfoW(filename, DB_FIELDNAME_albumartist,tmp,sizeof(tmp)/sizeof(wchar_t));
|
|
if(tmp[0]) { db_setFieldStringW(s,MAINTABLE_ID_ALBUMARTIST,tmp); hasalbumartist++; }
|
|
GetOptionalField(s, filename, DB_FIELDNAME_publisher, MAINTABLE_ID_PUBLISHER);
|
|
GetOptionalField(s, filename, DB_FIELDNAME_composer, MAINTABLE_ID_COMPOSER);
|
|
GetOptionalField(s, filename, DB_FIELDNAME_replaygain_album_gain, MAINTABLE_ID_ALBUMGAIN);
|
|
GetOptionalField(s, filename, DB_FIELDNAME_replaygain_track_gain, MAINTABLE_ID_TRACKGAIN);
|
|
GetOptionalFieldInt(s, filename, DB_FIELDNAME_bpm, MAINTABLE_ID_BPM);
|
|
GetOptionalField(s, filename, DB_FIELDNAME_GracenoteFileID, MAINTABLE_ID_GRACENOTEFILEID);
|
|
GetOptionalField(s, filename, DB_FIELDNAME_GracenoteExtData, MAINTABLE_ID_GRACENOTEEXTDATA);
|
|
GetOptionalFieldInt(s, filename, DB_FIELDNAME_lossless, MAINTABLE_ID_LOSSLESS);
|
|
GetOptionalField(s, filename, DB_FIELDNAME_category, MAINTABLE_ID_CATEGORY);
|
|
GetOptionalField(s, filename, DB_FIELDNAME_codec, MAINTABLE_ID_CODEC);
|
|
GetOptionalField(s, filename, DB_FIELDNAME_director, MAINTABLE_ID_DIRECTOR);
|
|
GetOptionalField(s, filename, DB_FIELDNAME_producer, MAINTABLE_ID_PRODUCER);
|
|
GetOptionalFieldInt(s, filename, DB_FIELDNAME_width, MAINTABLE_ID_WIDTH);
|
|
GetOptionalFieldInt(s, filename, DB_FIELDNAME_height, MAINTABLE_ID_HEIGHT);
|
|
if (g_config->ReadInt(L"writeratings", 0))
|
|
GetOptionalFieldInt(s, filename, DB_FIELDNAME_rating, MAINTABLE_ID_RATING);
|
|
else
|
|
GetNonBlankFieldInt(s, filename, DB_FIELDNAME_rating, MAINTABLE_ID_RATING);
|
|
GetOptionalField(s, filename, L"mime", MAINTABLE_ID_MIMETYPE);
|
|
}
|
|
|
|
int guessmode = guess_mode;
|
|
if (guessmode != 2 && ((!hasttitle) + (!hastartist) + (!hastalbum) + (!hasttrack) >= (g_guessifany ? 1 : 4)))
|
|
{
|
|
int tn = 0;
|
|
wchar_t *artist = 0, *album = 0, *title = 0, *guessbuf = 0;
|
|
|
|
if (guessmode==1)
|
|
{
|
|
guessbuf = _wcsdup(filename2[0] ? filename2 : filename);
|
|
|
|
wchar_t *p=scanstr_backW(guessbuf, L"\\/.", guessbuf);
|
|
if (*p == '.')
|
|
{
|
|
*p = 0;
|
|
p = scanstr_backW(guessbuf, L"\\/", guessbuf);
|
|
}
|
|
|
|
if (p > guessbuf)
|
|
{
|
|
*p = 0;
|
|
title = p+1;
|
|
p=scanstr_backW(guessbuf, L"\\/", guessbuf);
|
|
if (p > guessbuf)
|
|
{
|
|
*p = 0;
|
|
album = p+1;
|
|
p=scanstr_backW(guessbuf,L"\\/", guessbuf);
|
|
if (p > guessbuf)
|
|
{
|
|
*p = 0;
|
|
artist = p+1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
guessbuf = guessTitles(filename2[0] ? filename2 : filename, &tn, &artist, &album, &title);
|
|
|
|
if (guessbuf)
|
|
{
|
|
if (!hasttitle && title) { hasttitle++; db_setFieldStringW(s,MAINTABLE_ID_TITLE,title); }
|
|
if (!hastartist && artist) { hastartist++; db_setFieldStringW(s,MAINTABLE_ID_ARTIST,artist); StringCbCopyW(m_artist, sizeof(m_artist), artist); }
|
|
if (!hastalbum && album) { hastalbum++; db_setFieldStringW(s,MAINTABLE_ID_ALBUM,album); }
|
|
if (!hasttrack && tn) { hasttrack++; db_setFieldInt(s,MAINTABLE_ID_TRACKNB,tn); }
|
|
free(guessbuf);
|
|
}
|
|
}
|
|
|
|
if (!hastlength || !hasttitle)
|
|
{
|
|
// try to query length and title using older GetFileInfo input plugin API
|
|
wchar_t ft[1024] = {0};
|
|
basicFileInfoStructW bi={0};
|
|
bi.filename=filename2[0]?filename2:filename;
|
|
bi.length=-1;
|
|
bi.title=ft;
|
|
bi.titlelen=1024;
|
|
skipTitleInfo=true;
|
|
LeaveCriticalSection(&g_db_cs); // benski> not actually sure if this is safe, but it prevents a deadlock
|
|
SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&bi,IPC_GET_BASIC_FILE_INFOW);
|
|
EnterCriticalSection(&g_db_cs);
|
|
skipTitleInfo=false;
|
|
if (!hastlength && bi.length >= 0)
|
|
{
|
|
hastlength=1;
|
|
db_setFieldInt(s,MAINTABLE_ID_LENGTH,the_length_sec=bi.length);
|
|
}
|
|
if (!hasttitle && ft[0])
|
|
{
|
|
hasttitle=1;
|
|
db_setFieldStringW(s,MAINTABLE_ID_TITLE,ft);
|
|
}
|
|
}
|
|
|
|
// set up default (empty) strings/values
|
|
if (!hasttitle) // title=filename
|
|
{
|
|
wchar_t *p = PathFindFileNameW(filename), *dup = _wcsdup(p);
|
|
if (dup)
|
|
{
|
|
PathRemoveExtensionW(dup);
|
|
db_setFieldStringW(s,MAINTABLE_ID_TITLE,dup);
|
|
free(dup);
|
|
}
|
|
}
|
|
|
|
if (!hastartist) db_removeField(s,MAINTABLE_ID_ARTIST);
|
|
if (!hastalbum) db_removeField(s,MAINTABLE_ID_ALBUM);
|
|
if (!hasttrack) db_removeField(s,MAINTABLE_ID_TRACKNB);
|
|
if (!hastracks) db_removeField(s,MAINTABLE_ID_TRACKS);
|
|
if (!hastyear) db_removeField(s,MAINTABLE_ID_YEAR);
|
|
if (!hastlength) db_removeField(s,MAINTABLE_ID_LENGTH);
|
|
if (!hasttype) db_setFieldInt(s,MAINTABLE_ID_TYPE,0); //audio
|
|
if (!hasdisc) db_removeField(s, MAINTABLE_ID_DISC);
|
|
if (!hasdiscs) db_removeField(s, MAINTABLE_ID_DISCS);
|
|
if (!hasalbumartist)
|
|
{
|
|
if (hastartist && g_config->ReadInt(L"artist_as_albumartist", 1))
|
|
db_setFieldStringW(s, MAINTABLE_ID_ALBUMARTIST, m_artist);
|
|
else
|
|
db_removeField(s, MAINTABLE_ID_ALBUMARTIST);
|
|
}
|
|
|
|
if (file_size != INVALID_FILE_SIZE)
|
|
{
|
|
db_setFieldInt64(s,MAINTABLE_ID_FILESIZE, file_size);
|
|
}
|
|
else db_removeField(s,MAINTABLE_ID_FILESIZE);
|
|
|
|
db_setFieldInt(s,MAINTABLE_ID_LASTUPDTIME, (int)time(NULL));
|
|
db_setFieldInt(s,MAINTABLE_ID_FILETIME, (int)file_time);
|
|
|
|
if (!hasbitrate && the_length_sec)
|
|
{
|
|
__int64 br =(file_size*8LL) / (__int64)the_length_sec;
|
|
br /= 1000;
|
|
db_setFieldInt(s,MAINTABLE_ID_BITRATE,(int)br);
|
|
}
|
|
|
|
NDE_Scanner_Post(s);
|
|
g_table_dirty++;
|
|
|
|
NDE_Table_DestroyScanner(g_table, s);
|
|
|
|
LeaveCriticalSection(&g_db_cs);
|
|
|
|
if (found)
|
|
{
|
|
// Issue a wasabi system callback after we have successfully updated a file in the ml database
|
|
WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_UPDATED, (size_t)filename, 0);
|
|
}
|
|
else
|
|
{
|
|
// Issue a wasabi system callback after we have successfully added a file in the ml database
|
|
WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_ADDED, (size_t)filename, 0);
|
|
}
|
|
|
|
return 1;
|
|
} |