Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F623187
metabundle.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
63 KB
Referenced Files
None
Subscribers
None
metabundle.cpp
View Options
// Max Howell <max.howell@methylblue.com>, (C) 2004
// Alexandre Pereira de Oliveira <aleprj@gmail.com>, (C) 2005, 2006
// Gábor Lehel <illissius@gmail.com>, (C) 2005, 2006
// Shane King <kde@dontletsstart.com>, (C) 2006
// Peter C. Ndikuwera <pndiku@gmail.com>, (C) 2006
// License: GNU General Public License V2
#define DEBUG_PREFIX "MetaBundle"
#include
<stdlib.h>
#include
<unistd.h>
#include
<stdio.h>
#include
<time.h>
#include
<sys/time.h>
#include
<sys/types.h>
#include
<fcntl.h>
#include
"amarok.h"
#include
"amarokconfig.h"
#include
"debug.h"
#include
"collectiondb.h"
#include
"metabundlesaver.h"
#include
<kapplication.h>
#include
<kfilemetainfo.h>
#include
<kio/global.h>
#include
<kio/job.h>
#include
<kio/jobclasses.h>
#include
<kio/netaccess.h>
#include
<kmdcodec.h>
#include
<qdeepcopy.h>
#include
<qfile.h>
//decodePath()
#include
<taglib/attachedpictureframe.h>
#include
<taglib/fileref.h>
#include
<taglib/id3v1genres.h>
//used to load genre list
#include
<taglib/mpegfile.h>
#include
<taglib/tag.h>
#include
<taglib/tstring.h>
#include
<taglib/tlist.h>
#include
<taglib/apetag.h>
#include
<taglib/id3v2tag.h>
#include
<taglib/id3v1tag.h>
#include
<taglib/mpcfile.h>
#include
<taglib/mpegfile.h>
#include
<taglib/oggfile.h>
#include
<taglib/oggflacfile.h>
#include
<taglib/vorbisfile.h>
#include
<taglib/flacfile.h>
#include
<taglib/textidentificationframe.h>
#include
<taglib/uniquefileidentifierframe.h>
#include
<taglib/xiphcomment.h>
#include
<config.h>
#ifdef HAVE_MP4V2
#include
"metadata/mp4/mp4file.h"
#include
"metadata/mp4/mp4tag.h"
#else
#include
"metadata/m4a/mp4file.h"
#include
"metadata/m4a/mp4itunestag.h"
#endif
#include
"lastfm.h"
#include
"metabundle.h"
#include
"podcastbundle.h"
namespace
Amarok
{
KURL
detachedKURL
(
const
KURL
&
url
)
{
KURL
urlCopy
;
if
(
!
url
.
isEmpty
())
urlCopy
=
KURL
(
url
.
url
());
return
urlCopy
;
}
}
MetaBundle
::
EmbeddedImage
::
EmbeddedImage
(
const
TagLib
::
ByteVector
&
data
,
const
TagLib
::
String
&
description
)
:
m_description
(
TStringToQString
(
description
)
)
{
m_data
.
duplicate
(
data
.
data
(),
data
.
size
()
);
}
const
QCString
&
MetaBundle
::
EmbeddedImage
::
hash
()
const
{
if
(
m_hash
.
isEmpty
()
)
{
m_hash
=
KMD5
(
m_data
).
hexDigest
();
}
return
m_hash
;
}
bool
MetaBundle
::
EmbeddedImage
::
save
(
const
QDir
&
dir
)
const
{
QFile
file
(
dir
.
filePath
(
hash
()
)
);
if
(
file
.
open
(
IO_WriteOnly
|
IO_Raw
)
)
{
const
Q_LONG
s
=
file
.
writeBlock
(
m_data
.
data
(),
m_data
.
size
()
);
if
(
s
>=
0
&&
Q_ULONG
(
s
)
==
m_data
.
size
()
)
{
debug
()
<<
"EmbeddedImage::save "
<<
file
.
name
()
<<
endl
;
return
true
;
}
file
.
remove
();
}
debug
()
<<
"EmbeddedImage::save failed! "
<<
file
.
name
()
<<
endl
;
return
false
;
}
/// These are untranslated and used for storing/retrieving XML playlist
const
QString
&
MetaBundle
::
exactColumnName
(
int
c
)
//static
{
// construct static qstrings to avoid constructing them all the time
static
QString
columns
[]
=
{
"Filename"
,
"Title"
,
"Artist"
,
"AlbumArtist"
,
"Composer"
,
"Year"
,
"Album"
,
"DiscNumber"
,
"Track"
,
"BPM"
,
"Genre"
,
"Comment"
,
"Directory"
,
"Type"
,
"Length"
,
"Bitrate"
,
"SampleRate"
,
"Score"
,
"Rating"
,
"PlayCount"
,
"LastPlayed"
,
"Mood"
,
"Filesize"
};
static
QString
error
(
"ERROR"
);
if
(
c
>=
0
&&
c
<
NUM_COLUMNS
)
return
columns
[
c
];
else
return
error
;
}
const
QString
MetaBundle
::
prettyColumnName
(
int
index
)
//static
{
switch
(
index
)
{
case
Filename
:
return
i18n
(
"Filename"
);
case
Title
:
return
i18n
(
"Title"
);
case
Artist
:
return
i18n
(
"Artist"
);
case
AlbumArtist
:
return
i18n
(
"Album Artist"
);
case
Composer
:
return
i18n
(
"Composer"
);
case
Year
:
return
i18n
(
"Year"
);
case
Album
:
return
i18n
(
"Album"
);
case
DiscNumber
:
return
i18n
(
"Disc Number"
);
case
Track
:
return
i18n
(
"Track"
);
case
Bpm
:
return
i18n
(
"BPM"
);
case
Genre
:
return
i18n
(
"Genre"
);
case
Comment
:
return
i18n
(
"Comment"
);
case
Directory
:
return
i18n
(
"Directory"
);
case
Type
:
return
i18n
(
"Type"
);
case
Length
:
return
i18n
(
"Length"
);
case
Bitrate
:
return
i18n
(
"Bitrate"
);
case
SampleRate
:
return
i18n
(
"Sample Rate"
);
case
Score
:
return
i18n
(
"Score"
);
case
Rating
:
return
i18n
(
"Rating"
);
case
PlayCount
:
return
i18n
(
"Play Count"
);
case
LastPlayed
:
return
i18n
(
"Column name"
,
"Last Played"
);
case
Mood
:
return
i18n
(
"Mood"
);
case
Filesize
:
return
i18n
(
"File Size"
);
}
return
"This is a bug."
;
}
int
MetaBundle
::
columnIndex
(
const
QString
&
name
)
{
for
(
int
i
=
0
;
i
<
NUM_COLUMNS
;
++
i
)
if
(
exactColumnName
(
i
).
lower
()
==
name
.
lower
()
)
return
i
;
return
-1
;
}
MetaBundle
::
MetaBundle
()
:
m_uniqueId
(
QString
::
null
)
,
m_year
(
Undetermined
)
,
m_discNumber
(
Undetermined
)
,
m_track
(
Undetermined
)
,
m_bpm
(
Undetermined
)
,
m_bitrate
(
Undetermined
)
,
m_length
(
Undetermined
)
,
m_sampleRate
(
Undetermined
)
,
m_score
(
Undetermined
)
,
m_rating
(
Undetermined
)
,
m_playCount
(
Undetermined
)
,
m_lastPlay
(
abs
(
Undetermined
)
)
,
m_filesize
(
Undetermined
)
,
m_moodbar
(
0
)
,
m_type
(
other
)
,
m_exists
(
true
)
,
m_isValidMedia
(
true
)
,
m_isCompilation
(
false
)
,
m_notCompilation
(
false
)
,
m_safeToSave
(
false
)
,
m_waitingOnKIO
(
0
)
,
m_tempSavePath
(
QString
::
null
)
,
m_origRenamedSavePath
(
QString
::
null
)
,
m_tempSaveDigest
(
0
)
,
m_saveFileref
(
0
)
,
m_podcastBundle
(
0
)
,
m_lastFmBundle
(
0
)
,
m_isSearchDirty
(
true
)
,
m_searchColumns
(
Undetermined
)
{
init
();
}
MetaBundle
::
MetaBundle
(
const
KURL
&
url
,
bool
noCache
,
TagLib
::
AudioProperties
::
ReadStyle
readStyle
,
EmbeddedImageList
*
images
)
:
m_url
(
url
)
,
m_uniqueId
(
QString
::
null
)
,
m_year
(
Undetermined
)
,
m_discNumber
(
Undetermined
)
,
m_track
(
Undetermined
)
,
m_bpm
(
Undetermined
)
,
m_bitrate
(
Undetermined
)
,
m_length
(
Undetermined
)
,
m_sampleRate
(
Undetermined
)
,
m_score
(
Undetermined
)
,
m_rating
(
Undetermined
)
,
m_playCount
(
Undetermined
)
,
m_lastPlay
(
abs
(
Undetermined
)
)
,
m_filesize
(
Undetermined
)
,
m_moodbar
(
0
)
,
m_type
(
other
)
,
m_exists
(
isFile
()
&&
QFile
::
exists
(
url
.
path
()
)
)
,
m_isValidMedia
(
false
)
,
m_isCompilation
(
false
)
,
m_notCompilation
(
false
)
,
m_safeToSave
(
false
)
,
m_waitingOnKIO
(
0
)
,
m_tempSavePath
(
QString
::
null
)
,
m_origRenamedSavePath
(
QString
::
null
)
,
m_tempSaveDigest
(
0
)
,
m_saveFileref
(
0
)
,
m_podcastBundle
(
0
)
,
m_lastFmBundle
(
0
)
,
m_isSearchDirty
(
true
)
,
m_searchColumns
(
Undetermined
)
{
if
(
exists
()
)
{
if
(
!
noCache
)
m_isValidMedia
=
CollectionDB
::
instance
()
->
bundleForUrl
(
this
);
if
(
!
isValidMedia
()
||
(
!
m_podcastBundle
&&
m_length
<=
0
)
)
readTags
(
readStyle
,
images
);
}
else
{
// if it's a podcast we might get some info this way
CollectionDB
::
instance
()
->
bundleForUrl
(
this
);
m_bitrate
=
m_length
=
m_sampleRate
=
Unavailable
;
}
}
//StreamProvider ctor
MetaBundle
::
MetaBundle
(
const
QString
&
title
,
const
QString
&
streamUrl
,
const
int
bitrate
,
const
QString
&
genre
,
const
QString
&
streamName
,
const
KURL
&
url
)
:
m_url
(
url
)
,
m_genre
(
genre
)
,
m_streamName
(
streamName
)
,
m_streamUrl
(
streamUrl
)
,
m_uniqueId
(
QString
::
null
)
,
m_year
(
0
)
,
m_discNumber
(
0
)
,
m_track
(
0
)
,
m_bpm
(
Undetermined
)
,
m_bitrate
(
bitrate
)
,
m_length
(
Irrelevant
)
,
m_sampleRate
(
Unavailable
)
,
m_score
(
Undetermined
)
,
m_rating
(
Undetermined
)
,
m_playCount
(
Undetermined
)
,
m_lastPlay
(
abs
(
Undetermined
)
)
,
m_filesize
(
Undetermined
)
,
m_moodbar
(
0
)
,
m_type
(
other
)
,
m_exists
(
true
)
,
m_isValidMedia
(
false
)
,
m_isCompilation
(
false
)
,
m_notCompilation
(
false
)
,
m_safeToSave
(
false
)
,
m_waitingOnKIO
(
0
)
,
m_tempSavePath
(
QString
::
null
)
,
m_origRenamedSavePath
(
QString
::
null
)
,
m_tempSaveDigest
(
0
)
,
m_saveFileref
(
0
)
,
m_podcastBundle
(
0
)
,
m_lastFmBundle
(
0
)
,
m_isSearchDirty
(
true
)
,
m_searchColumns
(
Undetermined
)
{
if
(
title
.
contains
(
'-'
)
)
{
m_title
=
title
.
section
(
'-'
,
1
,
1
).
stripWhiteSpace
();
m_artist
=
title
.
section
(
'-'
,
0
,
0
).
stripWhiteSpace
();
}
else
{
m_title
=
title
;
m_artist
=
streamName
;
//which is sort of correct..
}
}
MetaBundle
::
MetaBundle
(
const
MetaBundle
&
bundle
)
:
m_moodbar
(
0
)
{
*
this
=
bundle
;
}
MetaBundle
::~
MetaBundle
()
{
delete
m_podcastBundle
;
delete
m_lastFmBundle
;
if
(
m_moodbar
!=
0
)
delete
m_moodbar
;
}
MetaBundle
&
MetaBundle
::
operator
=
(
const
MetaBundle
&
bundle
)
{
m_url
=
bundle
.
m_url
;
m_title
=
bundle
.
m_title
;
m_artist
=
bundle
.
m_artist
;
m_albumArtist
=
bundle
.
m_albumArtist
;
m_composer
=
bundle
.
m_composer
;
m_album
=
bundle
.
m_album
;
m_comment
=
bundle
.
m_comment
;
m_genre
=
bundle
.
m_genre
;
m_streamName
=
bundle
.
m_streamName
;
m_streamUrl
=
bundle
.
m_streamUrl
;
m_uniqueId
=
bundle
.
m_uniqueId
;
m_year
=
bundle
.
m_year
;
m_discNumber
=
bundle
.
m_discNumber
;
m_track
=
bundle
.
m_track
;
m_bpm
=
bundle
.
m_bpm
;
m_bitrate
=
bundle
.
m_bitrate
;
m_length
=
bundle
.
m_length
;
m_sampleRate
=
bundle
.
m_sampleRate
;
m_score
=
bundle
.
m_score
;
m_rating
=
bundle
.
m_rating
;
m_playCount
=
bundle
.
m_playCount
;
m_lastPlay
=
bundle
.
m_lastPlay
;
m_filesize
=
bundle
.
m_filesize
;
m_type
=
bundle
.
m_type
;
m_exists
=
bundle
.
m_exists
;
m_isValidMedia
=
bundle
.
m_isValidMedia
;
m_isCompilation
=
bundle
.
m_isCompilation
;
m_notCompilation
=
bundle
.
m_notCompilation
;
m_safeToSave
=
bundle
.
m_safeToSave
;
m_waitingOnKIO
=
bundle
.
m_waitingOnKIO
;
m_tempSavePath
=
bundle
.
m_tempSavePath
;
m_origRenamedSavePath
=
bundle
.
m_origRenamedSavePath
;
m_tempSaveDigest
=
bundle
.
m_tempSaveDigest
;
m_saveFileref
=
bundle
.
m_saveFileref
;
if
(
bundle
.
m_moodbar
!=
0
)
{
if
(
m_moodbar
==
0
)
m_moodbar
=
new
Moodbar
(
this
);
*
m_moodbar
=
*
bundle
.
m_moodbar
;
}
else
{
// If m_moodbar != 0, it's initialized for a reason
// Deleting it makes the PrettySlider code more ugly,
// since it'd have to reconnect the jobEvent() signal.
if
(
m_moodbar
!=
0
)
m_moodbar
->
reset
();
}
// delete m_podcastBundle; why does this crash Amarok? apparently m_podcastBundle isn't always initialized.
m_podcastBundle
=
0
;
if
(
bundle
.
m_podcastBundle
)
setPodcastBundle
(
*
bundle
.
m_podcastBundle
);
// delete m_lastFmBundle; same as above
m_lastFmBundle
=
0
;
if
(
bundle
.
m_lastFmBundle
)
setLastFmBundle
(
*
bundle
.
m_lastFmBundle
);
m_isSearchDirty
=
true
;
return
*
this
;
}
bool
MetaBundle
::
checkExists
()
{
m_exists
=
!
isFile
()
||
QFile
::
exists
(
url
().
path
()
);
return
m_exists
;
}
bool
MetaBundle
::
operator
==
(
const
MetaBundle
&
bundle
)
const
{
return
uniqueId
()
==
bundle
.
uniqueId
()
&&
//first, since if using IDs will return faster
artist
()
==
bundle
.
artist
()
&&
albumArtist
()
==
bundle
.
albumArtist
()
&&
title
()
==
bundle
.
title
()
&&
composer
()
==
bundle
.
composer
()
&&
album
()
==
bundle
.
album
()
&&
year
()
==
bundle
.
year
()
&&
comment
()
==
bundle
.
comment
()
&&
genre
()
==
bundle
.
genre
()
&&
track
()
==
bundle
.
track
()
&&
discNumber
()
==
bundle
.
discNumber
()
&&
bpm
()
==
bundle
.
bpm
()
&&
length
()
==
bundle
.
length
()
&&
bitrate
()
==
bundle
.
bitrate
()
&&
sampleRate
()
==
bundle
.
sampleRate
();
// FIXME: check for size equality?
}
void
MetaBundle
::
clear
()
{
*
this
=
MetaBundle
();
}
void
MetaBundle
::
init
(
TagLib
::
AudioProperties
*
ap
)
{
if
(
ap
)
{
m_bitrate
=
ap
->
bitrate
();
m_length
=
ap
->
length
();
m_sampleRate
=
ap
->
sampleRate
();
}
else
m_bitrate
=
m_length
=
m_sampleRate
=
Undetermined
;
}
void
MetaBundle
::
init
(
const
KFileMetaInfo
&
info
)
{
if
(
info
.
isValid
()
&&
!
info
.
isEmpty
()
)
{
m_artist
=
info
.
item
(
"Artist"
).
string
();
m_album
=
info
.
item
(
"Album"
).
string
();
m_comment
=
info
.
item
(
"Comment"
).
string
();
m_genre
=
info
.
item
(
"Genre"
).
string
();
m_year
=
info
.
item
(
"Year"
).
string
().
toInt
();
m_track
=
info
.
item
(
"Track"
).
string
().
toInt
();
m_bitrate
=
info
.
item
(
"Bitrate"
).
value
().
toInt
();
m_length
=
info
.
item
(
"Length"
).
value
().
toInt
();
m_sampleRate
=
info
.
item
(
"Sample Rate"
).
value
().
toInt
();
// For title, check if it is valid. If not, use prettyTitle.
// @see bug:83650
const
KFileMetaInfoItem
itemtitle
=
info
.
item
(
"Title"
);
m_title
=
itemtitle
.
isValid
()
?
itemtitle
.
string
()
:
prettyTitle
(
m_url
.
fileName
()
);
const
KFileMetaInfoItem
itemid
=
info
.
item
(
"Unique ID"
);
m_uniqueId
=
itemid
.
isValid
()
?
itemid
.
string
()
:
QString
::
null
;
// because whoever designed KMetaInfoItem is a donkey
#define makeSane( x ) if( x == "---" ) x = null;
QString
null
;
makeSane
(
m_artist
);
makeSane
(
m_album
);
makeSane
(
m_comment
);
makeSane
(
m_genre
);
makeSane
(
m_title
);
#undef makeSane
m_isValidMedia
=
true
;
}
else
{
m_bitrate
=
m_length
=
m_sampleRate
=
m_filesize
=
Undetermined
;
m_isValidMedia
=
false
;
}
}
void
MetaBundle
::
embeddedImages
(
MetaBundle
::
EmbeddedImageList
&
images
)
const
{
if
(
isFile
()
)
{
TagLib
::
FileRef
fileref
=
TagLib
::
FileRef
(
QFile
::
encodeName
(
url
().
path
()
),
false
);
if
(
!
fileref
.
isNull
()
)
{
if
(
TagLib
::
MPEG
::
File
*
file
=
dynamic_cast
<
TagLib
::
MPEG
::
File
*>
(
fileref
.
file
()
)
)
{
if
(
file
->
ID3v2Tag
()
)
loadImagesFromTag
(
*
file
->
ID3v2Tag
(),
images
);
}
else
if
(
TagLib
::
FLAC
::
File
*
file
=
dynamic_cast
<
TagLib
::
FLAC
::
File
*>
(
fileref
.
file
()
)
)
{
if
(
file
->
ID3v2Tag
()
)
loadImagesFromTag
(
*
file
->
ID3v2Tag
(),
images
);
}
else
if
(
TagLib
::
MP4
::
File
*
file
=
dynamic_cast
<
TagLib
::
MP4
::
File
*>
(
fileref
.
file
()
)
)
{
TagLib
::
MP4
::
Tag
*
mp4tag
=
dynamic_cast
<
TagLib
::
MP4
::
Tag
*>
(
file
->
tag
()
);
if
(
mp4tag
&&
mp4tag
->
cover
().
size
()
)
{
images
.
push_back
(
EmbeddedImage
(
mp4tag
->
cover
(),
""
)
);
}
}
}
}
}
void
MetaBundle
::
readTags
(
TagLib
::
AudioProperties
::
ReadStyle
readStyle
,
EmbeddedImageList
*
images
)
{
if
(
!
isFile
()
)
return
;
const
QString
path
=
url
().
path
();
TagLib
::
FileRef
fileref
;
TagLib
::
Tag
*
tag
=
0
;
fileref
=
TagLib
::
FileRef
(
QFile
::
encodeName
(
path
),
true
,
readStyle
);
if
(
!
fileref
.
isNull
()
)
{
setUniqueId
(
readUniqueId
(
&
fileref
)
);
m_filesize
=
QFile
(
path
).
size
();
tag
=
fileref
.
tag
();
if
(
tag
)
{
#define strip( x ) TStringToQString( x ).stripWhiteSpace()
setTitle
(
strip
(
tag
->
title
()
)
);
setArtist
(
strip
(
tag
->
artist
()
)
);
setAlbum
(
strip
(
tag
->
album
()
)
);
setComment
(
strip
(
tag
->
comment
()
)
);
setGenre
(
strip
(
tag
->
genre
()
)
);
setYear
(
tag
->
year
()
);
setTrack
(
tag
->
track
()
);
#undef strip
m_isValidMedia
=
true
;
}
/* As mpeg implementation on TagLib uses a Tag class that's not defined on the headers,
we have to cast the files, not the tags! */
QString
disc
;
QString
compilation
;
if
(
TagLib
::
MPEG
::
File
*
file
=
dynamic_cast
<
TagLib
::
MPEG
::
File
*>
(
fileref
.
file
()
)
)
{
m_type
=
mp3
;
if
(
file
->
ID3v2Tag
()
)
{
if
(
!
file
->
ID3v2Tag
()
->
frameListMap
()[
"TPOS"
].
isEmpty
()
)
disc
=
TStringToQString
(
file
->
ID3v2Tag
()
->
frameListMap
()[
"TPOS"
].
front
()
->
toString
()
).
stripWhiteSpace
();
if
(
!
file
->
ID3v2Tag
()
->
frameListMap
()[
"TBPM"
].
isEmpty
()
)
setBpm
(
TStringToQString
(
file
->
ID3v2Tag
()
->
frameListMap
()[
"TBPM"
].
front
()
->
toString
()
).
stripWhiteSpace
().
toFloat
()
);
if
(
!
file
->
ID3v2Tag
()
->
frameListMap
()[
"TCOM"
].
isEmpty
()
)
setComposer
(
TStringToQString
(
file
->
ID3v2Tag
()
->
frameListMap
()[
"TCOM"
].
front
()
->
toString
()
).
stripWhiteSpace
()
);
if
(
!
file
->
ID3v2Tag
()
->
frameListMap
()[
"TPE2"
].
isEmpty
()
)
// non-standard: Apple, Microsoft
setAlbumArtist
(
TStringToQString
(
file
->
ID3v2Tag
()
->
frameListMap
()[
"TPE2"
].
front
()
->
toString
()
).
stripWhiteSpace
()
);
if
(
!
file
->
ID3v2Tag
()
->
frameListMap
()[
"TCMP"
].
isEmpty
()
)
compilation
=
TStringToQString
(
file
->
ID3v2Tag
()
->
frameListMap
()[
"TCMP"
].
front
()
->
toString
()
).
stripWhiteSpace
();
if
(
images
)
{
loadImagesFromTag
(
*
file
->
ID3v2Tag
(),
*
images
);
}
}
}
else
if
(
TagLib
::
Ogg
::
Vorbis
::
File
*
file
=
dynamic_cast
<
TagLib
::
Ogg
::
Vorbis
::
File
*>
(
fileref
.
file
()
)
)
{
m_type
=
ogg
;
if
(
file
->
tag
()
)
{
if
(
!
file
->
tag
()
->
fieldListMap
()[
"COMPOSER"
].
isEmpty
()
)
setComposer
(
TStringToQString
(
file
->
tag
()
->
fieldListMap
()[
"COMPOSER"
].
front
()
).
stripWhiteSpace
()
);
if
(
!
file
->
tag
()
->
fieldListMap
()[
"BPM"
].
isEmpty
()
)
setBpm
(
TStringToQString
(
file
->
tag
()
->
fieldListMap
()[
"BPM"
].
front
()
).
stripWhiteSpace
().
toFloat
()
);
if
(
!
file
->
tag
()
->
fieldListMap
()[
"DISCNUMBER"
].
isEmpty
()
)
disc
=
TStringToQString
(
file
->
tag
()
->
fieldListMap
()[
"DISCNUMBER"
].
front
()
).
stripWhiteSpace
();
if
(
!
file
->
tag
()
->
fieldListMap
()[
"COMPILATION"
].
isEmpty
()
)
compilation
=
TStringToQString
(
file
->
tag
()
->
fieldListMap
()[
"COMPILATION"
].
front
()
).
stripWhiteSpace
();
}
}
else
if
(
TagLib
::
FLAC
::
File
*
file
=
dynamic_cast
<
TagLib
::
FLAC
::
File
*>
(
fileref
.
file
()
)
)
{
m_type
=
flac
;
if
(
file
->
xiphComment
()
)
{
if
(
!
file
->
xiphComment
()
->
fieldListMap
()[
"COMPOSER"
].
isEmpty
()
)
setComposer
(
TStringToQString
(
file
->
xiphComment
()
->
fieldListMap
()[
"COMPOSER"
].
front
()
).
stripWhiteSpace
()
);
if
(
!
file
->
xiphComment
()
->
fieldListMap
()[
"BPM"
].
isEmpty
()
)
setBpm
(
TStringToQString
(
file
->
xiphComment
()
->
fieldListMap
()[
"BPM"
].
front
()
).
stripWhiteSpace
().
toFloat
()
);
if
(
!
file
->
xiphComment
()
->
fieldListMap
()[
"DISCNUMBER"
].
isEmpty
()
)
disc
=
TStringToQString
(
file
->
xiphComment
()
->
fieldListMap
()[
"DISCNUMBER"
].
front
()
).
stripWhiteSpace
();
if
(
!
file
->
xiphComment
()
->
fieldListMap
()[
"COMPILATION"
].
isEmpty
()
)
compilation
=
TStringToQString
(
file
->
xiphComment
()
->
fieldListMap
()[
"COMPILATION"
].
front
()
).
stripWhiteSpace
();
}
if
(
images
&&
file
->
ID3v2Tag
()
)
{
loadImagesFromTag
(
*
file
->
ID3v2Tag
(),
*
images
);
}
}
else
if
(
TagLib
::
MP4
::
File
*
file
=
dynamic_cast
<
TagLib
::
MP4
::
File
*>
(
fileref
.
file
()
)
)
{
m_type
=
mp4
;
TagLib
::
MP4
::
Tag
*
mp4tag
=
dynamic_cast
<
TagLib
::
MP4
::
Tag
*>
(
file
->
tag
()
);
if
(
mp4tag
)
{
setComposer
(
TStringToQString
(
mp4tag
->
composer
()
)
);
setBpm
(
QString
::
number
(
mp4tag
->
bpm
()
).
toFloat
()
);
disc
=
QString
::
number
(
mp4tag
->
disk
()
);
compilation
=
QString
::
number
(
mp4tag
->
compilation
()
);
if
(
images
&&
mp4tag
->
cover
().
size
()
)
{
images
->
push_back
(
EmbeddedImage
(
mp4tag
->
cover
(),
""
)
);
}
}
}
if
(
!
disc
.
isEmpty
()
)
{
int
i
=
disc
.
find
(
'/'
);
if
(
i
!=
-1
)
// disc.right( i ).toInt() is total number of discs, we don't use this at the moment
setDiscNumber
(
disc
.
left
(
i
).
toInt
()
);
else
setDiscNumber
(
disc
.
toInt
()
);
}
if
(
compilation
.
isEmpty
()
)
{
// well, it wasn't set, but if the artist is VA assume it's a compilation
if
(
artist
().
string
()
==
i18n
(
"Various Artists"
)
)
setCompilation
(
CompilationYes
);
}
else
{
int
i
=
compilation
.
toInt
();
if
(
i
==
CompilationNo
)
setCompilation
(
CompilationNo
);
else
if
(
i
==
CompilationYes
)
setCompilation
(
CompilationYes
);
}
init
(
fileref
.
audioProperties
()
);
}
//FIXME disabled for beta4 as it's simpler to not got 100 bug reports
//else if( KMimeType::findByUrl( m_url )->is( "audio" ) )
// init( KFileMetaInfo( m_url, QString::null, KFileMetaInfo::Everything ) );
}
void
MetaBundle
::
updateFilesize
()
{
if
(
!
isFile
()
)
{
m_filesize
=
Undetermined
;
return
;
}
const
QString
path
=
url
().
path
();
m_filesize
=
QFile
(
path
).
size
();
}
float
MetaBundle
::
score
(
bool
ensureCached
)
const
{
if
(
m_score
==
Undetermined
&&
!
ensureCached
)
//const_cast is ugly, but other option was mutable, and then we lose const correctness checking
//everywhere else
*
const_cast
<
float
*>
(
&
m_score
)
=
CollectionDB
::
instance
()
->
getSongPercentage
(
m_url
.
path
()
);
return
m_score
;
}
int
MetaBundle
::
rating
(
bool
ensureCached
)
const
{
if
(
m_rating
==
Undetermined
&&
!
ensureCached
)
*
const_cast
<
int
*>
(
&
m_rating
)
=
CollectionDB
::
instance
()
->
getSongRating
(
m_url
.
path
()
);
return
m_rating
;
}
int
MetaBundle
::
playCount
(
bool
ensureCached
)
const
{
if
(
m_playCount
==
Undetermined
&&
!
ensureCached
)
*
const_cast
<
int
*>
(
&
m_playCount
)
=
CollectionDB
::
instance
()
->
getPlayCount
(
m_url
.
path
()
);
return
m_playCount
;
}
uint
MetaBundle
::
lastPlay
(
bool
ensureCached
)
const
{
if
(
(
int
)
m_lastPlay
==
abs
(
Undetermined
)
&&
!
ensureCached
)
*
const_cast
<
uint
*>
(
&
m_lastPlay
)
=
CollectionDB
::
instance
()
->
getLastPlay
(
m_url
.
path
()
).
toTime_t
();
return
m_lastPlay
;
}
void
MetaBundle
::
copyFrom
(
const
MetaBundle
&
bundle
)
{
setTitle
(
bundle
.
title
()
);
setArtist
(
bundle
.
artist
()
);
setAlbumArtist
(
bundle
.
albumArtist
()
);
setComposer
(
bundle
.
composer
()
);
setAlbum
(
bundle
.
album
()
);
setYear
(
bundle
.
year
()
);
setDiscNumber
(
bundle
.
discNumber
()
);
setBpm
(
bundle
.
bpm
()
);
setComment
(
bundle
.
comment
()
);
setGenre
(
bundle
.
genre
()
);
setTrack
(
bundle
.
track
()
);
setLength
(
bundle
.
length
()
);
setBitrate
(
bundle
.
bitrate
()
);
setSampleRate
(
bundle
.
sampleRate
()
);
setScore
(
bundle
.
score
()
);
setRating
(
bundle
.
rating
()
);
setPlayCount
(
bundle
.
playCount
()
);
setLastPlay
(
bundle
.
lastPlay
()
);
setFileType
(
bundle
.
fileType
()
);
setFilesize
(
bundle
.
filesize
()
);
if
(
bundle
.
m_podcastBundle
)
setPodcastBundle
(
*
bundle
.
m_podcastBundle
);
else
{
delete
m_podcastBundle
;
m_podcastBundle
=
0
;
}
if
(
bundle
.
m_lastFmBundle
)
setLastFmBundle
(
*
bundle
.
m_lastFmBundle
);
else
{
delete
m_lastFmBundle
;
m_lastFmBundle
=
0
;
}
}
void
MetaBundle
::
copyFrom
(
const
PodcastEpisodeBundle
&
peb
)
{
setPodcastBundle
(
peb
);
setTitle
(
peb
.
title
()
);
setArtist
(
peb
.
author
()
);
PodcastChannelBundle
pcb
;
if
(
CollectionDB
::
instance
()
->
getPodcastChannelBundle
(
peb
.
parent
(),
&
pcb
)
)
{
if
(
!
pcb
.
title
().
isEmpty
()
)
setAlbum
(
pcb
.
title
()
);
}
setGenre
(
QString
(
"Podcast"
)
);
}
void
MetaBundle
::
setExactText
(
int
column
,
const
QString
&
newText
)
{
switch
(
column
)
{
case
Title
:
setTitle
(
newText
);
break
;
case
Artist
:
setArtist
(
newText
);
break
;
case
AlbumArtist
:
setAlbumArtist
(
newText
);
break
;
case
Composer
:
setComposer
(
newText
);
break
;
case
Year
:
setYear
(
newText
.
toInt
()
);
break
;
case
Album
:
setAlbum
(
newText
);
break
;
case
DiscNumber
:
setDiscNumber
(
newText
.
toInt
()
);
break
;
case
Track
:
setTrack
(
newText
.
toInt
()
);
break
;
case
Bpm
:
setBpm
(
newText
.
toFloat
()
);
break
;
case
Genre
:
setGenre
(
newText
);
break
;
case
Comment
:
setComment
(
newText
);
break
;
case
Length
:
setLength
(
newText
.
toInt
()
);
break
;
case
Bitrate
:
setBitrate
(
newText
.
toInt
()
);
break
;
case
SampleRate
:
setSampleRate
(
newText
.
toInt
()
);
break
;
case
Score
:
setScore
(
newText
.
toFloat
()
);
break
;
case
Rating
:
setRating
(
newText
.
toInt
()
);
break
;
case
PlayCount
:
setPlayCount
(
newText
.
toInt
()
);
break
;
case
LastPlayed
:
setLastPlay
(
newText
.
toInt
()
);
break
;
case
Filesize
:
setFilesize
(
newText
.
toInt
()
);
break
;
case
Type
:
setFileType
(
newText
.
toInt
()
);
break
;
default
:
warning
()
<<
"Tried to set the text of an immutable or nonexistent column! ["
<<
column
<<
endl
;
}
}
QString
MetaBundle
::
exactText
(
int
column
,
bool
ensureCached
)
const
{
switch
(
column
)
{
case
Filename
:
return
filename
();
case
Title
:
return
title
();
case
Artist
:
return
artist
();
case
AlbumArtist
:
return
albumArtist
();
case
Composer
:
return
composer
();
case
Year
:
return
QString
::
number
(
year
()
);
case
Album
:
return
album
();
case
DiscNumber
:
return
QString
::
number
(
discNumber
()
);
case
Track
:
return
QString
::
number
(
track
()
);
case
Bpm
:
return
QString
::
number
(
bpm
()
);
case
Genre
:
return
genre
();
case
Comment
:
return
comment
();
case
Directory
:
return
directory
();
case
Type
:
return
QString
::
number
(
fileType
()
);
case
Length
:
return
QString
::
number
(
length
()
);
case
Bitrate
:
return
QString
::
number
(
bitrate
()
);
case
SampleRate
:
return
QString
::
number
(
sampleRate
()
);
case
Score
:
return
QString
::
number
(
score
(
ensureCached
)
);
case
Rating
:
return
QString
::
number
(
rating
(
ensureCached
)
);
case
PlayCount
:
return
QString
::
number
(
playCount
(
ensureCached
)
);
case
LastPlayed
:
return
QString
::
number
(
lastPlay
(
ensureCached
)
);
case
Filesize
:
return
QString
::
number
(
filesize
()
);
case
Mood
:
return
QString
();
default
:
warning
()
<<
"Tried to get the text of a nonexistent column! ["
<<
column
<<
endl
;
}
return
QString
();
//shouldn't happen
}
QString
MetaBundle
::
prettyText
(
int
column
)
const
{
QString
text
;
switch
(
column
)
{
case
Filename
:
text
=
isFile
()
?
MetaBundle
::
prettyTitle
(
filename
())
:
url
().
prettyURL
();
break
;
case
Title
:
text
=
title
().
isEmpty
()
?
MetaBundle
::
prettyTitle
(
filename
()
)
:
title
();
break
;
case
Artist
:
text
=
artist
();
break
;
case
AlbumArtist
:
text
=
albumArtist
();
break
;
case
Composer
:
text
=
composer
();
break
;
case
Year
:
text
=
year
()
?
QString
::
number
(
year
()
)
:
QString
::
null
;
break
;
case
Album
:
text
=
album
();
break
;
case
DiscNumber
:
text
=
discNumber
()
?
QString
::
number
(
discNumber
()
)
:
QString
::
null
;
break
;
case
Bpm
:
text
=
bpm
()
?
QString
::
number
(
bpm
()
)
:
QString
::
null
;
break
;
case
Track
:
text
=
track
()
?
QString
::
number
(
track
()
)
:
QString
::
null
;
break
;
case
Genre
:
text
=
genre
();
break
;
case
Comment
:
text
=
comment
();
break
;
case
Directory
:
text
=
url
().
isEmpty
()
?
QString
()
:
directory
();
break
;
case
Type
:
text
=
url
().
isEmpty
()
?
QString
()
:
type
();
break
;
case
Length
:
text
=
prettyLength
(
length
(),
true
);
break
;
case
Bitrate
:
text
=
prettyBitrate
(
bitrate
()
);
break
;
case
SampleRate
:
text
=
prettySampleRate
();
break
;
case
Score
:
text
=
QString
::
number
(
static_cast
<
int
>
(
score
()
)
);
break
;
case
Rating
:
text
=
prettyRating
();
break
;
case
PlayCount
:
text
=
QString
::
number
(
playCount
()
);
break
;
case
LastPlayed
:
text
=
Amarok
::
verboseTimeSince
(
lastPlay
()
);
break
;
case
Filesize
:
text
=
prettyFilesize
();
break
;
case
Mood
:
text
=
moodbar_const
().
state
()
==
Moodbar
::
JobRunning
?
i18n
(
"Calculating..."
)
:
moodbar_const
().
state
()
==
Moodbar
::
JobQueued
?
i18n
(
"Queued..."
)
:
QString
::
null
;
break
;
default
:
warning
()
<<
"Tried to get the text of a nonexistent column!"
<<
endl
;
break
;
}
return
text
.
stripWhiteSpace
();
}
bool
MetaBundle
::
matchesSimpleExpression
(
const
QString
&
expression
,
const
QValueList
<
int
>
&
columns
)
const
{
const
QStringList
terms
=
QStringList
::
split
(
' '
,
expression
.
lower
()
);
bool
matches
=
true
;
for
(
uint
x
=
0
;
matches
&&
x
<
terms
.
count
();
++
x
)
{
uint
y
=
0
,
n
=
columns
.
count
();
for
(;
y
<
n
;
++
y
)
if
(
prettyText
(
columns
[
y
]
).
lower
().
contains
(
terms
[
x
]
)
)
break
;
matches
=
(
y
<
n
);
}
return
matches
;
}
void
MetaBundle
::
reactToChanges
(
const
QValueList
<
int
>&
columns
)
{
// mark search dirty if we need to
for
(
uint
i
=
0
;
!
m_isSearchDirty
&&
i
<
columns
.
count
();
i
++
)
if
((
m_searchColumns
&
(
1
<<
columns
[
i
]))
>
0
)
m_isSearchDirty
=
true
;
}
bool
MetaBundle
::
matchesFast
(
const
QStringList
&
terms
,
ColumnMask
columnMask
)
const
{
// simple search for rating, last played, etc. makes no sense and it hurts us a
// lot if we have to fetch it from the db. so zero them out
columnMask
&=
~
(
1
<<
Score
|
1
<<
Rating
|
1
<<
PlayCount
|
1
<<
LastPlayed
|
1
<<
Mood
);
if
(
m_isSearchDirty
||
m_searchColumns
!=
columnMask
)
{
// assert the size of ColumnMask is large enough. In the absence of
// a compile assert mechanism, this is pretty much as good for
// optimized code (ie, free)
if
(
sizeof
(
ColumnMask
)
<
(
NUM_COLUMNS
/
8
)
)
{
warning
()
<<
"ColumnMask is not big enough!
\n
"
;
}
// recompute search text
// There is potential for mishap here if matchesFast gets called from multiple
// threads, but it's *highly* unlikely that something bad will happen
m_isSearchDirty
=
false
;
m_searchColumns
=
columnMask
;
m_searchStr
.
setLength
(
0
);
for
(
int
i
=
0
;
i
<
NUM_COLUMNS
;
i
++
)
{
if
((
columnMask
&
(
1
<<
i
))
>
0
)
{
if
(
!
m_searchStr
.
isEmpty
())
m_searchStr
+=
' '
;
m_searchStr
+=
prettyText
(
i
).
lower
();
}
}
}
// now search
for
(
uint
i
=
0
;
i
<
terms
.
count
();
i
++
)
{
if
(
!
m_searchStr
.
contains
(
terms
[
i
]))
return
false
;
}
return
true
;
}
bool
MetaBundle
::
matchesExpression
(
const
QString
&
expression
,
const
QValueList
<
int
>
&
defaultColumns
)
const
{
return
matchesParsedExpression
(
ExpressionParser
::
parse
(
expression
),
defaultColumns
);
}
bool
MetaBundle
::
matchesParsedExpression
(
const
ParsedExpression
&
data
,
const
QValueList
<
int
>
&
defaults
)
const
{
for
(
uint
i
=
0
,
n
=
data
.
count
();
i
<
n
;
++
i
)
//check each part for matchiness
{
bool
b
=
false
;
//whether at least one matches
for
(
uint
ii
=
0
,
count
=
data
[
i
].
count
();
ii
<
count
;
++
ii
)
{
expression_element
e
=
data
[
i
][
ii
];
int
column
=
-1
;
if
(
!
e
.
field
.
isEmpty
()
)
{
QString
field
=
e
.
field
.
lower
();
column
=
columnIndex
(
field
);
if
(
column
==
-1
)
{
column
=
-2
;
if
(
field
==
"size"
)
field
=
"filesize"
;
else
if
(
field
==
"filetype"
)
field
=
"type"
;
else
if
(
field
==
"disc"
)
field
=
"discnumber"
;
else
column
=
-1
;
if
(
column
==
-2
)
column
=
columnIndex
(
field
);
}
}
if
(
column
>=
0
)
//a field was specified and it exists
{
QString
q
=
e
.
text
,
v
=
prettyText
(
column
).
lower
(),
w
=
q
.
lower
();
//q = query, v = contents of the field, w = match against it
bool
condition
;
//whether it matches, not taking e.negateation into account
bool
numeric
;
switch
(
column
)
{
case
Year
:
case
DiscNumber
:
case
Track
:
case
Bpm
:
case
Bitrate
:
case
SampleRate
:
case
Score
:
case
PlayCount
:
case
LastPlayed
:
case
Filesize
:
numeric
=
true
;
break
;
default
:
numeric
=
false
;
}
if
(
column
==
Filesize
)
{
v
=
QString
::
number
(
filesize
()
);
if
(
w
.
endsWith
(
"m"
)
)
w
=
QString
::
number
(
w
.
left
(
w
.
length
()
-1
).
toLong
()
*
1024
*
1024
);
else
if
(
w
.
endsWith
(
"k"
)
)
w
=
QString
::
number
(
w
.
left
(
w
.
length
()
-1
).
toLong
()
*
1024
);
}
if
(
e
.
match
==
expression_element
::
More
)
{
if
(
numeric
)
condition
=
v
.
toInt
()
>
w
.
toInt
();
else
if
(
column
==
Rating
)
condition
=
v
.
toFloat
()
>
w
.
toFloat
();
else
if
(
column
==
Length
)
{
int
g
=
v
.
find
(
':'
),
h
=
w
.
find
(
':'
);
condition
=
v
.
left
(
g
).
toInt
()
>
w
.
left
(
h
).
toInt
()
||
(
v
.
left
(
g
).
toInt
()
==
w
.
left
(
h
).
toInt
()
&&
v
.
mid
(
g
+
1
).
toInt
()
>
w
.
mid
(
h
+
1
).
toInt
()
);
}
else
condition
=
v
>
w
;
//compare the strings
}
else
if
(
e
.
match
==
expression_element
::
Less
)
{
if
(
numeric
)
condition
=
v
.
toInt
()
<
w
.
toInt
();
else
if
(
column
==
Rating
)
condition
=
v
.
toFloat
()
<
w
.
toFloat
();
else
if
(
column
==
Length
)
{
int
g
=
v
.
find
(
':'
),
h
=
w
.
find
(
':'
);
condition
=
v
.
left
(
g
).
toInt
()
<
w
.
left
(
h
).
toInt
()
||
(
v
.
left
(
g
).
toInt
()
==
w
.
left
(
h
).
toInt
()
&&
v
.
mid
(
g
+
1
).
toInt
()
<
w
.
mid
(
h
+
1
).
toInt
()
);
}
else
condition
=
v
<
w
;
}
else
{
if
(
numeric
)
condition
=
v
.
toInt
()
==
w
.
toInt
();
else
if
(
column
==
Rating
)
condition
=
v
.
toFloat
()
==
w
.
toFloat
();
else
if
(
column
==
Length
)
{
int
g
=
v
.
find
(
':'
),
h
=
w
.
find
(
':'
);
condition
=
v
.
left
(
g
).
toInt
()
==
w
.
left
(
h
).
toInt
()
&&
v
.
mid
(
g
+
1
).
toInt
()
==
w
.
mid
(
h
+
1
).
toInt
();
}
else
condition
=
v
.
contains
(
q
,
false
);
}
if
(
condition
==
(
e
.
negate
?
false
:
true
)
)
{
b
=
true
;
break
;
}
}
else
//check just the default fields
{
for
(
int
it
=
0
,
end
=
defaults
.
size
();
it
!=
end
;
++
it
)
{
b
=
prettyText
(
defaults
[
it
]
).
contains
(
e
.
text
,
false
)
==
(
e
.
negate
?
false
:
true
);
if
(
(
e
.
negate
&&
!
b
)
||
(
!
e
.
negate
&&
b
)
)
break
;
}
if
(
b
)
break
;
}
}
if
(
!
b
)
return
false
;
}
return
true
;
}
QString
MetaBundle
::
prettyTitle
()
const
{
QString
s
=
artist
();
//NOTE this gets regressed often, please be careful!
// whatever you do, handle the stream case, streams have no artist but have an excellent title
//FIXME doesn't work for resume playback
if
(
s
.
isEmpty
()
)
s
=
title
();
else
s
=
i18n
(
"%1 - %2"
).
arg
(
artist
(),
title
()
);
if
(
s
.
isEmpty
()
)
s
=
prettyTitle
(
filename
()
);
return
s
;
}
QString
MetaBundle
::
veryNiceTitle
()
const
{
QString
s
;
//NOTE I'm not sure, but the notes and FIXME's in the prettyTitle function should be fixed now.
// If not then they do apply to this function also!
if
(
!
title
().
isEmpty
()
)
{
if
(
!
artist
().
isEmpty
()
)
s
=
i18n
(
"%1 by %2"
).
arg
(
title
(),
artist
()
);
else
s
=
title
();
}
else
{
s
=
prettyTitle
(
filename
()
);
}
return
s
;
}
QString
MetaBundle
::
prettyTitle
(
const
QString
&
filename
)
//static
{
QString
s
=
filename
;
//just so the code is more readable
//remove .part extension if it exists
if
(
s
.
endsWith
(
".part"
))
s
=
s
.
left
(
s
.
length
()
-
5
);
//remove file extension, s/_/ /g and decode %2f-like sequences
s
=
s
.
left
(
s
.
findRev
(
'.'
)
).
replace
(
'_'
,
' '
);
s
=
KURL
::
decode_string
(
s
);
return
s
;
}
QString
MetaBundle
::
prettyLength
(
int
seconds
,
bool
showHours
)
//static
{
if
(
seconds
>
0
)
return
prettyTime
(
seconds
,
showHours
);
if
(
seconds
==
Undetermined
)
return
"?"
;
if
(
seconds
==
Irrelevant
)
return
"-"
;
return
QString
();
//Unavailable = ""
}
QString
MetaBundle
::
prettyTime
(
uint
seconds
,
bool
showHours
)
//static
{
QString
s
=
QChar
(
':'
);
s
.
append
(
zeroPad
(
seconds
%
60
)
);
//seconds
seconds
/=
60
;
if
(
showHours
&&
seconds
>=
60
)
{
s
.
prepend
(
zeroPad
(
seconds
%
60
)
);
//minutes
s
.
prepend
(
':'
);
seconds
/=
60
;
}
//don't zeroPad the last one, as it can be greater than 2 digits
s
.
prepend
(
QString
::
number
(
seconds
)
);
//hours or minutes depending on above if block
return
s
;
}
QString
MetaBundle
::
veryPrettyTime
(
int
time
)
{
if
(
time
==
Undetermined
)
return
i18n
(
"?"
);
if
(
time
==
Irrelevant
)
return
i18n
(
"-"
);
QStringList
s
;
s
<<
QString
::
number
(
time
%
60
);
//seconds
time
/=
60
;
if
(
time
)
s
<<
QString
::
number
(
time
%
60
);
//minutes
time
/=
60
;
if
(
time
)
s
<<
QString
::
number
(
time
%
24
);
//hours
time
/=
24
;
if
(
time
)
s
<<
QString
::
number
(
time
);
//days
switch
(
s
.
count
()
)
{
case
1
:
return
i18n
(
"seconds"
,
"%1s"
).
arg
(
s
[
0
]
);
case
2
:
return
i18n
(
"minutes, seconds"
,
"%2m %1s"
).
arg
(
s
[
0
],
s
[
1
]
);
case
3
:
return
i18n
(
"hours, minutes, seconds"
,
"%3h %2m %1s"
).
arg
(
s
[
0
],
s
[
1
],
s
[
2
]
);
case
4
:
return
i18n
(
"days, hours, minutes, seconds"
,
"%4d %3h %2m %1s"
).
arg
(
s
[
0
],
s
[
1
],
s
[
2
],
s
[
3
]
);
default
:
return
"omg bug!"
;
}
}
QString
MetaBundle
::
fuzzyTime
(
int
time
)
{
QString
s
;
int
secs
=
0
,
min
=
0
,
hr
=
0
,
day
=
0
,
week
=
0
;
if
(
time
==
Undetermined
)
return
i18n
(
"?"
);
if
(
time
==
Irrelevant
)
return
i18n
(
"-"
);
secs
=
time
%
60
;
//seconds
time
/=
60
;
min
=
time
%
60
;
//minutes
time
/=
60
;
hr
=
time
%
24
;
//hours
time
/=
24
;
day
=
time
%
7
;
//days
time
/=
7
;
week
=
time
;
//weeks
if
(
week
&&
hr
>=
12
)
{
day
++
;
if
(
day
==
7
)
{
week
++
;
day
=
0
;
}
}
else
if
(
day
&&
min
>=
30
)
{
hr
++
;
if
(
hr
==
24
)
{
day
++
;
hr
=
0
;
}
}
else
if
(
hr
&&
secs
>=
30
)
{
min
++
;
if
(
min
==
60
)
{
hr
++
;
min
=
0
;
}
}
QString
weeks
=
i18n
(
"1 week %1"
,
"%n weeks %1"
,
week
);
QString
days
=
i18n
(
"1 day %1"
,
"%n days %1"
,
day
);
QString
hours
=
i18n
(
"1 hour"
,
"%n hours"
,
hr
);
if
(
week
)
return
weeks
.
arg
(
day
?
days
.
arg
(
""
)
:
""
).
simplifyWhiteSpace
();
else
if
(
day
)
return
days
.
arg
(
hr
?
hours
:
""
).
simplifyWhiteSpace
();
else
if
(
hr
)
return
i18n
(
"%1:%2 hours"
).
arg
(
hr
).
arg
(
zeroPad
(
min
)
);
else
return
i18n
(
"%1:%2"
).
arg
(
min
).
arg
(
zeroPad
(
secs
)
);
}
QString
MetaBundle
::
prettyBitrate
(
int
i
)
{
//the point here is to force sharing of these strings returned from prettyBitrate()
static
const
QString
bitrateStore
[
9
]
=
{
"?"
,
"32"
,
"64"
,
"96"
,
"128"
,
"160"
,
"192"
,
"224"
,
"256"
};
return
(
i
>=
0
&&
i
<=
256
&&
i
%
32
==
0
)
?
bitrateStore
[
i
/
32
]
:
prettyGeneric
(
"%1"
,
i
);
}
QString
MetaBundle
::
prettyFilesize
(
int
s
)
{
return
KIO
::
convertSize
(
s
);
}
QString
MetaBundle
::
prettyRating
(
int
r
,
bool
trailingzero
)
//static
{
if
(
trailingzero
)
return
QString
::
number
(
float
(
r
)
/
2
,
'f'
,
1
);
else
return
r
?
QString
::
number
(
float
(
r
)
/
2
)
:
QString
();
}
QString
MetaBundle
::
ratingDescription
(
int
r
)
{
switch
(
r
)
{
case
1
:
return
i18n
(
"Awful"
);
case
2
:
return
i18n
(
"Bad"
);
case
3
:
return
i18n
(
"Barely tolerable"
);
case
4
:
return
i18n
(
"Tolerable"
);
case
5
:
return
i18n
(
"Okay"
);
case
6
:
return
i18n
(
"Good"
);
case
7
:
return
i18n
(
"Very good"
);
case
8
:
return
i18n
(
"Excellent"
);
case
9
:
return
i18n
(
"Amazing"
);
case
10
:
return
i18n
(
"Favorite"
);
case
0
:
default
:
return
i18n
(
"Not rated"
);
// assume weird values as not rated
}
return
"if you can see this, then that's a bad sign."
;
}
QStringList
MetaBundle
::
ratingList
()
{
QString
s
=
i18n
(
"rating - description"
,
"%1 - %2"
);
QStringList
list
;
list
+=
ratingDescription
(
0
);
for
(
int
i
=
1
;
i
<=
10
;
i
++
)
list
+=
s
.
arg
(
prettyRating
(
i
,
true
)
).
arg
(
ratingDescription
(
i
)
);
return
list
;
}
QStringList
MetaBundle
::
genreList
()
//static
{
QStringList
list
;
TagLib
::
StringList
genres
=
TagLib
::
ID3v1
::
genreList
();
for
(
TagLib
::
StringList
::
ConstIterator
it
=
genres
.
begin
(),
end
=
genres
.
end
();
it
!=
end
;
++
it
)
list
+=
TStringToQString
(
(
*
it
)
);
list
.
sort
();
return
list
;
}
void
MetaBundle
::
setExtendedTag
(
TagLib
::
File
*
file
,
int
tag
,
const
QString
value
)
{
const
char
*
id
=
0
;
if
(
m_type
==
mp3
)
{
switch
(
tag
)
{
case
(
composerTag
):
id
=
"TCOM"
;
break
;
case
(
discNumberTag
):
id
=
"TPOS"
;
break
;
case
(
bpmTag
):
id
=
"TBPM"
;
break
;
case
(
compilationTag
):
id
=
"TCMP"
;
break
;
case
(
albumArtistTag
):
id
=
"TPE2"
;
break
;
// non-standard: Apple, Microsoft
}
fprintf
(
stderr
,
"Setting extended tag %s to %s
\n
"
,
id
,
value
.
utf8
().
data
());
TagLib
::
MPEG
::
File
*
mpegFile
=
dynamic_cast
<
TagLib
::
MPEG
::
File
*>
(
file
);
if
(
mpegFile
&&
mpegFile
->
ID3v2Tag
()
)
{
if
(
value
.
isEmpty
()
)
mpegFile
->
ID3v2Tag
()
->
removeFrames
(
id
);
else
{
if
(
!
mpegFile
->
ID3v2Tag
()
->
frameListMap
()[
id
].
isEmpty
()
)
mpegFile
->
ID3v2Tag
()
->
frameListMap
()[
id
].
front
()
->
setText
(
QStringToTString
(
value
)
);
else
{
TagLib
::
ID3v2
::
TextIdentificationFrame
*
frame
=
new
TagLib
::
ID3v2
::
TextIdentificationFrame
(
id
,
TagLib
::
ID3v2
::
FrameFactory
::
instance
()
->
defaultTextEncoding
()
);
frame
->
setText
(
QStringToTString
(
value
)
);
mpegFile
->
ID3v2Tag
()
->
addFrame
(
frame
);
}
}
}
}
else
if
(
m_type
==
ogg
)
{
switch
(
tag
)
{
case
(
composerTag
):
id
=
"COMPOSER"
;
break
;
case
(
discNumberTag
):
id
=
"DISCNUMBER"
;
break
;
case
(
bpmTag
):
id
=
"BPM"
;
break
;
case
(
compilationTag
):
id
=
"COMPILATION"
;
break
;
case
(
albumArtistTag
):
id
=
"ALBUMARTIST"
;
break
;
// non-standard: Amarok
}
TagLib
::
Ogg
::
Vorbis
::
File
*
oggFile
=
dynamic_cast
<
TagLib
::
Ogg
::
Vorbis
::
File
*>
(
file
);
if
(
oggFile
&&
oggFile
->
tag
()
)
{
value
.
isEmpty
()
?
oggFile
->
tag
()
->
removeField
(
id
)
:
oggFile
->
tag
()
->
addField
(
id
,
QStringToTString
(
value
),
true
);
}
}
else
if
(
m_type
==
flac
)
{
switch
(
tag
)
{
case
(
composerTag
):
id
=
"COMPOSER"
;
break
;
case
(
discNumberTag
):
id
=
"DISCNUMBER"
;
break
;
case
(
bpmTag
):
id
=
"BPM"
;
break
;
case
(
compilationTag
):
id
=
"COMPILATION"
;
break
;
case
(
albumArtistTag
):
id
=
"ALBUMARTIST"
;
break
;
// non-standard: Amarok
}
TagLib
::
FLAC
::
File
*
flacFile
=
dynamic_cast
<
TagLib
::
FLAC
::
File
*>
(
file
);
if
(
flacFile
&&
flacFile
->
xiphComment
()
)
{
value
.
isEmpty
()
?
flacFile
->
xiphComment
()
->
removeField
(
id
)
:
flacFile
->
xiphComment
()
->
addField
(
id
,
QStringToTString
(
value
),
true
);
}
}
else
if
(
m_type
==
mp4
)
{
TagLib
::
MP4
::
Tag
*
mp4tag
=
dynamic_cast
<
TagLib
::
MP4
::
Tag
*>
(
file
->
tag
()
);
if
(
mp4tag
)
{
switch
(
tag
)
{
case
(
composerTag
):
mp4tag
->
setComposer
(
QStringToTString
(
value
)
);
break
;
case
(
discNumberTag
):
mp4tag
->
setDisk
(
value
.
toInt
()
);
case
(
bpmTag
):
mp4tag
->
setBpm
(
value
.
toInt
()
);
// mp4 doesn't support float bpm
case
(
compilationTag
):
mp4tag
->
setCompilation
(
value
.
toInt
()
==
CompilationYes
);
}
}
}
}
void
MetaBundle
::
setPodcastBundle
(
const
PodcastEpisodeBundle
&
peb
)
{
delete
m_podcastBundle
;
m_podcastBundle
=
new
PodcastEpisodeBundle
;
*
m_podcastBundle
=
peb
;
}
void
MetaBundle
::
setLastFmBundle
(
const
LastFm
::
Bundle
&
last
)
{
delete
m_lastFmBundle
;
// m_lastFmBundle = new LastFm::Bundle(last);
m_lastFmBundle
=
new
LastFm
::
Bundle
;
*
m_lastFmBundle
=
last
;
}
void
MetaBundle
::
loadImagesFromTag
(
const
TagLib
::
ID3v2
::
Tag
&
tag
,
EmbeddedImageList
&
images
)
const
{
TagLib
::
ID3v2
::
FrameList
l
=
tag
.
frameListMap
()[
"APIC"
];
foreachType
(
TagLib
::
ID3v2
::
FrameList
,
l
)
{
debug
()
<<
"Found APIC frame"
<<
endl
;
TagLib
::
ID3v2
::
AttachedPictureFrame
*
ap
=
static_cast
<
TagLib
::
ID3v2
::
AttachedPictureFrame
*>
(
*
it
);
const
TagLib
::
ByteVector
&
imgVector
=
ap
->
picture
();
debug
()
<<
"Size of image: "
<<
imgVector
.
size
()
<<
" byte"
<<
endl
;
// ignore APIC frames without picture and those with obviously bogus size
if
(
imgVector
.
size
()
>
0
&&
imgVector
.
size
()
<
10000000
/*10MB*/
)
{
images
.
push_back
(
EmbeddedImage
(
imgVector
,
ap
->
description
()
)
);
}
}
}
bool
MetaBundle
::
safeSave
()
{
bool
noproblem
;
MetaBundleSaver
mbs
(
this
);
TagLib
::
FileRef
*
fileref
=
mbs
.
prepareToSave
();
if
(
!
fileref
)
{
debug
()
<<
"Could not get a fileref!"
<<
endl
;
mbs
.
cleanupSave
();
return
false
;
}
noproblem
=
save
(
fileref
);
if
(
!
noproblem
)
{
debug
()
<<
"MetaBundle::save() didn't work!"
<<
endl
;
mbs
.
cleanupSave
();
return
false
;
}
noproblem
=
mbs
.
doSave
();
if
(
!
noproblem
)
{
debug
()
<<
"Something failed during the save, cleaning up and exiting!"
<<
endl
;
mbs
.
cleanupSave
();
return
false
;
}
setUniqueId
(
readUniqueId
()
);
if
(
CollectionDB
::
instance
()
->
isFileInCollection
(
url
().
path
()
)
)
CollectionDB
::
instance
()
->
doAFTStuff
(
this
,
false
);
noproblem
=
mbs
.
cleanupSave
();
return
noproblem
;
}
bool
MetaBundle
::
save
(
TagLib
::
FileRef
*
fileref
)
{
DEBUG_BLOCK
if
(
!
isFile
()
)
return
false
;
//Set default codec to UTF-8 (see bugs 111246 and 111232)
TagLib
::
ID3v2
::
FrameFactory
::
instance
()
->
setDefaultTextEncoding
(
TagLib
::
String
::
UTF8
);
bool
passedin
=
fileref
;
bool
returnval
=
false
;
TagLib
::
FileRef
*
f
;
if
(
!
passedin
)
f
=
new
TagLib
::
FileRef
(
QFile
::
encodeName
(
url
().
path
()
),
false
);
else
f
=
fileref
;
if
(
f
&&
!
f
->
isNull
()
)
{
TagLib
::
Tag
*
t
=
f
->
tag
();
if
(
t
)
{
// f.tag() can return null if the file couldn't be opened for writing
t
->
setTitle
(
QStringToTString
(
title
().
stripWhiteSpace
()
)
);
t
->
setArtist
(
QStringToTString
(
artist
().
string
().
stripWhiteSpace
()
)
);
t
->
setAlbum
(
QStringToTString
(
album
().
string
().
stripWhiteSpace
()
)
);
t
->
setTrack
(
track
()
);
t
->
setYear
(
year
()
);
t
->
setComment
(
QStringToTString
(
comment
().
string
().
stripWhiteSpace
()
)
);
t
->
setGenre
(
QStringToTString
(
genre
().
string
().
stripWhiteSpace
()
)
);
if
(
hasExtendedMetaInformation
()
)
{
setExtendedTag
(
f
->
file
(),
albumArtistTag
,
albumArtist
()
);
setExtendedTag
(
f
->
file
(),
composerTag
,
composer
().
string
().
stripWhiteSpace
()
);
setExtendedTag
(
f
->
file
(),
discNumberTag
,
discNumber
()
?
QString
::
number
(
discNumber
()
)
:
QString
()
);
setExtendedTag
(
f
->
file
(),
bpmTag
,
bpm
()
?
QString
::
number
(
bpm
()
)
:
QString
()
);
if
(
compilation
()
!=
CompilationUnknown
)
setExtendedTag
(
f
->
file
(),
compilationTag
,
QString
::
number
(
compilation
()
)
);
}
if
(
!
passedin
)
{
returnval
=
f
->
save
();
setUniqueId
(
readUniqueId
()
);
if
(
returnval
&&
CollectionDB
::
instance
()
->
isFileInCollection
(
url
().
path
()
)
)
CollectionDB
::
instance
()
->
doAFTStuff
(
this
,
false
);
}
else
returnval
=
true
;
}
}
if
(
!
passedin
)
delete
f
;
return
returnval
;
}
// QT's version encodeAttr is 1) private to QDom, and 2) a litte slow. This
// one can be made public if needed. It happens to be on a critical path
// (each char of playlist / undo save). QStyleSheet::escape does not deal with
// unicode chars illegal for XML. There's a lot of junk in those tags
static
inline
void
xmlEncode
(
QTextStream
&
stream
,
const
QString
&
str
)
{
QString
tmp
;
const
QString
*
cur
=
&
str
;
uint
i
=
0
;
while
(
i
<
cur
->
length
()
)
{
uint
uc
=
(
*
cur
)[
i
].
unicode
();
// we try to accumulate unescaped chars before writing to stream
const
char
*
escaped
;
// careful about the order of tests, common before less common
if
(
'a'
<=
uc
&&
uc
<=
'z'
||
'0'
<=
uc
&&
uc
<=
'9'
||
'A'
<=
uc
&&
uc
<=
'Z'
)
// most common case
escaped
=
NULL
;
else
if
(
uc
==
'<'
)
escaped
=
"<"
;
else
if
(
uc
==
'>'
)
escaped
=
">"
;
else
if
(
uc
==
'&'
)
escaped
=
"&"
;
else
if
(
uc
==
'"'
)
escaped
=
"""
;
else
{
// see if it's a XML-valid unicode char at all
if
(
(
0x20
<=
uc
&&
uc
<=
0xD7FF
||
0xE000
<=
uc
&&
uc
<=
0xFFFD
||
uc
==
0x9
||
uc
==
0xA
||
uc
==
0xD
)
)
// fairly common, other ascii chars
escaped
=
NULL
;
else
escaped
=
""
;
// special char, will write later
}
if
(
escaped
)
{
// flush previous unescaped chars
if
(
i
>
0
)
stream
<<
cur
->
left
(
i
);
tmp
=
cur
->
right
(
cur
->
length
()
-
i
-
1
);
cur
=
&
tmp
;
i
=
0
;
// now write escaped string
if
(
escaped
[
0
]
)
stream
<<
escaped
;
else
stream
<<
"&#x"
<<
QString
::
number
(
uc
,
16
)
<<
';'
;
}
else
i
++
;
}
if
(
!
cur
->
isEmpty
())
stream
<<
*
cur
;
}
bool
MetaBundle
::
save
(
QTextStream
&
stream
,
const
QStringList
&
attributes
)
const
{
// QDom is too slow to use here
stream
<<
" <item url=
\"
"
;
xmlEncode
(
stream
,
url
().
url
()
);
stream
<<
"
\"
uniqueid=
\"
"
<<
uniqueId
()
<<
'\"'
;
if
(
m_isCompilation
)
stream
<<
" compilation=
\"
True
\"
"
;
// attrs are never custom text (e.g. ID3 tags)
for
(
int
i
=
0
,
n
=
attributes
.
count
();
i
<
n
;
i
+=
2
)
stream
<<
" "
<<
attributes
[
i
]
<<
"=
\"
"
<<
attributes
[
i
+
1
]
<<
"
\"
"
;
stream
<<
">
\n
"
;
// end of <item ...>
for
(
int
i
=
0
;
i
<
NUM_COLUMNS
;
++
i
)
{
if
(
i
==
Filename
)
continue
;
// the file name is already in the URL
const
QString
&
tag
=
exactColumnName
(
i
);
// 2 indents
stream
<<
" <"
<<
tag
<<
">"
;
xmlEncode
(
stream
,
exactText
(
i
,
true
)
);
stream
<<
"</"
<<
tag
<<
">
\n
"
;
}
stream
<<
" </item>
\n
"
;
return
true
;
}
void
MetaBundle
::
setUrl
(
const
KURL
&
url
)
{
QValueList
<
int
>
changes
;
for
(
int
i
=
0
;
i
<
NUM_COLUMNS
;
++
i
)
changes
<<
i
;
aboutToChange
(
changes
);
m_url
=
url
;
reactToChanges
(
changes
);
setUniqueId
();
}
void
MetaBundle
::
setPath
(
const
QString
&
path
)
{
QValueList
<
int
>
changes
;
for
(
int
i
=
0
;
i
<
NUM_COLUMNS
;
++
i
)
changes
<<
i
;
aboutToChange
(
changes
);
m_url
.
setPath
(
path
);
reactToChanges
(
changes
);
setUniqueId
();
}
void
MetaBundle
::
setUniqueId
()
{
if
(
!
isFile
()
)
return
;
m_uniqueId
=
CollectionDB
::
instance
()
->
uniqueIdFromUrl
(
url
()
);
}
void
MetaBundle
::
setUniqueId
(
const
QString
&
id
)
{
//WARNING WARNING WARNING
//Don't call this function if you don't know what you're doing!
m_uniqueId
=
id
;
}
const
TagLib
::
ByteVector
MetaBundle
::
readUniqueIdHelper
(
TagLib
::
FileRef
fileref
)
const
{
if
(
TagLib
::
MPEG
::
File
*
file
=
dynamic_cast
<
TagLib
::
MPEG
::
File
*>
(
fileref
.
file
()
)
)
{
if
(
file
->
ID3v2Tag
()
)
return
file
->
ID3v2Tag
()
->
render
();
else
if
(
file
->
ID3v1Tag
()
)
return
file
->
ID3v1Tag
()
->
render
();
else
if
(
file
->
APETag
()
)
return
file
->
APETag
()
->
render
();
}
else
if
(
TagLib
::
Ogg
::
Vorbis
::
File
*
file
=
dynamic_cast
<
TagLib
::
Ogg
::
Vorbis
::
File
*>
(
fileref
.
file
()
)
)
{
if
(
file
->
tag
()
)
return
file
->
tag
()
->
render
();
}
else
if
(
TagLib
::
FLAC
::
File
*
file
=
dynamic_cast
<
TagLib
::
FLAC
::
File
*>
(
fileref
.
file
()
)
)
{
if
(
file
->
ID3v2Tag
()
)
return
file
->
ID3v2Tag
()
->
render
();
else
if
(
file
->
ID3v1Tag
()
)
return
file
->
ID3v1Tag
()
->
render
();
else
if
(
file
->
xiphComment
()
)
return
file
->
xiphComment
()
->
render
();
}
else
if
(
TagLib
::
Ogg
::
FLAC
::
File
*
file
=
dynamic_cast
<
TagLib
::
Ogg
::
FLAC
::
File
*>
(
fileref
.
file
()
)
)
{
if
(
file
->
tag
()
)
return
file
->
tag
()
->
render
();
}
else
if
(
TagLib
::
MPC
::
File
*
file
=
dynamic_cast
<
TagLib
::
MPC
::
File
*>
(
fileref
.
file
()
)
)
{
if
(
file
->
ID3v1Tag
()
)
return
file
->
ID3v1Tag
()
->
render
();
else
if
(
file
->
APETag
()
)
return
file
->
APETag
()
->
render
();
}
TagLib
::
ByteVector
bv
;
return
bv
;
}
QString
MetaBundle
::
readUniqueId
(
TagLib
::
FileRef
*
fileref
)
{
//This is used in case we don't get given a fileref
TagLib
::
FileRef
tmpfileref
;
if
(
!
fileref
&&
isFile
()
)
{
const
QString
path
=
url
().
path
();
//Make it get cleaned up at the end of the function automagically
tmpfileref
=
TagLib
::
FileRef
(
QFile
::
encodeName
(
path
),
true
,
TagLib
::
AudioProperties
::
Fast
);
fileref
=
&
tmpfileref
;
}
if
(
!
fileref
||
fileref
->
isNull
()
)
return
QString
();
TagLib
::
ByteVector
bv
=
readUniqueIdHelper
(
*
fileref
);
//get our unique id
KMD5
md5
(
0
,
0
);
QFile
qfile
(
url
().
path
()
);
char
databuf
[
8192
];
int
readlen
=
0
;
QCString
size
=
0
;
QString
returnval
;
md5
.
update
(
bv
.
data
(),
bv
.
size
()
);
if
(
qfile
.
open
(
IO_Raw
|
IO_ReadOnly
)
)
{
if
(
(
readlen
=
qfile
.
readBlock
(
databuf
,
8192
)
)
>
0
)
{
md5
.
update
(
databuf
,
readlen
);
md5
.
update
(
size
.
setNum
(
(
ulong
)
qfile
.
size
()
)
);
return
QString
(
md5
.
hexDigest
().
data
()
);
}
else
return
QString
();
}
return
QString
::
null
;
}
int
MetaBundle
::
getRand
()
{
//KRandom supposedly exists in SVN, although it's not checked out on my machine, and it's certainly not in 3.3, so I'm just going to steal its code
unsigned
int
seed
;
int
fd
=
open
(
"/dev/urandom"
,
O_RDONLY
);
if
(
fd
<
0
||
::
read
(
fd
,
&
seed
,
sizeof
(
seed
))
!=
sizeof
(
seed
))
{
// No /dev/urandom... try something else.
srand
(
getpid
());
seed
=
rand
()
+
time
(
0
);
}
if
(
fd
>=
0
)
close
(
fd
);
srand
(
seed
);
return
rand
();
}
QString
MetaBundle
::
getRandomString
(
int
size
,
bool
numbersOnly
)
{
if
(
size
!=
8
)
{
debug
()
<<
"Wrong size passed in!"
<<
endl
;
return
QString
();
}
QString
str
;
//do a memory op once, much faster than doing multiple later, especially since we know how big it will be
str
.
reserve
(
size
);
int
i
=
getRand
();
//seed it
i
=
0
;
while
(
size
--
)
{
// check your ASCII tables
// we want characters you can see...93 is the range from ! to ~
int
r
=
rand
()
%
94
;
// shift the value to the visible characters
r
+=
33
;
// we don't want ", %, ', <, >, \, `, or &
// so that we don't have issues with escaping/quoting in QStrings,
// and so that we don't have <> in our XML files where they might cause issues
// hopefully this list is final, as once users really start using this
// it will be a pain to change...however, there is an ATF version in CollectionDB
// which will help if this ever needs to change
// In addition we can change our vendor string
while
(
r
==
34
||
r
==
37
||
r
==
38
||
r
==
39
||
r
==
60
||
r
==
62
||
r
==
92
||
r
==
96
)
r
++
;
if
(
numbersOnly
&&
(
r
<
48
||
r
>
57
)
)
{
size
++
;
continue
;
}
str
[
i
++
]
=
char
(
r
);
// this next comment kept in for fun, as it was from the source of KRandomString, where I got
// most of this code from to start with :-)
// so what if I work backwards?
}
return
str
;
}
void
MetaBundle
::
setTitle
(
const
QString
&
title
)
{
aboutToChange
(
Title
);
m_title
=
title
;
reactToChange
(
Title
);
}
void
MetaBundle
::
setArtist
(
const
AtomicString
&
artist
)
{
aboutToChange
(
Artist
);
m_artist
=
artist
;
reactToChange
(
Artist
);
}
void
MetaBundle
::
setAlbum
(
const
AtomicString
&
album
)
{
aboutToChange
(
Album
);
m_album
=
album
;
reactToChange
(
Album
);
}
void
MetaBundle
::
setComment
(
const
AtomicString
&
comment
)
{
aboutToChange
(
Comment
);
m_comment
=
comment
;
reactToChange
(
Comment
);
}
void
MetaBundle
::
setGenre
(
const
AtomicString
&
genre
)
{
aboutToChange
(
Genre
);
m_genre
=
genre
;
reactToChange
(
Genre
);
}
void
MetaBundle
::
setYear
(
int
year
)
{
aboutToChange
(
Year
);
m_year
=
year
;
reactToChange
(
Year
);
}
void
MetaBundle
::
setTrack
(
int
track
)
{
aboutToChange
(
Track
);
m_track
=
track
;
reactToChange
(
Track
);
}
void
MetaBundle
::
setCompilation
(
int
compilation
)
{
switch
(
compilation
)
{
case
CompilationYes
:
m_isCompilation
=
true
;
m_notCompilation
=
false
;
break
;
case
CompilationNo
:
m_isCompilation
=
false
;
m_notCompilation
=
true
;
break
;
case
CompilationUnknown
:
m_isCompilation
=
m_notCompilation
=
false
;
break
;
}
}
void
MetaBundle
::
setLength
(
int
length
)
{
aboutToChange
(
Length
);
m_length
=
length
;
reactToChange
(
Length
);
}
void
MetaBundle
::
setBitrate
(
int
bitrate
)
{
aboutToChange
(
Bitrate
);
m_bitrate
=
bitrate
;
reactToChange
(
Bitrate
);
}
void
MetaBundle
::
setSampleRate
(
int
sampleRate
)
{
aboutToChange
(
SampleRate
);
m_sampleRate
=
sampleRate
;
reactToChange
(
SampleRate
);
}
void
MetaBundle
::
setDiscNumber
(
int
discnumber
)
{
aboutToChange
(
DiscNumber
);
m_discNumber
=
discnumber
;
reactToChange
(
DiscNumber
);
}
void
MetaBundle
::
setBpm
(
float
bpm
)
{
aboutToChange
(
Bpm
);
m_bpm
=
bpm
;
reactToChange
(
Bpm
);
}
void
MetaBundle
::
setComposer
(
const
AtomicString
&
composer
)
{
aboutToChange
(
Composer
);
m_composer
=
composer
;
reactToChange
(
Composer
);
}
void
MetaBundle
::
setAlbumArtist
(
const
AtomicString
&
albumArtist
)
{
aboutToChange
(
AlbumArtist
);
m_albumArtist
=
albumArtist
;
reactToChange
(
AlbumArtist
);
}
void
MetaBundle
::
setPlayCount
(
int
playcount
)
{
aboutToChange
(
PlayCount
);
m_playCount
=
playcount
;
reactToChange
(
PlayCount
);
}
void
MetaBundle
::
setLastPlay
(
uint
lastplay
)
{
aboutToChange
(
LastPlayed
);
m_lastPlay
=
lastplay
;
reactToChange
(
LastPlayed
);
}
void
MetaBundle
::
setRating
(
int
rating
)
{
aboutToChange
(
Rating
);
m_rating
=
rating
;
reactToChange
(
Rating
);
}
void
MetaBundle
::
setScore
(
float
score
)
{
aboutToChange
(
Score
);
m_score
=
score
;
reactToChange
(
Score
);
}
void
MetaBundle
::
setFilesize
(
int
bytes
)
{
aboutToChange
(
Filesize
);
m_filesize
=
bytes
;
reactToChange
(
Filesize
);
}
void
MetaBundle
::
setFileType
(
int
type
)
{
m_type
=
type
;
}
void
MetaBundle
::
detach
()
{
// FIXME: we'd do that, but unfortunately it does not exist
//m_url.detach();
m_url
=
Amarok
::
detachedKURL
(
m_url
);
m_title
=
QDeepCopy
<
QString
>
(
m_title
);
m_artist
=
m_artist
.
deepCopy
();
m_albumArtist
=
m_albumArtist
.
deepCopy
();
m_album
=
m_album
.
deepCopy
();
m_comment
=
m_comment
.
deepCopy
();
m_composer
=
m_composer
.
deepCopy
();
m_genre
=
m_genre
.
deepCopy
();
m_streamName
=
QDeepCopy
<
QString
>
(
m_streamName
);
m_streamUrl
=
QDeepCopy
<
QString
>
(
m_streamUrl
);
if
(
m_moodbar
!=
0
)
m_moodbar
->
detach
();
m_uniqueId
=
QDeepCopy
<
QString
>
(
m_uniqueId
);
if
(
m_podcastBundle
)
setPodcastBundle
(
QDeepCopy
<
PodcastEpisodeBundle
>
(
*
m_podcastBundle
)
);
if
(
m_lastFmBundle
)
setLastFmBundle
(
QDeepCopy
<
LastFm
::
Bundle
>
(
*
m_lastFmBundle
)
);
}
void
PodcastEpisodeBundle
::
detach
()
{
m_url
=
Amarok
::
detachedKURL
(
m_url
);
m_localUrl
=
Amarok
::
detachedKURL
(
m_localUrl
);
m_parent
=
Amarok
::
detachedKURL
(
m_parent
);
m_author
=
QDeepCopy
<
QString
>
(
m_author
);
m_title
=
QDeepCopy
<
QString
>
(
m_title
);
m_subtitle
=
QDeepCopy
<
QString
>
(
m_subtitle
);
m_description
=
QDeepCopy
<
QString
>
(
m_subtitle
);
m_date
=
QDeepCopy
<
QString
>
(
m_date
);
m_type
=
QDeepCopy
<
QString
>
(
m_type
);
m_guid
=
QDeepCopy
<
QString
>
(
m_guid
);
}
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Sat, Nov 14, 8:58 PM (9 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
134942
Default Alt Text
metabundle.cpp (63 KB)
Attached To
Mode
R70 Amarok
Attached
Detach File
Event Timeline