1 /**
2  * \file taggedfile.h
3  * Base class for tagged files.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 25 Sep 2005
8  *
9  * Copyright (C) 2005-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #pragma once
28 
29 #include <QString>
30 #include <QStringList>
31 #include <QList>
32 #include <QPersistentModelIndex>
33 #include "frame.h"
34 
35 class FileProxyModel;
36 
37 /** Base class for tagged files. */
38 class KID3_CORE_EXPORT TaggedFile {
39 public:
40   /**
41    * Special features and formats supported.
42    * Additional information which cannot be deduced from the file format
43    * supported.
44    */
45   enum Feature {
46     TF_ID3v11      = 1 << 0, /**< Supports ID3v1.1 tags */
47     TF_ID3v22      = 1 << 1, /**< Supports ID3v2.2 tags */
48     TF_ID3v23      = 1 << 2, /**< Supports ID3v2.3 tags */
49     TF_ID3v24      = 1 << 3, /**< Supports ID3v2.4 tags */
50     TF_OggPictures = 1 << 4, /**< Supports pictures in Ogg files */
51     TF_OggFlac     = 1 << 5  /**< Supports Ogg FLAC files */
52   };
53 
54   /** Information about file. */
55   struct KID3_CORE_EXPORT DetailInfo {
56     /** Channel mode. */
57     enum ChannelMode { CM_None, CM_Stereo, CM_JointStereo };
58 
59     /** Constructor. */
60     DetailInfo();
61 
62     QString format;          /**< format description */
63     ChannelMode channelMode; /**< channel mode */
64     unsigned channels;       /**< number of channels > 0 */
65     unsigned sampleRate;     /**< sample rate in Hz > 0 */
66     unsigned bitrate;        /**< 0 < bitrate in kbps < 16384 */
67     unsigned long duration;  /**< duration in seconds > 0 */
68     bool valid;              /**< true if information valid */
69     bool vbr;                /**< true if variable bitrate */
70 
71     /**
72      * Get string representation of detail information.
73      * @return information summary as string.
74      */
75     QString toString() const;
76   };
77 
78   /**
79    * Constructor.
80    *
81    * @param idx index in file proxy model
82    */
83   explicit TaggedFile(const QPersistentModelIndex& idx);
84 
85   /**
86    * Destructor.
87    */
88   virtual ~TaggedFile() = default;
89 
90   /**
91    * Set file name.
92    *
93    * @param fn file name
94    */
95   void setFilename(const QString& fn);
96 
97   /**
98    * Set file name and format it if format while editing is switched on.
99    *
100    * @param fn file name
101    */
102   void setFilenameFormattedIfEnabled(QString fn);
103 
104   /**
105    * Get file name.
106    *
107    * @return file name
108    */
getFilename()109   const QString& getFilename() const { return m_newFilename; }
110 
111   /**
112    * Get directory name.
113    *
114    * @return directory name
115    */
116   QString getDirname() const;
117 
118   /**
119    * Get key of tagged file format.
120    * @return key.
121    */
122   virtual QString taggedFileKey() const = 0;
123 
124   /**
125    * Get features supported.
126    * @return bit mask with Feature flags set.
127    */
128   virtual int taggedFileFeatures() const;
129 
130   /**
131    * Get currently active tagged file features.
132    * @return active tagged file features.
133    * @see setActiveTaggedFileFeatures()
134    */
135   virtual int activeTaggedFileFeatures() const;
136 
137   /**
138    * Activate some features provided by the tagged file.
139    * For example, if the TF_ID3v24 feature is provided, it can be set, so that
140    * writeTags() will write ID3v2.4.0 tags. If the feature is deactivated by
141    * passing 0, tags in the default format will be written again.
142    *
143    * @param features bit mask with some of the Feature flags which are
144    * provided by this file, as returned by taggedFileFeatures(), 0 to disable
145    * special features.
146    */
147   virtual void setActiveTaggedFileFeatures(int features);
148 
149   /**
150    * Read tags from file.
151    * Implementations should call notifyModelDataChanged().
152    *
153    * @param force true to force reading even if tags were already read.
154    */
155   virtual void readTags(bool force) = 0;
156 
157   /**
158    * Write tags to file and rename it if necessary.
159    *
160    * @param force   true to force writing even if file was not changed.
161    * @param renamed will be set to true if the file was renamed,
162    *                i.e. the file name is no longer valid, else *renamed
163    *                is left unchanged
164    * @param preserve true to preserve file time stamps
165    *
166    * @return true if ok, false if the file could not be written or renamed.
167    */
168   virtual bool writeTags(bool force, bool* renamed, bool preserve) = 0;
169 
170   /**
171    * Free resources allocated when calling readTags().
172    * Implementations should call notifyModelDataChanged().
173    *
174    * @param force true to force clearing even if the tags are modified
175    */
176   virtual void clearTags(bool force) = 0;
177 
178   /**
179    * Remove frames.
180    *
181    * @param tagNr tag number
182    * @param flt filter specifying which frames to remove
183    */
184   virtual void deleteFrames(Frame::TagNumber tagNr, const FrameFilter& flt);
185 
186   /**
187    * Check if file has a tag.
188    *
189    * @return true if a tag is available.
190    * @see isTagInformationRead()
191    */
192   virtual bool hasTag(Frame::TagNumber tagNr) const;
193 
194   /**
195    * Check if tags are supported by the format of this file.
196    *
197    * @param tagNr tag number
198    * @return true if tags are supported.
199    */
200   virtual bool isTagSupported(Frame::TagNumber tagNr) const;
201 
202   /**
203    * Check if tag information has already been read.
204    *
205    * @return true if information is available,
206    *         false if the tags have not been read yet, in which case
207    *         hasTag() does not return meaningful information.
208    */
209   virtual bool isTagInformationRead() const = 0;
210 
211   /**
212    * Get technical detail information.
213    *
214    * @param info the detail information is returned here
215    */
216   virtual void getDetailInfo(DetailInfo& info) const = 0;
217 
218   /**
219    * Get duration of file.
220    *
221    * @return duration in seconds,
222    *         0 if unknown.
223    */
224   virtual unsigned getDuration() const = 0;
225 
226   /**
227    * Get file extension including the dot.
228    *
229    * @return file extension, e.g. ".mp3".
230    */
231   virtual QString getFileExtension() const = 0;
232 
233   /**
234    * Get the format of tag.
235    *
236    * @param tagNr tag number
237    * @return string describing format of tag,
238    *         e.g. "ID3v1.1", "ID3v2.3", "Vorbis", "APE",
239    *         QString::null if unknown.
240    */
241   virtual QString getTagFormat(Frame::TagNumber tagNr) const;
242 
243   /**
244    * Get a specific frame from the tags.
245    *
246    * @param tagNr tag number
247    * @param type  frame type
248    * @param frame the frame is returned here
249    *
250    * @return true if ok.
251    */
252   virtual bool getFrame(Frame::TagNumber tagNr, Frame::Type type, Frame& frame) const = 0;
253 
254   /**
255    * Set a frame in the tags.
256    *
257    * @param tagNr tag number
258    * @param frame frame to set.
259    *
260    * @return true if ok.
261    */
262   virtual bool setFrame(Frame::TagNumber tagNr, const Frame& frame) = 0;
263 
264   /**
265    * Add a frame in the tags.
266    *
267    * @param tagNr tag number
268    * @param frame frame to add, a field list may be added by this method
269    *
270    * @return true if ok.
271    */
272   virtual bool addFrame(Frame::TagNumber tagNr, Frame& frame);
273 
274   /**
275    * Delete a frame from the tags.
276    *
277    * @param tagNr tag number
278    * @param frame frame to delete
279    *
280    * @return true if ok.
281    */
282   virtual bool deleteFrame(Frame::TagNumber tagNr, const Frame& frame);
283 
284   /**
285    * Get a list of frame IDs which can be added.
286    * @param tagNr tag number
287    * @return list with frame IDs.
288    */
289   virtual QStringList getFrameIds(Frame::TagNumber tagNr) const = 0;
290 
291   /**
292    * Get all frames in tag.
293    *
294    * @param tagNr tag number
295    * @param frames frame collection to set.
296    */
297   virtual void getAllFrames(Frame::TagNumber tagNr, FrameCollection& frames);
298 
299   /**
300    * Close any file handles which are held open by the tagged file object.
301    * The default implementation does nothing. If a concrete subclass holds
302    * any file handles open, it has to close them in this method. This method
303    * can be used before operations which require that a file is not open,
304    * e.g. file renaming on Windows.
305    */
306   virtual void closeFileHandle();
307 
308   /**
309    * Add a suitable field list for the frame if missing.
310    * If a frame is created, its field list is empty. This method will create
311    * a field list appropriate for the frame type and tagged file type if no
312    * field list exists. The default implementation does nothing.
313    * @param tagNr tag number
314    * @param frame frame where field list is added
315    */
316   virtual void addFieldList(Frame::TagNumber tagNr, Frame& frame) const;
317 
318   /**
319    * Set frames in tag.
320    *
321    * @param tagNr tag number
322    * @param frames      frame collection
323    * @param onlyChanged only frames with value marked as changed are set
324    */
325   void setFrames(Frame::TagNumber tagNr, const FrameCollection& frames, bool onlyChanged = true);
326 
327   /**
328    * Get tags from filename.
329    * Supported formats:
330    * album/track - artist - song
331    * artist - album/track song
332    * /artist - album - track - song
333    * album/artist - track - song
334    * artist/album/track song
335    * album/artist - song
336    *
337    * @param frames frames to put result
338    * @param fmt format string containing the following codes:
339    *            %s title (song)
340    *            %l album
341    *            %a artist
342    *            %c comment
343    *            %y year
344    *            %t track
345    */
346   void getTagsFromFilename(FrameCollection& frames, const QString& fmt);
347 
348   /**
349    * Check if file is changed.
350    *
351    * @return true if file was changed.
352    */
isChanged()353   bool isChanged() const { return m_modified; }
354 
355   /**
356    * Check if filename is changed.
357    *
358    * @return true if filename was changed.
359    */
isFilenameChanged()360   bool isFilenameChanged() const { return m_newFilename != m_filename; }
361 
362   /**
363    * Get absolute filename.
364    *
365    * @return absolute file path.
366    */
367   QString getAbsFilename() const;
368 
369   /**
370    * Undo reverted modification of filename.
371    * When writeTags() fails because the file is not writable, the filename is
372    * reverted using revertChangedFilename() so that the file permissions can be
373    * changed using the real filename. After changing the permissions, this
374    * function can be used to change the filename back before saving the file.
375    */
376   void undoRevertChangedFilename();
377 
378   /**
379    * Update the current filename after the file was renamed.
380    */
381   void updateCurrentFilename();
382 
383   /**
384    * Check if tag was changed.
385    * @param tagNr tag number
386    * @return true if tag 1 was changed.
387    */
isTagChanged(Frame::TagNumber tagNr)388   bool isTagChanged(Frame::TagNumber tagNr) const {
389     return tagNr < Frame::Tag_NumValues ? m_changed[tagNr] : false;
390   }
391 
392   /**
393    * Mark tag as changed.
394    *
395    * @param tagNr tag number
396    * @param type type of changed frame
397    */
398   void markTagChanged(Frame::TagNumber tagNr, Frame::Type type);
399 
400   /**
401    * Mark tag as unchanged.
402    * @param tagNr tag number
403    */
404   void markTagUnchanged(Frame::TagNumber tagNr);
405 
406   /**
407    * Get the mask of the frame types changed in tag.
408    * @param tagNr tag number
409    * @return mask of frame types.
410    */
getChangedFrames(Frame::TagNumber tagNr)411   quint64 getChangedFrames(Frame::TagNumber tagNr) const {
412     return tagNr < Frame::Tag_NumValues ? m_changedFrames[tagNr] : 0;
413   }
414 
415   /**
416    * Set the mask of the frame types changed in tag.
417    * @param tagNr tag number
418    * @param mask mask of frame types
419    */
420   void setChangedFrames(Frame::TagNumber tagNr, quint64 mask);
421 
422   /**
423    * Get the truncation flags.
424    * @param tagNr tag number
425    * @return truncation flags.
426    */
getTruncationFlags(Frame::TagNumber tagNr)427   quint64 getTruncationFlags(Frame::TagNumber tagNr) const {
428     return tagNr == Frame::Tag_Id3v1 ? m_truncation : 0;
429   }
430 
431   /**
432    * Format track number/total number of tracks with configured digits.
433    *
434    * @param num track number, <= 0 if empty
435    * @param numTracks total number of tracks, <= 0 to disable
436    *
437    * @return formatted "track/total" string.
438    */
439   QString trackNumberString(int num, int numTracks) const;
440 
441   /**
442    * Format the track number (digits, total number of tracks) if enabled.
443    *
444    * @param value    string containing track number, will be modified
445    * @param addTotal true to add total number of tracks if enabled
446    *                 "/t" with t = total number of tracks will be appended
447    *                 if enabled and value contains a number
448    */
449   void formatTrackNumberIfEnabled(QString& value, bool addTotal) const;
450 
451   /**
452    * Get the total number of tracks in the directory.
453    *
454    * @return total number of tracks, -1 if unavailable.
455    */
456   int getTotalNumberOfTracksInDir() const;
457 
458   /**
459    * Get index of tagged file in model.
460    * @return index
461    */
getIndex()462   const QPersistentModelIndex& getIndex() const { return m_index; }
463 
464   /**
465    * Check if the file is marked.
466    */
isMarked()467   bool isMarked() const { return m_marked; }
468 
469   /**
470    * Format a time string "h:mm:ss".
471    * If the time is less than an hour, the hour is not put into the
472    * string and the minute is not padded with zeroes.
473    *
474    * @param seconds time in seconds
475    *
476    * @return string with the time in hours, minutes and seconds.
477    */
478   static QString formatTime(unsigned seconds);
479 
480   /**
481    * Split a track string into number and total.
482    *
483    * @param str track
484    * @param total the total is returned here if found, else 0
485    *
486    * @return number, 0 if parsing failed, -1 if str is null
487    */
488   static int splitNumberAndTotal(const QString& str, int* total=nullptr);
489 
490   /**
491    * Get access and modification time of file.
492    * @param path file path
493    * @param actime the last access time is returned here
494    * @param modtime the last modification time is returned here
495    * @return true if ok.
496    */
497   static bool getFileTimeStamps(const QString& path,
498                                 quint64& actime, quint64& modtime);
499 
500   /**
501    * Set access and modification time of file.
502    * @param path file path
503    * @param actime last access time
504    * @param modtime last modification time
505    * @return true if ok.
506    */
507   static bool setFileTimeStamps(const QString& path,
508                                 quint64 actime, quint64 modtime);
509 
510   /**
511    * Free static resources.
512    */
513   static void staticCleanup();
514 
515 protected:
516   /**
517    * Rename a file.
518    * This methods takes care of case insensitive filesystems.
519    * @return true if ok.
520    */
521   bool renameFile() const;
522 
523   /**
524    * Get field name for comment from configuration.
525    *
526    * @return field name.
527    */
528   QString getCommentFieldName() const;
529 
530     /**
531    * Get the total number of tracks if it is enabled.
532    *
533    * @return total number of tracks,
534    *         -1 if disabled or unavailable.
535    */
536   int getTotalNumberOfTracksIfEnabled() const;
537 
538   /**
539    * Get the number of track number digits configured.
540    *
541    * @return track number digits,
542    *         1 if invalid or unavailable.
543    */
544   int getTrackNumberDigits() const;
545 
546   /**
547    * Get current filename.
548    * @return existing name.
549    */
currentFilename()550   QString currentFilename() const { return m_filename; }
551 
552   /**
553    * Get current path to file.
554    * @return absolute path.
555    */
556   QString currentFilePath() const;
557 
558   /**
559    * Mark filename as unchanged.
560    */
561   void markFilenameUnchanged();
562 
563   /**
564    * Revert modification of filename.
565    */
566   void revertChangedFilename();
567 
568   /**
569    * Check if a string has to be truncated.
570    *
571    * @param tagNr tag number
572    * @param str  string to be checked
573    * @param flag flag to be set if string has to be truncated
574    * @param len  maximum length of string
575    *
576    * @return str truncated to len characters if necessary, else QString::null.
577    */
578   QString checkTruncation(Frame::TagNumber tagNr, const QString& str,
579                           quint64 flag, int len = 30);
580 
581   /**
582    * Check if a number has to be truncated.
583    *
584    * @param tagNr tag number
585    * @param val  value to be checked
586    * @param flag flag to be set if number has to be truncated
587    * @param max  maximum value
588    *
589    * @return val truncated to max if necessary, else -1.
590    */
591   int checkTruncation(Frame::TagNumber tagNr, int val, quint64 flag,
592                       int max = 255);
593 
594   /**
595    * Clear all truncation flags.
596    * @param tagNr tag number
597    */
clearTrunctionFlags(Frame::TagNumber tagNr)598   void clearTrunctionFlags(Frame::TagNumber tagNr) {
599     if (tagNr == Frame::Tag_Id3v1) m_truncation = 0;
600   }
601 
602   /**
603    * Get file proxy model.
604    * @return file proxy model.
605    */
606   const FileProxyModel* getFileProxyModel() const;
607 
608   /**
609    * Notify model about changes in extra model data, e.g. the information on
610    * which the CoreTaggedFileIconProvider depends.
611    *
612    * This method shall be called when such data changes, e.g. at the end of
613    * readTags() implementations.
614    *
615    * @param priorIsTagInformationRead prior value returned by
616    * isTagInformationRead()
617    */
618   void notifyModelDataChanged(bool priorIsTagInformationRead) const;
619 
620   /**
621    * Notify model about changes in the truncation state.
622    *
623    * This method shall be called when truncation is checked.
624    *
625    * @param priorTruncation prior value of m_truncation != 0
626    */
627   void notifyTruncationChanged(bool priorTruncation) const;
628 
629   /**
630    * Update marked property of frames.
631    * Mark frames which violate configured rules. This method should be called
632    * in reimplementations of getAllFrames().
633    *
634    * @param tagNr tag number
635    * @param frames frames to check
636    */
637   void updateMarkedState(Frame::TagNumber tagNr, FrameCollection& frames);
638 
639 private:
640   TaggedFile(const TaggedFile&);
641   TaggedFile& operator=(const TaggedFile&);
642 
643   void updateModifiedState();
644 
645   /** Index of file in model */
646   QPersistentModelIndex m_index;
647   /** File name */
648   QString m_filename;
649   /** New file name */
650   QString m_newFilename;
651   /** File name reverted because file was not writable */
652   QString m_revertedFilename;
653   /** changed tag frame types */
654   quint64 m_changedFrames[Frame::Tag_NumValues];
655   /** Truncation flags. */
656   quint64 m_truncation;
657   /** true if tags were changed */
658   bool m_changed[Frame::Tag_NumValues];
659   /** true if tagged file is modified */
660   bool m_modified;
661   /** true if tagged file is marked */
662   bool m_marked;
663 };
664