summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS2
-rw-r--r--COPYING481
-rw-r--r--ChangeLog19
-rw-r--r--INSTALL229
-rw-r--r--Makefile.am16
-rw-r--r--NEWS0
-rw-r--r--README0
-rwxr-xr-xautogen.sh20
-rw-r--r--configure.ac120
-rw-r--r--libgpod-1.0.pc.in11
-rw-r--r--po/ChangeLog0
-rw-r--r--po/Makefile.in.in263
-rw-r--r--po/POTFILES.in2
-rw-r--r--src/Makefile.am18
-rw-r--r--src/itdb.h504
-rw-r--r--src/itdb_itunesdb.c3622
-rw-r--r--src/itdb_playlist.c1290
-rw-r--r--src/itdb_private.h100
-rw-r--r--src/itdb_track.c205
-rw-r--r--tests/Makefile.am7
-rw-r--r--tests/itdb_main.c88
21 files changed, 6997 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..d0a7de6
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+Jorg Schuler <jcsjcs at users.sourceforge.net> (original gtkpod code)
+Christophe Fergeau <teuf at gnome.org> (libraryfication)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..eb685a5
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,481 @@
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it. You can use it for
+your libraries, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library. If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software. To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+ Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs. This
+license, the GNU Library General Public License, applies to certain
+designated libraries. This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+ The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it. Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program. However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+ Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries. We
+concluded that weaker conditions might promote sharing better.
+
+ However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves. This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them. (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.) The hope is that this
+will lead to faster development of free libraries.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+ Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License"). Each licensee is
+addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ c) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ d) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ Appendix: How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..004a700
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,19 @@
+2005-09-10 Christophe Fergeau <teuf@gnome.org>
+
+ * AUTHORS:
+ * COPYING:
+ * INSTALL:
+ * Makefile.am:
+ * autogen.sh:
+ * configure.ac:
+ * libgpod-1.0.pc.in:
+ * po/Makefile.in.in:
+ * po/POTFILES.in:
+ * src/Makefile.am:
+ * src/itdb.h:
+ * src/itdb_itunesdb.c:
+ * src/itdb_playlist.c:
+ * src/itdb_private.h:
+ * src/itdb_track.c:
+ * tests/Makefile.am:
+ * tests/itdb_main.c: initial import
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..a4b3414
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,229 @@
+Copyright 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software
+Foundation, Inc.
+
+ This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+ These are generic installation instructions.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+ It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring. (Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.)
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+ The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'. You only need
+`configure.ac' if you want to change it or regenerate `configure' using
+a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system. If you're
+ using `csh' on an old version of System V, you might need to type
+ `sh ./configure' instead to prevent `csh' from trying to execute
+ `configure' itself.
+
+ Running `configure' takes awhile. While running, it prints some
+ messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation.
+
+ 5. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+Compilers and Options
+=====================
+
+ Some systems require unusual options for compilation or linking that
+the `configure' script does not know about. Run `./configure --help'
+for details on some of the pertinent environment variables.
+
+ You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment. Here
+is an example:
+
+ ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix
+
+ *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+ You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+ If you have to use a `make' that does not support the `VPATH'
+variable, you have to compile the package for one architecture at a
+time in the source code directory. After you have installed the
+package for one architecture, use `make distclean' before reconfiguring
+for another architecture.
+
+Installation Names
+==================
+
+ By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc. You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=PATH' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+ Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+ There may be some features `configure' cannot figure out
+automatically, but needs to determine by the type of machine the package
+will run on. Usually, assuming the package is built to be run on the
+_same_ architectures, `configure' can figure that out, but if it prints
+a message saying it cannot guess the machine type, give it the
+`--build=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+ CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+ OS KERNEL-OS
+
+ See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+ If you are _building_ compiler tools for cross-compiling, you should
+use the `--target=TYPE' option to select the type of system they will
+produce code for.
+
+ If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+ If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+ Variables not defined in a site shell script can be set in the
+environment passed to `configure'. However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost. In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'. For example:
+
+ ./configure CC=/usr/local2/bin/gcc
+
+will cause the specified gcc to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+`configure' Invocation
+======================
+
+ `configure' recognizes the following options to control how it
+operates.
+
+`--help'
+`-h'
+ Print a summary of the options to `configure', and exit.
+
+`--version'
+`-V'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`--cache-file=FILE'
+ Enable the cache: use and save the results of the tests in FILE,
+ traditionally `config.cache'. FILE defaults to `/dev/null' to
+ disable caching.
+
+`--config-cache'
+`-C'
+ Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`configure' also accepts some other, not widely useful, options. Run
+`configure --help' for more details.
+
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..76e7436
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,16 @@
+SUBDIRS=src tests po
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libgpod-1.0.pc
+
+EXTRA_DIST = \
+ intltool-merge.in \
+ intltool-update.in \
+ intltool-extract.in \
+ libgpod-1.0.pc.in
+
+DISTCLEANFILES = \
+ intltool-extract \
+ intltool-merge \
+ intltool-update
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWS
diff --git a/README b/README
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/README
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..b0daa2f
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+PKG_NAME="libgpod"
+
+(test -f $srcdir/configure.ac) || {
+ echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
+ echo " top-level $PKG_NAME directory"
+ exit 1
+}
+
+which gnome-autogen.sh || {
+ echo "You need to install gnome-common from the GNOME CVS"
+ exit 1
+}
+
+REQUIRED_AUTOMAKE_VERSION=1.8 USE_GNOME2_MACROS=1 . gnome-autogen.sh
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..6a5ef92
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,120 @@
+AC_PREREQ(2.52)
+AC_INIT(src)
+AC_CONFIG_SRCDIR(configure.ac)
+
+AM_CONFIG_HEADER(config.h)
+
+# Making releases:
+# LIBGPOD_MICRO_VERSION += 1;
+# LIBGPOD_INTERFACE_AGE += 1;
+# if any functions have been added, set LIBGPOD_INTERFACE_AGE to 0.
+# if backwards compatibility has been broken,
+# set LIBGPOD_BINARY_AGE and LIBGPOD_INTERFACE_AGE to 0.
+#
+LIBGPOD_MAJOR_VERSION=0
+LIBGPOD_MINOR_VERSION=1
+LIBGPOD_MICRO_VERSION=1
+LIBGPOD_INTERFACE_AGE=1
+# If you need a modifier for the version number.
+# Normally empty, but can be used to make "fixup" releases.
+LIBGPOD_EXTRAVERSION=
+
+dnl libtool versioning from libgnome
+
+LIBGPOD_CURRENT=`expr 100 '*' $LIBGPOD_MINOR_VERSION + $LIBGPOD_MICRO_VERSION - $LIBGPOD_INTERFACE_AGE`
+LIBGPOD_BINARY_AGE=`expr 100 '*' $LIBGPOD_MINOR_VERSION + $LIBGPOD_MICRO_VERSION`
+LIBGPOD_REVISION=$LIBGPOD_INTERFACE_AGE
+LIBGPOD_AGE=`expr $LIBGPOD_BINARY_AGE - $LIBGPOD_INTERFACE_AGE`
+LIBGPOD_VERSION=$LIBGPOD_MAJOR_VERSION.$LIBGPOD_MINOR_VERSION.$LIBGPOD_MICRO_VERSION$LIBGPOD_EXTRAVERSION
+
+AC_SUBST(LIBGPOD_CURRENT)
+AC_SUBST(LIBGPOD_REVISION)
+AC_SUBST(LIBGPOD_AGE)
+AC_SUBST(LIBGPOD_VERSION)
+
+AM_INIT_AUTOMAKE(libgpod, $LIBGPOD_VERSION)
+
+dnl make sure we keep ACLOCAL_FLAGS around for maintainer builds to work
+AC_SUBST(ACLOCAL_AMFLAGS, "$ACLOCAL_FLAGS")
+
+AM_MAINTAINER_MODE
+
+AC_PROG_CC
+AC_PROG_LD
+AC_STDC_HEADERS
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+AC_PROG_INTLTOOL([0.21])
+
+PKG_CHECK_MODULES(LIBGPOD, glib-2.0)
+LIBGPOD_CFLAGS="$LIBGPOD_CFLAGS -Wall"
+AC_SUBST(LIBGPOD_CFLAGS)
+AC_SUBST(LIBGPOD_LIBS)
+
+dnl **************************************************
+dnl * internationalization support
+dnl **************************************************
+ALL_LINGUAS=""
+
+GETTEXT_PACKAGE=libgpod
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package.])
+
+AM_GLIB_GNU_GETTEXT
+
+
+dnl warnings bits, copied from gnome-keyring configure.in
+dnl Turn on the additional warnings last, so -Werror doesn't affect other tests.
+
+AC_ARG_ENABLE(more-warnings,
+[ --enable-more-warnings Maximum compiler warnings],
+set_more_warnings="$enableval",[
+if test -d "$srcdir/{arch}" || test -d "$srcdir/CVS"; then
+ set_more_warnings=yes
+else
+ set_more_warnings=no
+fi
+])
+AC_MSG_CHECKING(for more warnings, including -Werror)
+if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then
+ AC_MSG_RESULT(yes)
+ CFLAGS="\
+ -Wall \
+ -Wchar-subscripts -Wmissing-declarations -Wmissing-prototypes \
+ -Wnested-externs -Wpointer-arith \
+ -Wcast-align -Wsign-compare \
+ -Werror -std=c89 \
+ $CFLAGS"
+
+ for option in -Wno-strict-aliasing -Wno-sign-compare -Wdeclaration-after-statement; do
+ SAVE_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $option"
+ AC_MSG_CHECKING([whether gcc understands $option])
+ AC_TRY_COMPILE([], [],
+ has_option=yes,
+ has_option=no,)
+ if test $has_option = no; then
+ CFLAGS="$SAVE_CFLAGS"
+ fi
+ AC_MSG_RESULT($has_option)
+ unset has_option
+ unset SAVE_CFLAGS
+ done
+ unset option
+else
+ AC_MSG_RESULT(no)
+fi
+
+AC_SUBST(CFLAGS)
+AC_SUBST(CPPFLAGS)
+AC_SUBST(LDFLAGS)
+
+AC_OUTPUT([
+Makefile
+po/Makefile.in
+src/Makefile
+tests/Makefile
+libgpod-1.0.pc
+])
diff --git a/libgpod-1.0.pc.in b/libgpod-1.0.pc.in
new file mode 100644
index 0000000..450c656
--- /dev/null
+++ b/libgpod-1.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libgpod
+Description: A library to manipulate songs and playlists stored on an ipod
+Version: @VERSION@
+Requires: glib-2.0
+Libs: -L${libdir} -lgpod
+Cflags: -I${includedir}/gpod-1.0
diff --git a/po/ChangeLog b/po/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/po/ChangeLog
diff --git a/po/Makefile.in.in b/po/Makefile.in.in
new file mode 100644
index 0000000..1a6961e
--- /dev/null
+++ b/po/Makefile.in.in
@@ -0,0 +1,263 @@
+# Makefile for program source directory in GNU NLS utilities package.
+# Copyright (C) 1995, 1996, 1997 by Ulrich Drepper <drepper@gnu.ai.mit.edu>
+#
+# This file file be copied and used freely without restrictions. It can
+# be used in projects which are not available under the GNU Public License
+# but which still want to provide support for the GNU gettext functionality.
+# Please note that the actual code is *not* freely available.
+#
+# - Modified by Owen Taylor <otaylor@redhat.com> to use GETTEXT_PACKAGE
+# instead of PACKAGE and to look for po2tbl in ./ not in intl/
+#
+# - Modified by jacob berkman <jacob@ximian.com> to install
+# Makefile.in.in and po2tbl.sed.in for use with glib-gettextize
+#
+# - Modified by Rodney Dawes <dobey@novell.com> for use with intltool
+#
+# We have the following line for use by intltoolize:
+# INTLTOOL_MAKEFILE
+
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+
+SHELL = /bin/sh
+@SET_MAKE@
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+top_builddir = ..
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datadir = @datadir@
+datarootdir = @datarootdir@
+libdir = @libdir@
+localedir = $(libdir)/locale
+gnulocaledir = $(datadir)/locale
+gettextsrcdir = $(datadir)/glib-2.0/gettext/po
+subdir = po
+install_sh = @install_sh@
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+
+CC = @CC@
+GENCAT = @GENCAT@
+GMSGFMT = @GMSGFMT@
+MSGFMT = @MSGFMT@
+XGETTEXT = @XGETTEXT@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+MSGMERGE = INTLTOOL_EXTRACT=$(INTLTOOL_EXTRACT) srcdir=$(srcdir) $(INTLTOOL_UPDATE) --gettext-package $(GETTEXT_PACKAGE) --dist
+GENPOT = INTLTOOL_EXTRACT=$(INTLTOOL_EXTRACT) srcdir=$(srcdir) $(INTLTOOL_UPDATE) --gettext-package $(GETTEXT_PACKAGE) --pot
+
+DEFS = @DEFS@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+
+INCLUDES = -I.. -I$(top_srcdir)/intl
+
+COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) $(XCFLAGS)
+
+SOURCES =
+POFILES = @POFILES@
+GMOFILES = @GMOFILES@
+DISTFILES = ChangeLog Makefile.in.in POTFILES.in \
+$(POFILES) $(SOURCES)
+EXTRA_DISTFILES = POTFILES.skip Makevars LINGUAS
+
+POTFILES = \
+
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+INSTOBJEXT = @INSTOBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .c .o .po .pox .gmo .mo .msg .cat
+
+.c.o:
+ $(COMPILE) $<
+
+.po.pox:
+ $(MAKE) $(GETTEXT_PACKAGE).pot
+ $(MSGMERGE) $< $(GETTEXT_PACKAGE).pot -o $*.pox
+
+.po.mo:
+ $(MSGFMT) -o $@ $<
+
+.po.gmo:
+ file=`echo $* | sed 's,.*/,,'`.gmo \
+ && rm -f $$file && $(GMSGFMT) -o $$file $<
+
+.po.cat:
+ sed -f ../intl/po2msg.sed < $< > $*.msg \
+ && rm -f $@ && $(GENCAT) $@ $*.msg
+
+
+all: all-@USE_NLS@
+
+all-yes: $(CATALOGS)
+all-no:
+
+$(GETTEXT_PACKAGE).pot: $(POTFILES)
+ $(GENPOT)
+
+install: install-exec install-data
+install-exec:
+install-data: install-data-@USE_NLS@
+install-data-no: all
+install-data-yes: all
+ if test -n "$(MKINSTALLDIRS)"; then \
+ $(MKINSTALLDIRS) $(DESTDIR)$(datadir); \
+ else \
+ $(SHELL) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(datadir); \
+ fi
+ @catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ cat=`basename $$cat`; \
+ case "$$cat" in \
+ *.gmo) destdir=$(gnulocaledir);; \
+ *) destdir=$(localedir);; \
+ esac; \
+ lang=`echo $$cat | sed 's/\$(CATOBJEXT)$$//'`; \
+ dir=$(DESTDIR)$$destdir/$$lang/LC_MESSAGES; \
+ if test -n "$(MKINSTALLDIRS)"; then \
+ $(MKINSTALLDIRS) $$dir; \
+ else \
+ $(SHELL) $(top_srcdir)/mkinstalldirs $$dir; \
+ fi; \
+ if test -r $$cat; then \
+ $(INSTALL_DATA) $$cat $$dir/$(GETTEXT_PACKAGE)$(INSTOBJEXT); \
+ echo "installing $$cat as $$dir/$(GETTEXT_PACKAGE)$(INSTOBJEXT)"; \
+ else \
+ $(INSTALL_DATA) $(srcdir)/$$cat $$dir/$(GETTEXT_PACKAGE)$(INSTOBJEXT); \
+ echo "installing $(srcdir)/$$cat as" \
+ "$$dir/$(GETTEXT_PACKAGE)$(INSTOBJEXT)"; \
+ fi; \
+ if test -r $$cat.m; then \
+ $(INSTALL_DATA) $$cat.m $$dir/$(GETTEXT_PACKAGE)$(INSTOBJEXT).m; \
+ echo "installing $$cat.m as $$dir/$(GETTEXT_PACKAGE)$(INSTOBJEXT).m"; \
+ else \
+ if test -r $(srcdir)/$$cat.m ; then \
+ $(INSTALL_DATA) $(srcdir)/$$cat.m \
+ $$dir/$(GETTEXT_PACKAGE)$(INSTOBJEXT).m; \
+ echo "installing $(srcdir)/$$cat as" \
+ "$$dir/$(GETTEXT_PACKAGE)$(INSTOBJEXT).m"; \
+ else \
+ true; \
+ fi; \
+ fi; \
+ done
+ if test "$(PACKAGE)" = "glib"; then \
+ if test -n "$(MKINSTALLDIRS)"; then \
+ $(MKINSTALLDIRS) $(DESTDIR)$(gettextsrcdir); \
+ else \
+ $(SHELL) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(gettextsrcdir); \
+ fi; \
+ $(INSTALL_DATA) $(srcdir)/Makefile.in.in \
+ $(DESTDIR)$(gettextsrcdir)/Makefile.in.in; \
+ else \
+ : ; \
+ fi
+
+# Define this as empty until I found a useful application.
+installcheck:
+
+uninstall:
+ catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ cat=`basename $$cat`; \
+ lang=`echo $$cat | sed 's/\$(CATOBJEXT)$$//'`; \
+ rm -f $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(GETTEXT_PACKAGE)$(INSTOBJEXT); \
+ rm -f $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(GETTEXT_PACKAGE)$(INSTOBJEXT).m; \
+ rm -f $(DESTDIR)$(gnulocaledir)/$$lang/LC_MESSAGES/$(GETTEXT_PACKAGE)$(INSTOBJEXT); \
+ rm -f $(DESTDIR)$(gnulocaledir)/$$lang/LC_MESSAGES/$(GETTEXT_PACKAGE)$(INSTOBJEXT).m; \
+ done
+ if test "$(PACKAGE)" = "glib"; then \
+ rm -f $(DESTDIR)$(gettextsrcdir)/Makefile.in.in; \
+ fi
+
+check: all $(GETTEXT_PACKAGE).pot
+
+dvi info tags TAGS ID:
+
+mostlyclean:
+ rm -f core core.* *.pox $(GETTEXT_PACKAGE).pot *.old.po cat-id-tbl.tmp
+ rm -fr *.o
+ rm -f .intltool-merge-cache
+
+clean: mostlyclean
+
+distclean: clean
+ rm -f Makefile Makefile.in POTFILES
+ rm -f *.mo *.msg *.cat *.cat.m $(GMOFILES)
+
+maintainer-clean: distclean
+ @echo "This command is intended for maintainers to use;"
+ @echo "it deletes files that may require special tools to rebuild."
+ rm -f Makefile.in.in
+
+distdir = ../$(GETTEXT_PACKAGE)-$(VERSION)/$(subdir)
+dist distdir: $(DISTFILES)
+ dists="$(DISTFILES)"; \
+ extra_dists="$(EXTRA_DISTFILES)"; \
+ for file in $$extra_dists; do \
+ test -f $$file && dists="$$dists $$file"; \
+ done; \
+ for file in $$dists; do \
+ ln $(srcdir)/$$file $(distdir) 2> /dev/null \
+ || cp -p $(srcdir)/$$file $(distdir); \
+ done
+
+update-po: Makefile
+ $(MAKE) $(GETTEXT_PACKAGE).pot
+ tmpdir=`pwd`; \
+ catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ cat=`basename $$cat`; \
+ lang=`echo $$cat | sed 's/\$(CATOBJEXT)$$//'`; \
+ echo "$$lang:"; \
+ result="`$(MSGMERGE) -o $$tmpdir/$$lang.new.po $$lang`"; \
+ if $$result; then \
+ if cmp $(srcdir)/$$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
+ rm -f $$tmpdir/$$lang.new.po; \
+ else \
+ if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
+ :; \
+ else \
+ echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
+ rm -f $$tmpdir/$$lang.new.po; \
+ exit 1; \
+ fi; \
+ fi; \
+ else \
+ echo "msgmerge for $$cat failed!"; \
+ rm -f $$tmpdir/$$lang.new.po; \
+ fi; \
+ done
+
+# POTFILES is created from POTFILES.in by stripping comments, empty lines
+# and Intltool tags (enclosed in square brackets), and appending a full
+# relative path to them
+POTFILES: POTFILES.in
+ ( posrcprefix='$(top_srcdir)/'; \
+ rm -f $@-t $@ \
+ && (sed -e '/^#/d' \
+ -e 's/^[[].*] *//' \
+ -e '/^[ ]*$$/d' \
+ -e "s@^@ $$posrcprefix@" $(srcdir)/$@.in \
+ | sed -e '$$!s/$$/ \\/') > $@-t \
+ && chmod a-w $@-t \
+ && mv $@-t $@ )
+
+Makefile: Makefile.in.in ../config.status POTFILES
+ cd .. \
+ && CONFIG_FILES=$(subdir)/$@.in CONFIG_HEADERS= \
+ $(SHELL) ./config.status
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..f1261ee
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,2 @@
+src/itdb_itunesdb.c
+src/itdb_playlist.c
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..e0998fd
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,18 @@
+lib_LTLIBRARIES = libgpod.la
+
+libgpod_la_SOURCES = \
+ itdb.h \
+ itdb_itunesdb.c \
+ itdb_playlist.c \
+ itdb_private.h \
+ itdb_track.c
+libgpod_la_headers = itdb.h
+libgpod_la_noinst_headers = itdb_private.h
+libgpod_la_LDFLAGS = -version-info $(LIBGPOD_CURRENT):$(LIBGPOD_REVISION):$(LIBGPOD_AGE) \
+ -no-undefined
+
+libgpodincludedir = $(includedir)/gpod-1.0/gpod
+libgpodinclude_HEADERS = $(libgpod_la_headers)
+
+INCLUDES=$(LIBGPOD_CFLAGS)
+LIBS=$(LIBGPOD_LIBS)
diff --git a/src/itdb.h b/src/itdb.h
new file mode 100644
index 0000000..9dc67e5
--- /dev/null
+++ b/src/itdb.h
@@ -0,0 +1,504 @@
+/* Time-stamp: <2005-06-17 22:25:30 jcs>
+|
+| Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
+| Part of the gtkpod project.
+|
+| URL: http://www.gtkpod.org/
+| URL: http://gtkpod.sourceforge.net/
+|
+| Most of the code in this file has been ported from the perl
+| script "mktunes.pl" (part of the gnupod-tools collection) written
+| by Adrian Ulrich <pab at blinkenlights.ch>.
+|
+| gnupod-tools: http://www.blinkenlights.ch/cgi-bin/fm.pl?get=ipod
+|
+| The code contained in this file is free software; you can redistribute
+| it and/or modify it under the terms of the GNU Lesser General Public
+| License as published by the Free Software Foundation; either version
+| 2.1 of the License, or (at your option) any later version.
+|
+| This file is distributed in the hope that it will be useful,
+| but WITHOUT ANY WARRANTY; without even the implied warranty of
+| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+| Lesser General Public License for more details.
+|
+| You should have received a copy of the GNU Lesser General Public
+| License along with this code; if not, write to the Free Software
+| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+|
+| iTunes and iPod are trademarks of Apple
+|
+| This product is not supported/written/published by Apple!
+|
+| $Id$
+*/
+
+#ifndef __ITUNESDB_H__
+#define __ITUNESDB_H__
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <time.h>
+#include <glib.h>
+
+
+/* one star is how much (track->rating) */
+#define ITDB_RATING_STEP 20
+
+enum ItdbPlType { /* types for playlist->type */
+ ITDB_PL_TYPE_NORM = 0, /* normal playlist, visible in iPod */
+ ITDB_PL_TYPE_MPL = 1 /* master playlist, contains all tracks,
+ not visible in iPod */
+};
+
+
+
+/* Most of the knowledge about smart playlists has been provided by
+ Samuel "Otto" Wood (sam dot wood at gmail dot com) who let me dig
+ in his impressive C++ class. Contact him for a complete
+ copy. Further, all enums and #defines below, SPLRule, SPLRules, and
+ SPLPref may also be used under a FreeBSD license. */
+
+#define SPL_STRING_MAXLEN 255
+
+/* Definitions for smart playlists */
+enum { /* types for match_operator */
+ SPLMATCH_AND = 0, /* AND rule - all of the rules must be true in
+ order for the combined rule to be applied */
+ SPLMATCH_OR = 1 /* OR rule */
+};
+
+/* Limit Types.. like limit playlist to 100 minutes or to 100 songs */
+enum {
+ LIMITTYPE_MINUTES = 0x01,
+ LIMITTYPE_MB = 0x02,
+ LIMITTYPE_SONGS = 0x03,
+ LIMITTYPE_HOURS = 0x04,
+ LIMITTYPE_GB = 0x05
+};
+
+/* Limit Sorts.. Like which songs to pick when using a limit type
+ Special note: the values for LIMITSORT_LEAST_RECENTLY_ADDED,
+ LIMITSORT_LEAST_OFTEN_PLAYED, LIMITSORT_LEAST_RECENTLY_PLAYED, and
+ LIMITSORT_LOWEST_RATING are really 0x10, 0x14, 0x15, 0x17, with the
+ 'limitsort_opposite' flag set. This is the same value as the
+ "positive" value (i.e. LIMITSORT_LEAST_RECENTLY_ADDED), and is
+ really very terribly awfully weird, so we map the values to iPodDB
+ specific values with the high bit set.
+
+ On writing, we check the high bit and write the limitsort_opposite
+ from that. That way, we don't have to deal with programs using the
+ class needing to set the wrong limit and then make it into the
+ "opposite", which would be frickin' annoying. */
+enum {
+ LIMITSORT_RANDOM = 0x02,
+ LIMITSORT_SONG_NAME = 0x03,
+ LIMITSORT_ALBUM = 0x04,
+ LIMITSORT_ARTIST = 0x05,
+ LIMITSORT_GENRE = 0x07,
+ LIMITSORT_MOST_RECENTLY_ADDED = 0x10,
+ LIMITSORT_LEAST_RECENTLY_ADDED = 0x80000010, /* See note above */
+ LIMITSORT_MOST_OFTEN_PLAYED = 0x14,
+ LIMITSORT_LEAST_OFTEN_PLAYED = 0x80000014, /* See note above */
+ LIMITSORT_MOST_RECENTLY_PLAYED = 0x15,
+ LIMITSORT_LEAST_RECENTLY_PLAYED = 0x80000015,/* See note above */
+ LIMITSORT_HIGHEST_RATING = 0x17,
+ LIMITSORT_LOWEST_RATING = 0x80000017, /* See note above */
+};
+
+/* Smartlist Actions - Used in the rules.
+Note by Otto (Samuel Wood):
+ really this is a bitmapped field...
+ high byte
+ bit 0 = "string" values if set, "int" values if not set
+ bit 1 = "not", or to negate the check.
+ lower 2 bytes
+ bit 0 = simple "IS" query
+ bit 1 = contains
+ bit 2 = begins with
+ bit 3 = ends with
+ bit 4 = greater than
+ bit 5 = unknown, but probably greater than or equal to
+ bit 6 = less than
+ bit 7 = unknown, but probably less than or equal to
+ bit 8 = a range selection
+ bit 9 = "in the last"
+*/
+typedef enum {
+ SPLACTION_IS_INT = 0x00000001, /* "Is Set" in iTunes */
+ SPLACTION_IS_GREATER_THAN = 0x00000010, /* "Is After" in iTunes */
+ SPLACTION_IS_LESS_THAN = 0x00000040, /* "Is Before" in iTunes */
+ SPLACTION_IS_IN_THE_RANGE = 0x00000100,
+ SPLACTION_IS_IN_THE_LAST = 0x00000200,
+
+ SPLACTION_IS_STRING = 0x01000001,
+ SPLACTION_CONTAINS = 0x01000002,
+ SPLACTION_STARTS_WITH = 0x01000004,
+ SPLACTION_ENDS_WITH = 0x01000008,
+
+ SPLACTION_IS_NOT_INT = 0x02000001, /* "Is Not Set" in iTunes */
+
+ /* Note: Not available in iTunes 4.5 (untested on iPod) */
+ SPLACTION_IS_NOT_GREATER_THAN = 0x02000010,
+ /* Note: Not available in iTunes 4.5 (untested on iPod) */
+ SPLACTION_IS_NOT_LESS_THAN = 0x02000040,
+ /* Note: Not available in iTunes 4.5 (seems to work on iPod) */
+ SPLACTION_IS_NOT_IN_THE_RANGE = 0x02000100,
+
+ SPLACTION_IS_NOT_IN_THE_LAST = 0x02000200,
+ SPLACTION_IS_NOT = 0x03000001,
+ SPLACTION_DOES_NOT_CONTAIN = 0x03000002,
+
+ /* Note: Not available in iTunes 4.5 (seems to work on iPod) */
+ SPLACTION_DOES_NOT_START_WITH = 0x03000004,
+ /* Note: Not available in iTunes 4.5 (seems to work on iPod) */
+ SPLACTION_DOES_NOT_END_WITH = 0x03000008,
+} SPLAction;
+
+typedef enum
+{
+ splft_string = 1,
+ splft_int,
+ splft_boolean,
+ splft_date,
+ splft_playlist,
+ splft_unknown
+} SPLFieldType;
+
+typedef enum
+{
+ splat_string = 1,
+ splat_int,
+ splat_date,
+ splat_range_int,
+ splat_range_date,
+ splat_inthelast,
+ splat_playlist,
+ splat_none,
+ splat_invalid,
+ splat_unknown
+} SPLActionType;
+
+
+/* These are to pass to AddRule() when you need a unit for the two "in
+ the last" action types Or, in theory, you can use any time
+ range... iTunes might not like it, but the iPod shouldn't care. */
+enum {
+ SPLACTION_LAST_DAYS_VALUE = 86400, /* nr of secs in 24 hours */
+ SPLACTION_LAST_WEEKS_VALUE = 604800, /* nr of secs in 7 days */
+ SPLACTION_LAST_MONTHS_VALUE = 2628000,/* nr of secs in 30.4167
+ days ~= 1 month */
+} ;
+
+#if 0
+// Hey, why limit ourselves to what iTunes can do? If the iPod can deal with it, excellent!
+#define SPLACTION_LAST_HOURS_VALUE 3600 // number of seconds in 1 hour
+#define SPLACTION_LAST_MINUTES_VALUE 60 // number of seconds in 1 minute
+#define SPLACTION_LAST_YEARS_VALUE 31536000 // number of seconds in 365 days
+
+// fun ones.. Near as I can tell, all of these work. It's open like that. :)
+#define SPLACTION_LAST_LUNARCYCLE_VALUE 2551443 // a "lunar cycle" is the time it takes the moon to circle the earth
+#define SPLACTION_LAST_SIDEREAL_DAY 86164 // a "sidereal day" is time in one revolution of earth on its axis
+#define SPLACTION_LAST_SWATCH_BEAT 86 // a "swatch beat" is 1/1000th of a day.. search for "internet time" on google
+#define SPLACTION_LAST_MOMENT 90 // a "moment" is 1/40th of an hour, or 1.5 minutes
+#define SPLACTION_LAST_OSTENT 600 // an "ostent" is 1/10th of an hour, or 6 minutes
+#define SPLACTION_LAST_FORTNIGHT 1209600 // a "fortnight" is 14 days
+#define SPLACTION_LAST_VINAL 1728000 // a "vinal" is 20 days
+#define SPLACTION_LAST_QUARTER 7889231 // a "quarter" is a quarter year
+#define SPLACTION_LAST_SOLAR_YEAR 31556926 // a "solar year" is the time it takes the earth to go around the sun
+#define SPLACTION_LAST_SIDEREAL_YEAR 31558150 // a "sidereal year" is the time it takes the earth to reach the same point in space again, compared to the stars
+#endif
+
+/* Smartlist fields - Used for rules. */
+typedef enum {
+ SPLFIELD_SONG_NAME = 0x02, /* String */
+ SPLFIELD_ALBUM = 0x03, /* String */
+ SPLFIELD_ARTIST = 0x04, /* String */
+ SPLFIELD_BITRATE = 0x05, /* Int (e.g. from/to = 128) */
+ SPLFIELD_SAMPLE_RATE = 0x06, /* Int (e.g. from/to = 44100) */
+ SPLFIELD_YEAR = 0x07, /* Int (e.g. from/to = 2004) */
+ SPLFIELD_GENRE = 0x08, /* String */
+ SPLFIELD_KIND = 0x09, /* String */
+ SPLFIELD_DATE_MODIFIED = 0x0a,/* Int/Mac Timestamp (e.g. from/to =
+ bcf93280 == is before 6/19/2004)*/
+ SPLFIELD_TRACKNUMBER = 0x0b, /* Int (e.g. from = 1, to = 2) */
+ SPLFIELD_SIZE = 0x0c, /* Int (e.g. from/to = 0x00600000
+ for 6MB) */
+ SPLFIELD_TIME = 0x0d, /* Int (e.g. from/to = 83999 for
+ 1:23/83 seconds) */
+ SPLFIELD_COMMENT = 0x0e, /* String */
+ SPLFIELD_DATE_ADDED = 0x10, /* Int/Mac Timestamp (e.g. from/to =
+ bcfa83ff == is after 6/19/2004) */
+ SPLFIELD_COMPOSER = 0x12, /* String */
+ SPLFIELD_PLAYCOUNT = 0x16, /* Int (e.g. from/to = 1) */
+ SPLFIELD_LAST_PLAYED = 0x17, /* Int/Mac Timestamp (e.g. from =
+ bcfa83ff (6/19/2004) to =
+ 0xbcfbd57f (6/20/2004)) */
+ SPLFIELD_DISC_NUMBER = 0x18, /* Int (e.g. from/to = 1) */
+ SPLFIELD_RATING = 0x19, /* Int/Stars Rating (e.g. from/to =
+ 60 (3 stars)) */
+ SPLFIELD_COMPILATION = 0x1f, /* Int (e.g. is set ->
+ SPLACTION_IS_INT/from=1,
+ is not set ->
+ SPLACTION_IS_NOT_INT/from=1) */
+ SPLFIELD_BPM = 0x23, /* Int (e.g. from/to = 60) */
+ SPLFIELD_GROUPING = 0x27, /* String */
+ SPLFIELD_PLAYLIST = 0x28, /* XXX - Unknown...not parsed
+ correctly...from/to = 0xb6fbad5f
+ for * "Purchased Music". Extra
+ data after * "to"... */
+} SPLField;
+
+#define SPLDATE_IDENTIFIER (G_GINT64_CONSTANT (0x2dae2dae2dae2daeU))
+
+/* Maximum string length that iTunes writes to the database */
+#define SPL_MAXSTRINGLENGTH 255
+
+typedef struct SPLPref
+{
+ guint8 liveupdate; /* "live Updating" check box */
+ guint8 checkrules; /* "Match X of the following
+ conditions" check box */
+ guint8 checklimits; /* "Limit To..." check box */
+ guint32 limittype; /* See types defined above */
+ guint32 limitsort; /* See types defined above */
+ guint32 limitvalue; /* The value typed next to "Limit
+ type" */
+ guint8 matchcheckedonly; /* "Match only checked songs" check
+ box */
+} SPLPref;
+
+typedef struct SPLRule
+{
+ guint32 field;
+ guint32 action;
+ gchar *string; /* data in UTF8 */
+ /* from and to are pretty stupid.. if it's a date type of field,
+ then
+ value = 0x2dae2dae2dae2dae,
+ date = some number, like 2 or -2
+ units = unit in seconds, like 604800 = a week
+ but if this is actually some kind of integer comparison, like
+ rating = 60 (3 stars)
+ value = the value we care about
+ date = 0
+ units = 1 */
+ guint64 fromvalue;
+ gint64 fromdate;
+ guint64 fromunits;
+ guint64 tovalue;
+ gint64 todate;
+ guint64 tounits;
+ guint32 unk052;
+ guint32 unk056;
+ guint32 unk060;
+ guint32 unk064;
+ guint32 unk068;
+} SPLRule;
+
+
+typedef struct SPLRules
+{
+ guint32 unk004;
+ guint32 match_operator; /* "All" (logical AND): SPLMATCH_AND,
+ "Any" (logical OR): SPLMATCH_OR */
+ GList *rules;
+} SPLRules;
+
+
+typedef void (* ItdbUserDataDestroyFunc) (gpointer userdata);
+typedef gpointer (* ItdbUserDataDuplicateFunc) (gpointer userdata);
+
+typedef struct
+{
+ GList *tracks;
+ GList *playlists;
+ gchar *filename; /* filename of iTunesDB */
+ gchar *mountpoint; /* mountpoint of iPod (if available) */
+ guint32 version;
+ guint64 id;
+ /* below is for use by application */
+ guint64 usertype;
+ gpointer userdata;
+ /* function called to duplicate userdata */
+ ItdbUserDataDuplicateFunc userdata_duplicate;
+ /* function called to free userdata */
+ ItdbUserDataDestroyFunc userdata_destroy;
+} Itdb_iTunesDB;
+
+
+typedef struct
+{
+ Itdb_iTunesDB *itdb; /* pointer to iTunesDB (for convenience) */
+ gchar *name; /* name of playlist in UTF8 */
+ guint32 type; /* PL_TYPE_MPL: master play list */
+ gint num; /* number of tracks in playlist */
+ GList *members; /* tracks in playlist (Track *) */
+ gboolean is_spl; /* smart playlist? */
+ guint32 timestamp; /* some timestamp */
+ guint64 id; /* playlist ID */
+ guint32 unk036, unk040, unk044;
+ SPLPref splpref; /* smart playlist prefs */
+ SPLRules splrules; /* rules for smart playlists */
+ /* below is for use by application */
+ guint64 usertype;
+ gpointer userdata;
+ /* function called to duplicate userdata */
+ ItdbUserDataDuplicateFunc userdata_duplicate;
+ /* function called to free userdata */
+ ItdbUserDataDestroyFunc userdata_destroy;
+} Itdb_Playlist;
+
+
+typedef struct
+{
+ Itdb_iTunesDB *itdb; /* pointer to iTunesDB (for convenience) */
+ gchar *album; /* album (utf8) */
+ gchar *artist; /* artist (utf8) */
+ gchar *title; /* title (utf8) */
+ gchar *genre; /* genre (utf8) */
+ gchar *comment; /* comment (utf8) */
+ gchar *composer; /* Composer (utf8) */
+ gchar *fdesc; /* eg. "MP3-File"...(utf8)*/
+ gchar *grouping; /* ? (utf8) */
+ gchar *ipod_path; /* name of file on iPod: uses ":"
+ instead of "/" */
+ guint32 id; /* unique ID of track */
+ gint32 size; /* size of file in bytes */
+ gint32 tracklen; /* Length of track in ms */
+ gint32 cd_nr; /* CD number */
+ gint32 cds; /* number of CDs */
+ gint32 track_nr; /* track number */
+ gint32 tracks; /* number of tracks */
+ gint32 bitrate; /* bitrate */
+ guint16 samplerate; /* samplerate (CD: 44100) */
+ gint32 year; /* year */
+ gint32 volume; /* volume adjustment */
+ guint32 soundcheck; /* volume adjustment "soundcheck" */
+ guint32 time_added; /* time when added (Mac type) */
+ guint32 time_played; /* time of last play (Mac type) */
+ guint32 time_modified; /* time of last modification (Mac type)*/
+ guint32 bookmark_time; /* bookmark set for (AudioBook) in ms */
+ guint32 rating; /* star rating (stars * RATING_STEP (20)) */
+ guint32 playcount; /* number of times track was played */
+ guint32 recent_playcount; /* times track was played since last sync */
+ gboolean transferred; /* has file been transferred to iPod? */
+ gint16 BPM; /* supposed to vary the playback speed */
+ guint8 app_rating; /* star rating set by appl. (not iPod) */
+ guint16 type;
+ guint8 compilation;
+ guint32 starttime;
+ guint32 stoptime;
+ guint8 checked;
+ guint64 dbid; /* unique database ID */
+/* present in the mhit but not used by gtkpod yet */
+ guint32 unk020, unk024, unk084, unk100, unk124;
+ guint32 unk128, unk132, unk136, unk140, unk144, unk148, unk152;
+ /* below is for use by application */
+ guint64 usertype;
+ gpointer userdata;
+ /* function called to duplicate userdata */
+ ItdbUserDataDuplicateFunc userdata_duplicate;
+ /* function called to free userdata */
+ ItdbUserDataDestroyFunc userdata_destroy;
+} Itdb_Track;
+/* (gtkpod note: don't forget to add fields read from the file to
+ * copy_new_info() in file.c!) */
+
+/* Error codes */
+typedef enum
+{
+ ITDB_FILE_ERROR_SEEK, /* file corrupt: illegal seek occured */
+ ITDB_FILE_ERROR_CORRUPT, /* file corrupt */
+ ITDB_FILE_ERROR_NOTFOUND, /* file not found */
+ ITDB_FILE_ERROR_RENAME, /* file could not be renamed */
+ ITDB_FILE_ERROR_ITDB_CORRUPT /* iTunesDB in memory corrupt */
+} ItdbFileError;
+
+/* Error domain */
+#define ITDB_FILE_ERROR itdb_file_error_quark ()
+GQuark itdb_file_error_quark (void);
+
+/* functions for reading/writing database, general itdb functions */
+Itdb_iTunesDB *itdb_parse (const gchar *mp, GError **error);
+Itdb_iTunesDB *itdb_parse_file (const gchar *filename, GError **error);
+gboolean itdb_write (Itdb_iTunesDB *itdb, const gchar *mp, GError **error);
+gboolean itdb_write_file (Itdb_iTunesDB *itdb, const gchar *filename,
+ GError **error);
+gboolean itdb_shuffle_write (Itdb_iTunesDB *itdb,
+ const gchar *mp, GError **error);
+gboolean itdb_shuffle_write_file (Itdb_iTunesDB *itdb,
+ const gchar *filename, GError **error);
+Itdb_iTunesDB *itdb_new (void);
+void itdb_free (Itdb_iTunesDB *itdb);
+Itdb_iTunesDB *itdb_duplicate (Itdb_iTunesDB *itdb);
+guint32 itdb_tracks_number (Itdb_iTunesDB *itdb);
+guint32 itdb_tracks_number_nontransferred (Itdb_iTunesDB *itdb);
+guint32 itdb_playlists_number (Itdb_iTunesDB *itdb);
+
+/* general file functions */
+gchar * itdb_resolve_path (const gchar *root,
+ const gchar * const * components);
+gboolean itdb_rename_files (const gchar *mp, GError **error);
+gboolean itdb_cp_track_to_ipod (const gchar *mp, Itdb_Track *track,
+ gchar *filename, GError **error);
+gboolean itdb_cp (const gchar *from_file, const gchar *to_file,
+ GError **error);
+void itdb_filename_fs2ipod (gchar *filename);
+void itdb_filename_ipod2fs (gchar *ipod_file);
+gchar *itdb_filename_on_ipod (const gchar *mp, Itdb_Track *track);
+
+/* track functions */
+Itdb_Track *itdb_track_new (void);
+void itdb_track_free (Itdb_Track *track);
+void itdb_track_add (Itdb_iTunesDB *itdb, Itdb_Track *track, gint32 pos);
+void itdb_track_remove (Itdb_Track *track);
+void itdb_track_unlink (Itdb_Track *track);
+Itdb_Track *itdb_track_duplicate (Itdb_Track *tr);
+Itdb_Track *itdb_track_by_id (Itdb_iTunesDB *itdb, guint32 id);
+GTree *itdb_track_id_tree_create (Itdb_iTunesDB *itdb);
+void itdb_track_id_tree_destroy (GTree *idtree);
+Itdb_Track *itdb_track_id_tree_by_id (GTree *idtree, guint32 id);
+
+/* playlist functions */
+Itdb_Playlist *itdb_playlist_new (const gchar *title, gboolean spl);
+void itdb_playlist_free (Itdb_Playlist *pl);
+void itdb_playlist_add (Itdb_iTunesDB *itdb, Itdb_Playlist *pl, gint32 pos);
+void itdb_playlist_move (Itdb_Playlist *pl, guint32 pos);
+void itdb_playlist_remove (Itdb_Playlist *pl);
+void itdb_playlist_unlink (Itdb_Playlist *pl);
+Itdb_Playlist *itdb_playlist_duplicate (Itdb_Playlist *pl);
+gboolean itdb_playlist_exists (Itdb_iTunesDB *itdb, Itdb_Playlist *pl);
+void itdb_playlist_add_track (Itdb_Playlist *pl,
+ Itdb_Track *track, gint32 pos);
+Itdb_Playlist *itdb_playlist_by_id (Itdb_iTunesDB *itdb, guint64 id);
+Itdb_Playlist *itdb_playlist_by_nr (Itdb_iTunesDB *itdb, guint32 num);
+Itdb_Playlist *itdb_playlist_by_name (Itdb_iTunesDB *itdb, gchar *name);
+Itdb_Playlist *itdb_playlist_mpl (Itdb_iTunesDB *itdb);
+gboolean itdb_playlist_contains_track (Itdb_Playlist *pl, Itdb_Track *track);
+guint32 itdb_playlist_contain_track_number (Itdb_Track *tr);
+void itdb_playlist_remove_track (Itdb_Playlist *pl, Itdb_Track *track);
+guint32 itdb_playlist_tracks_number (Itdb_Playlist *pl);
+void itdb_playlist_randomize (Itdb_Playlist *pl);
+
+/* smart playlist functions */
+SPLFieldType itdb_splr_get_field_type (const SPLRule *splr);
+SPLActionType itdb_splr_get_action_type (const SPLRule *splr);
+void itdb_splr_validate (SPLRule *splr);
+void itdb_splr_remove (Itdb_Playlist *pl, SPLRule *splr);
+SPLRule *itdb_splr_new (void);
+void itdb_splr_add (Itdb_Playlist *pl, SPLRule *splr, gint pos);
+SPLRule *itdb_splr_add_new (Itdb_Playlist *pl, gint pos);
+void itdb_spl_copy_rules (Itdb_Playlist *dest, Itdb_Playlist *src);
+gboolean itdb_splr_eval (Itdb_iTunesDB *itdb, SPLRule *splr, Itdb_Track *track);
+void itdb_spl_update (Itdb_iTunesDB *itdb, Itdb_Playlist *spl);
+void itdb_spl_update_all (Itdb_iTunesDB *itdb);
+
+/* time functions */
+guint64 itdb_time_get_mac_time (void);
+time_t itdb_time_mac_to_host (guint64 mactime);
+guint64 itdb_time_host_to_mac (time_t time);
+
+#endif
diff --git a/src/itdb_itunesdb.c b/src/itdb_itunesdb.c
new file mode 100644
index 0000000..f039dee
--- /dev/null
+++ b/src/itdb_itunesdb.c
@@ -0,0 +1,3622 @@
+/* Time-stamp: <2005-08-29 23:23:59 jcs>
+|
+| Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
+| Part of the gtkpod project.
+|
+| URL: http://www.gtkpod.org/
+| URL: http://gtkpod.sourceforge.net/
+|
+| Much of the code in this file has originally been ported from the
+| perl script "mktunes.pl" (part of the gnupod-tools collection)
+| written by Adrian Ulrich <pab at blinkenlights.ch>.
+|
+| gnupod-tools: http://www.blinkenlights.ch/cgi-bin/fm.pl?get=ipod
+|
+| The code contained in this file is free software; you can redistribute
+| it and/or modify it under the terms of the GNU Lesser General Public
+| License as published by the Free Software Foundation; either version
+| 2.1 of the License, or (at your option) any later version.
+|
+| This file is distributed in the hope that it will be useful,
+| but WITHOUT ANY WARRANTY; without even the implied warranty of
+| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+| Lesser General Public License for more details.
+|
+| You should have received a copy of the GNU Lesser General Public
+| License along with this code; if not, write to the Free Software
+| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+|
+| iTunes and iPod are trademarks of Apple
+|
+| This product is not supported/written/published by Apple!
+|
+| $Id$
+*/
+
+/* Some notes on how to use the functions in this file:
+
+
+ *** Reading the iTunesDB ***
+
+ Itdb_iTunesDB *itunesdb_parse (gchar *path); /+ path to mountpoint +/
+ will read an Itdb_iTunesDB and pass the data over to your program.
+
+ The information given in the "Play Counts" file is also read if
+ available and the playcounts, star rating and the time last played
+ is updated.
+
+ Itdb_iTunesDB is a structure containing a GList for the tracks and a
+ GList for the playlists.
+
+ For each track these fields are set as follows:
+
+ "transferred" will be set to TRUE because all tracks read from a
+ Itdb_iTunesDB are obviously (or hopefully) already transferred to the
+ iPod.
+
+ "recent_playcount" is for information only (it will allow to
+ generate playlists with tracks played since the last time) and will
+ not be stored to the iPod.
+
+ The master playlist is guaranteed to be the first playlist, and
+ this must not be changed by your code.
+
+
+ *** Writing the Itdb_iTunesDB ***
+
+ gboolean itunesdb_write (gchar *path, Itdb_iTunesDB *itb)
+ /+ @path to mountpoint, itb to @write +/
+ will write an updated version of the Itdb_iTunesDB.
+
+ The "Play Counts" file is renamed to "Play Counts.bak" if it exists
+ to avoid reading it multiple times.
+
+ Please note that non-transferred tracks are not automatically
+ transferred to the iPod. A function
+
+ gboolean itunesdb_copy_track_to_ipod (gchar *path, Itdb_Track *track, gchar *pcfile)
+
+ is provided to help you do that, however.
+
+ The following functions most likely will be useful:
+
+ Itdb_Track *itunesdb_new_track (void);
+ Use itunesdb_new_track() to get an "initialized" track structure
+ (the "unknowns" are initialized with reasonable values).
+
+ gboolean itunesdb_cp (gchar *from_file, gchar *to_file);
+ void itunesdb_convert_filename_fs2ipod(gchar *ipod_file);
+ void itunesdb_convert_filename_ipod2fs(gchar *ipod_file);
+
+ guint32 itunesdb_time_get_mac_time (void);
+ time_t itunesdb_time_mac_to_host (guint32 mactime);
+ guint32 itunesdb_time_host_to_mac (time_t time);
+
+ void itunesdb_rename_files (const gchar *dirname);
+
+ (Renames/removes some files on the iPod (Playcounts, OTG
+ semaphore). Needs to be called if you write the Itdb_iTunesDB not
+ directly to the iPod but to some other location and then manually
+ copy the file from there to the iPod. That's much faster in the
+ case of using an iPod mounted in sync'ed mode.)
+
+ Jorg Schuler, 29.12.2004 */
+
+
+/* call itdb_parse () to read the Itdb_iTunesDB */
+/* call itdb_write () to write the Itdb_iTunesDB */
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <time.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include "itdb_private.h"
+#include <glib/gi18n-lib.h>
+
+#define ITUNESDB_DEBUG 0
+#define ITUNESDB_MHIT_DEBUG 0
+
+#define ITUNESDB_COPYBLK 262144 /* blocksize for cp () */
+
+
+enum MHOD_ID {
+ MHOD_ID_TITLE = 1,
+ MHOD_ID_PATH = 2,
+ MHOD_ID_ALBUM = 3,
+ MHOD_ID_ARTIST = 4,
+ MHOD_ID_GENRE = 5,
+ MHOD_ID_FDESC = 6,
+ MHOD_ID_COMMENT = 8,
+ MHOD_ID_COMPOSER = 12,
+ MHOD_ID_GROUPING = 13,
+ MHOD_ID_SPLPREF = 50, /* settings for smart playlist */
+ MHOD_ID_SPLRULES = 51, /* rules for smart playlist */
+ MHOD_ID_MHYP = 52, /* unknown */
+ MHOD_ID_PLAYLIST = 100
+};
+
+
+/* ID for error domain */
+GQuark itdb_file_error_quark (void)
+{
+ static GQuark q = 0;
+ if (q == 0)
+ q = g_quark_from_static_string ("itdb-file-error-quark");
+ return q;
+}
+
+/* Get length of utf16 string in number of characters (words) */
+static guint32 utf16_strlen (gunichar2 *utf16)
+{
+ guint32 i=0;
+ if (utf16)
+ while (utf16[i] != 0) ++i;
+ return i;
+}
+
+
+/* Read the contents of @filename and return a FContents
+ struct. Returns NULL in case of error and @error is set
+ accordingly */
+static FContents *fcontents_read (const gchar *fname, GError **error)
+{
+ FContents *cts;
+
+ g_return_val_if_fail (fname, NULL);
+
+ cts = g_new0 (FContents, 1);
+
+ if (g_file_get_contents (fname, &cts->contents, &cts->length, error))
+ {
+ cts->filename = g_strdup (fname);
+ }
+ else
+ {
+ g_free (cts);
+ cts = NULL;
+ }
+ return cts;
+}
+
+
+/* Frees the memory taken by a FContents structure. NULL pointer will
+ * be ignored */
+static void fcontents_free (FContents *cts)
+{
+ if (cts)
+ {
+ g_free (cts->filename);
+ g_free (cts->contents);
+ /* must not g_error_free (cts->error) because the error was
+ propagated -> might free the error twice */
+ g_free (cts);
+ }
+}
+
+
+/* There seems to be a problem with some distributions (kernel
+ versions or whatever -- even identical version numbers don't don't
+ show identical behaviour...): even though vfat is supposed to be
+ case insensitive, a difference is made between upper and lower case
+ under some special circumstances. As in "/iPod_Control/Music/F00"
+ and "/iPod_Control/Music/f00 "... If the former filename does not
+ exist, we try to find an existing case insensitive match for each
+ component of the filename. If we can find such a match, we return
+ it. Otherwise, we return NULL.*/
+
+/* We start by assuming that the ipod mount point exists. Then, for
+ * each component c of track->ipod_path, we try to find an entry d in
+ * good_path that is case-insensitively equal to c. If we find d, we
+ * append d to good_path and make the result the new good_path.
+ * Otherwise, we quit and return NULL. @root: in local encoding,
+ * @components: in utf8 */
+gchar * itdb_resolve_path (const gchar *root,
+ const gchar * const * components)
+{
+ gchar *good_path = g_strdup(root);
+ guint32 i;
+
+ if (!root) return NULL;
+
+ for(i = 0 ; components[i] ; i++) {
+ GDir *cur_dir;
+ gchar *component_as_filename;
+ gchar *test_path;
+ gchar *component_stdcase;
+ const gchar *dir_file=NULL;
+
+ /* skip empty components */
+ if (strlen (components[i]) == 0) continue;
+ component_as_filename =
+ g_filename_from_utf8(components[i],-1,NULL,NULL,NULL);
+ test_path = g_build_filename(good_path,component_as_filename,NULL);
+ g_free(component_as_filename);
+ if(g_file_test(test_path,G_FILE_TEST_EXISTS)) {
+ /* This component does not require fixup */
+ g_free(good_path);
+ good_path = test_path;
+ continue;
+ }
+ g_free(test_path);
+ component_stdcase = g_utf8_casefold(components[i],-1);
+ /* Case insensitively compare the current component with each entry
+ * in the current directory. */
+
+ cur_dir = g_dir_open(good_path,0,NULL);
+ if (cur_dir) while ((dir_file = g_dir_read_name(cur_dir)))
+ {
+ gchar *file_utf8 = g_filename_to_utf8(dir_file,-1,NULL,NULL,NULL);
+ gchar *file_stdcase = g_utf8_casefold(file_utf8,-1);
+ gboolean found = !g_utf8_collate(file_stdcase,component_stdcase);
+ gchar *new_good_path;
+ g_free(file_stdcase);
+ if(!found)
+ {
+ /* This is not the matching entry */
+ g_free(file_utf8);
+ continue;
+ }
+
+ new_good_path = dir_file ? g_build_filename(good_path,dir_file,NULL) : NULL;
+ g_free(good_path);
+ good_path= new_good_path;
+ /* This is the matching entry, so we can stop searching */
+ break;
+ }
+
+ if(!dir_file) {
+ /* We never found a matching entry */
+ g_free(good_path);
+ good_path = NULL;
+ }
+
+ g_free(component_stdcase);
+ if (cur_dir) g_dir_close(cur_dir);
+ if(!good_path || !g_file_test(good_path,G_FILE_TEST_EXISTS))
+ break; /* We couldn't fix this component, so don't try later ones */
+ }
+
+ if(good_path && g_file_test(good_path,G_FILE_TEST_EXISTS))
+ return good_path;
+
+ return NULL;
+}
+
+
+/* Check if the @seek with length @len is legal or out of
+ * range. Returns TRUE if legal and FALSE when it is out of range, in
+ * which case cts->error is set as well. */
+static gboolean check_seek (FContents *cts, glong seek, glong len)
+{
+ g_return_val_if_fail (cts, FALSE);
+ g_return_val_if_fail (cts->contents, FALSE);
+
+ if ((seek+len <= cts->length) && (seek >=0))
+ {
+ return TRUE;
+ }
+ else
+ {
+ g_return_val_if_fail (cts->filename, FALSE);
+ g_set_error (&cts->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_SEEK,
+ _("Illegal seek to offset %ld (length %ld) in file '%s'."),
+ seek, len, cts->filename);
+ return FALSE;
+ }
+}
+
+
+/* Copies @len bytes from position @seek in @cts->contents to
+ @data. Returns FALSE on error and sets cts->error accordingly. */
+static gboolean seek_get_n_bytes (FContents *cts, gchar *data,
+ glong seek, glong len)
+{
+ if (check_seek (cts, seek, len))
+ {
+ memcpy (data, &cts->contents[seek], len);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Compare @n bytes of @cts->contents starting at @seek and
+ * @data. Returns TRUE if equal, FALSE if not. Also returns FALSE on
+ * error, so you must check cts->error */
+static gboolean cmp_n_bytes_seek (FContents *cts, gchar *data,
+ glong seek, glong len)
+{
+ if (check_seek (cts, seek, len))
+ {
+ gint i;
+ for (i=0; i<len; ++i)
+ {
+ if (cts->contents[seek+i] != data[i]) return FALSE;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/* Returns the 1-byte number stored at position @seek. On error the
+ * GError in @cts is set. */
+static guint8 get8int (FContents *cts, glong seek)
+{
+ guint8 n=0;
+
+ if (check_seek (cts, seek, 1))
+ {
+ n = cts->contents[seek];
+ }
+ return n;
+}
+
+
+/* Get the 4-byte-number stored at position "seek" in little endian
+ encoding. On error the GError in @cts is set. */
+static guint32 get32lint (FContents *cts, glong seek)
+{
+ guint32 n=0;
+
+ if (check_seek (cts, seek, 4))
+ {
+ g_return_val_if_fail (cts->contents, 0);
+ memcpy (&n, &cts->contents[seek], 4);
+# if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ n = GUINT32_SWAP_LE_BE (n);
+# endif
+ }
+ return n;
+}
+
+
+/* Get the 4-byte-number stored at position "seek" in big endian
+ encoding. On error the GError in @cts is set. */
+static guint32 get32bint (FContents *cts, glong seek)
+{
+ guint32 n=0;
+
+ if (check_seek (cts, seek, 4))
+ {
+ g_return_val_if_fail (cts->contents, 0);
+ memcpy (&n, &cts->contents[seek], 4);
+# if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ n = GUINT32_SWAP_LE_BE (n);
+# endif
+ }
+ return n;
+}
+
+/* Get the 8-byte-number stored at position "seek" in little endian
+ encoding. On error the GError in @cts is set. */
+static guint64 get64lint (FContents *cts, glong seek)
+{
+ guint64 n=0;
+
+ if (check_seek (cts, seek, 8))
+ {
+ g_return_val_if_fail (cts->contents, 0);
+ memcpy (&n, &cts->contents[seek], 8);
+# if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ n = GUINT64_SWAP_LE_BE (n);
+# endif
+ }
+ return n;
+}
+
+
+/* Get the 8-byte-number stored at position "seek" in big endian
+ encoding. On error the GError in @cts is set. */
+static guint64 get64bint (FContents *cts, glong seek)
+{
+ guint64 n=0;
+
+ if (check_seek (cts, seek, 8))
+ {
+ g_return_val_if_fail (cts->contents, 0);
+ memcpy (&n, &cts->contents[seek], 8);
+# if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ n = GUINT64_SWAP_LE_BE (n);
+# endif
+ }
+ return n;
+}
+
+/* Fix little endian UTF16 String to correct byteorder if necessary
+ * (all strings in the Itdb_iTunesDB are little endian except for the ones
+ * in smart playlists). */
+static gunichar2 *fixup_little_utf16 (gunichar2 *utf16_string)
+{
+# if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ gint32 i;
+ if (utf16_string)
+ {
+ for(i=0; i<utf16_strlen(utf16_string); i++)
+ {
+ utf16_string[i] = GUINT16_SWAP_LE_BE (utf16_string[i]);
+ }
+ }
+# endif
+ return utf16_string;
+}
+
+/* Fix big endian UTF16 String to correct byteorder if necessary (only
+ * strings in smart playlists are big endian) */
+static gunichar2 *fixup_big_utf16 (gunichar2 *utf16_string)
+{
+# if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ gint32 i;
+ if (utf16_string)
+ {
+ for(i=0; i<utf16_strlen(utf16_string); i++)
+ {
+ utf16_string[i] = GUINT16_SWAP_LE_BE (utf16_string[i]);
+ }
+ }
+# endif
+ return utf16_string;
+}
+
+
+#define CHECK_ERROR(imp, val) if (cts->error) { g_propagate_error (&imp->error, cts->error); return (val); }
+
+
+/* get next playcount, that is the first entry of GList
+ * playcounts. This entry is removed from the list. You must free the
+ * return value after use */
+static struct playcount *playcount_get_next (FImport *fimp)
+{
+ struct playcount *playcount;
+ g_return_val_if_fail (fimp, NULL);
+
+ playcount = g_list_nth_data (fimp->playcounts, 0);
+
+ if (playcount)
+ fimp->playcounts = g_list_remove (fimp->playcounts, playcount);
+ return playcount;
+}
+
+/* delete all entries of GList *playcounts */
+static void playcounts_free (FImport *fimp)
+{
+ struct playcount *playcount;
+
+ g_return_if_fail (fimp);
+
+ while ((playcount=playcount_get_next (fimp))) g_free (playcount);
+}
+
+
+/* called by init_playcounts */
+static gboolean playcounts_read (FImport *fimp, FContents *cts)
+{
+ guint32 header_length, entry_length, entry_num, i=0;
+
+ g_return_val_if_fail (fimp, FALSE);
+ g_return_val_if_fail (cts, FALSE);
+
+ if (!cmp_n_bytes_seek (cts, "mhdp", 0, 4))
+ {
+ if (cts->error)
+ {
+ g_propagate_error (&fimp->error, cts->error);
+ }
+ else
+ { /* set error */
+ g_return_val_if_fail (cts->filename, FALSE);
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("Not a Play Counts file: '%s' (missing mhdp header)."),
+ cts->filename);
+ }
+ return FALSE;
+ }
+ header_length = get32lint (cts, 4);
+ CHECK_ERROR (fimp, FALSE);
+ /* all the headers I know are 0x60 long -- if this one is longer
+ we can simply ignore the additional information */
+ if (header_length < 0x60)
+ {
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("Play Counts file ('%s'): header length smaller than expected (%d<96)."),
+ cts->filename, header_length);
+ return FALSE;
+ }
+ entry_length = get32lint (cts, 8);
+ CHECK_ERROR (fimp, FALSE);
+ /* all the entries I know are 0x0c (firmware 1.3) or 0x10
+ * (firmware 2.0) or 0x14 (iTunesDB version 0x0d) in length */
+ if (entry_length < 0x0c)
+ {
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("Play Counts file ('%s'): entry length smaller than expected (%d<12)."),
+ cts->filename, entry_length);
+ return FALSE;
+ }
+ /* number of entries */
+ entry_num = get32lint (cts, 12);
+ CHECK_ERROR (fimp, FALSE);
+ for (i=0; i<entry_num; ++i)
+ {
+ struct playcount *playcount = g_new0 (struct playcount, 1);
+ glong seek = header_length + i*entry_length;
+
+ fimp->playcounts = g_list_append (fimp->playcounts, playcount);
+ playcount->playcount = get32lint (cts, seek);
+ CHECK_ERROR (fimp, FALSE);
+ playcount->time_played = get32lint (cts, seek+4);
+ CHECK_ERROR (fimp, FALSE);
+ playcount->bookmark_time = get32lint (cts, seek+8);
+ CHECK_ERROR (fimp, FALSE);
+ /* NOTE:
+ *
+ * The iPod (firmware 1.3, 2.0, ...?) doesn't seem to use the
+ * timezone information correctly -- no matter what you set
+ * iPod's timezone to, it will always record as if it were set
+ * to UTC -- we need to subtract the difference between
+ * current timezone and UTC to get a correct
+ * display. -- this should be done by the application were
+ * necessary */
+
+ /* rating only exists if the entry length is at least 0x10 */
+ if (entry_length >= 0x10)
+ {
+ playcount->rating = get32lint (cts, seek+12);
+ CHECK_ERROR (fimp, FALSE);
+ }
+ else
+ {
+ playcount->rating = NO_PLAYCOUNT;
+ }
+ /* unk16 only exists if the entry length is at least 0x14 */
+ if (entry_length >= 0x14)
+ {
+ playcount->unk16 = get32lint (cts, seek+16);
+ CHECK_ERROR (fimp, FALSE);
+ }
+ else
+ {
+ playcount->unk16 = 0;
+ }
+ }
+ return TRUE;
+}
+
+
+
+/* Read the Play Count file (formed by adding "Play Counts" to the
+ * directory component of fimp->itdb->itdb_filename) and set up the
+ * GList *playcounts.
+ * Returns TRUE on success (also when no Play Count
+ * file is found as this is not an error) and FALSE otherwise, in
+ * which case fimp->error is set accordingly. */
+static gboolean playcounts_init (FImport *fimp)
+{
+ const gchar *db[] = {"Play Counts", NULL};
+ gchar *plcname, *dirname;
+ gboolean result=FALSE;
+ struct stat filestat;
+ FContents *cts;
+
+ g_return_val_if_fail (fimp, FALSE);
+ g_return_val_if_fail (!fimp->error, FALSE);
+ g_return_val_if_fail (!fimp->playcounts, FALSE);
+ g_return_val_if_fail (fimp->itdb, FALSE);
+ g_return_val_if_fail (fimp->itdb->filename, FALSE);
+
+ dirname = g_path_get_dirname (fimp->itdb->filename);
+
+ plcname = itdb_resolve_path (dirname, db);
+
+ g_free (dirname);
+
+ /* skip if no playcounts file is present */
+ if (!plcname) return TRUE;
+
+ /* skip if playcounts file has zero-length (often happens after
+ * dosfsck) */
+ stat (plcname, &filestat);
+ if (filestat.st_size < 0x60) return TRUE; /* check for header length */
+
+ cts = fcontents_read (plcname, &fimp->error);
+ if (cts)
+ {
+ result = playcounts_read (fimp, cts);
+ fcontents_free (cts);
+ }
+ g_free (plcname);
+ return result;
+}
+
+
+/* Free the memory taken by @fimp. fimp->itdb must be freed separately
+ * before calling this function */
+static void itdb_free_fimp (FImport *fimp)
+{
+ if (fimp)
+ {
+ if (fimp->itunesdb) fcontents_free (fimp->itunesdb);
+ g_list_free (fimp->pos_glist);
+ playcounts_free (fimp);
+ g_free (fimp);
+ }
+}
+
+/* Free the memory taken by @itdb. */
+void itdb_free (Itdb_iTunesDB *itdb)
+{
+ if (itdb)
+ {
+ g_list_foreach (itdb->playlists,
+ (GFunc)(itdb_playlist_free), NULL);
+ g_list_free (itdb->playlists);
+ g_list_foreach (itdb->tracks,
+ (GFunc)(itdb_track_free), NULL);
+ g_list_free (itdb->tracks);
+ g_free (itdb->filename);
+ g_free (itdb->mountpoint);
+ if (itdb->userdata && itdb->userdata_destroy)
+ (*itdb->userdata_destroy) (itdb->userdata);
+ g_free (itdb);
+ }
+}
+
+/* Free the memory taken by @itdb. */
+Itdb_iTunesDB *itdb_duplicate (Itdb_iTunesDB *itdb)
+{
+ g_return_val_if_fail (itdb, NULL);
+ g_return_val_if_fail (!itdb->userdata ||
+ itdb->userdata_duplicate, NULL);
+ /* FIXME: not yet implemented */
+ g_return_val_if_reached (NULL);
+}
+
+/* return number of playlists */
+guint32 itdb_playlists_number (Itdb_iTunesDB *itdb)
+{
+ g_return_val_if_fail (itdb, 0);
+
+ return g_list_length (itdb->playlists);
+}
+
+
+/* return total number of tracks */
+guint32 itdb_tracks_number (Itdb_iTunesDB *itdb)
+{
+ g_return_val_if_fail (itdb, 0);
+
+ return g_list_length (itdb->tracks);
+}
+
+
+guint32 itdb_tracks_number_nontransferred (Itdb_iTunesDB *itdb)
+{
+ guint n = 0;
+ GList *gl;
+ g_return_val_if_fail (itdb, 0);
+
+ for (gl=itdb->tracks; gl; gl=gl->next)
+ {
+ Itdb_Track *track = gl->data;
+ g_return_val_if_fail (track, 0);
+ if (!track->transferred) ++n;
+ }
+ return n;
+}
+
+
+
+/* Creates a new Itdb_iTunesDB with the unknowns filled in to reasonable
+ values */
+Itdb_iTunesDB *itdb_new (void)
+{
+ GRand *grand = g_rand_new ();
+
+ Itdb_iTunesDB *itdb = g_new0 (Itdb_iTunesDB, 1);
+ itdb->version = 0x09;
+ itdb->id = ((guint64)g_rand_int (grand) << 32) |
+ ((guint64)g_rand_int (grand));
+ return itdb;
+}
+
+/* Returns the type of the mhod and the length *ml. *ml is set to -1
+ * on error (e.g. because there's no mhod at @seek). */
+/* A return value of -1 and no error set means that no mhod was found
+ at @seek */
+static gint32 get_mhod_type (FContents *cts, glong seek, gint32 *ml)
+{
+ gint32 type = -1;
+
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "get_mhod_type seek: %x\n", (int)seek);
+#endif
+
+ if (ml) *ml = -1;
+
+ if (cmp_n_bytes_seek (cts, "mhod", seek, 4))
+ {
+ guint32 len = get32lint (cts, seek+8); /* total length */
+ if (cts->error) return -1;
+ if (ml) *ml = len;
+ type = get32lint (cts, seek+12); /* mhod_id */
+ if (cts->error) return -1;
+ }
+ return type;
+}
+
+/* Returns a pointer to the data contained in the mhod at position
+ @seek. This can be a simple string or something more complicated as
+ in the case for SPLPREF or SPLRULES. *ml is set to the total length
+ of the mhod (-1 in case of an error), *mty is set to the type of
+ the mhod.
+ On error NULL is returned and cts->error is set appropriately. */
+static void *get_mhod (FContents *cts, gulong mhod_seek,
+ gint32 *ml, gint32 *mty)
+{
+ gunichar2 *entry_utf16 = NULL;
+ SPLPref *splp = NULL;
+ guint8 limitsort_opposite;
+ void *result = NULL;
+ gint32 xl, len;
+ gint32 header_length;
+ gulong seek;
+
+ g_return_val_if_fail (ml, NULL);
+ g_return_val_if_fail (mty, NULL);
+ g_return_val_if_fail (cts, NULL);
+
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "get_mhod seek: %ld\n", mhod_seek);
+#endif
+
+ g_return_val_if_fail (cts, NULL);
+
+ *ml = -1;
+
+ g_return_val_if_fail (!cts->error, NULL);
+
+ *mty = get_mhod_type (cts, mhod_seek, &len);
+ if (*mty == -1)
+ {
+ if (!cts->error)
+ { /* set error */
+ g_set_error (&cts->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("iTunesDB corrupt: no MHOD at offset %ld in file '%s'."),
+ mhod_seek, cts->filename);
+ }
+ return NULL;
+ }
+ header_length = get32lint (cts, mhod_seek+4); /* header length */
+ if (cts->error) return NULL;
+
+ seek = mhod_seek + header_length;
+
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "ml: %x mty: %x\n", *ml, *mty);
+#endif
+
+ switch ((enum MHOD_ID)*mty)
+ {
+ case MHOD_ID_MHYP:
+ /* this is not yet supported */
+ case MHOD_ID_PLAYLIST:
+ /* return the position indicator */
+ result = (void *)get32lint (cts, mhod_seek+24);
+ if (cts->error) return NULL;
+ break;
+ case MHOD_ID_TITLE:
+ case MHOD_ID_PATH:
+ case MHOD_ID_ALBUM:
+ case MHOD_ID_ARTIST:
+ case MHOD_ID_GENRE:
+ case MHOD_ID_FDESC:
+ case MHOD_ID_COMMENT:
+ case MHOD_ID_COMPOSER:
+ case MHOD_ID_GROUPING:
+ xl = get32lint (cts, seek+4); /* entry length */
+ if (cts->error) return NULL;
+ entry_utf16 = g_new0 (gunichar2, (xl+2)/2);
+ if (seek_get_n_bytes (cts, (gchar *)entry_utf16, seek+16, xl))
+ {
+ result = fixup_little_utf16 (entry_utf16);
+ }
+ else
+ { /* error */
+ g_free (entry_utf16);
+ return NULL;
+ }
+ break;
+ case MHOD_ID_SPLPREF: /* Settings for smart playlist */
+ splp = g_new0 (SPLPref, 1);
+ splp->liveupdate = get8int (cts, seek);
+ if (cts->error) return NULL;
+ splp->checkrules = get8int (cts, seek+1);
+ if (cts->error) return NULL;
+ splp->checklimits = get8int (cts, seek+2);
+ if (cts->error) return NULL;
+ splp->limittype = get8int (cts, seek+3);
+ if (cts->error) return NULL;
+ splp->limitsort = get8int (cts, seek+4);
+ if (cts->error) return NULL;
+ splp->limitvalue = get32lint (cts, seek+8);
+ if (cts->error) return NULL;
+ splp->matchcheckedonly = get8int (cts, seek+12);
+ if (cts->error) return NULL;
+ limitsort_opposite = get8int (cts, seek+13);
+ if (cts->error) return NULL;
+ /* if the opposite flag is on, set limitsort's high bit -- see
+ note in itunesdb.h for more info */
+ if (limitsort_opposite)
+ splp->limitsort |= 0x80000000;
+ result = splp;
+ break;
+ case MHOD_ID_SPLRULES: /* Rules for smart playlist */
+ if (cmp_n_bytes_seek (cts, "SLst", seek, 4))
+ {
+ /* !!! for some reason the SLst part is the only part of the
+ iTunesDB with big-endian encoding, including UTF16
+ strings */
+ gint i;
+ guint32 numrules;
+ SPLRules *splrs = g_new0 (SPLRules, 1);
+ splrs->unk004 = get32bint (cts, seek+4);
+ if (cts->error) return NULL;
+ numrules = get32bint (cts, seek+8);
+ if (cts->error) return NULL;
+ splrs->match_operator = get32bint (cts, seek+12);
+ if (cts->error) return NULL;
+ seek += 136; /* I can't find this value stored in the
+ iTunesDB :-( */
+ for (i=0; i<numrules; ++i)
+ {
+ guint32 length;
+ SPLRule *splr = g_new0 (SPLRule, 1);
+ splr->field = get32bint (cts, seek);
+ if (cts->error) return NULL;
+ splr->action = get32bint (cts, seek+4);
+ if (cts->error) return NULL;
+ seek += 52;
+ length = get32bint (cts, seek);
+ if (cts->error) return NULL;
+ if (itdb_spl_action_known (splr->action))
+ {
+ gint ft = itdb_splr_get_field_type (splr);
+ if (ft == splft_string)
+ {
+ gunichar2 *string_utf16 = g_new0 (gunichar2,
+ (length+2)/2);
+ if (!seek_get_n_bytes (cts, (gchar *)string_utf16,
+ seek+4, length))
+ {
+ g_free (string_utf16);
+ g_free (splr);
+ return NULL;
+ }
+ fixup_big_utf16 (string_utf16);
+ splr->string = g_utf16_to_utf8 (
+ string_utf16, -1, NULL, NULL, NULL);
+ g_free (string_utf16);
+ }
+ else
+ {
+ if (length != 0x44)
+ {
+ g_warning (_("Length of smart playlist rule field (%d) not as expected. Trying to continue anyhow.\n"), length);
+ }
+ splr->fromvalue = get64bint (cts, seek+4);
+ if (cts->error) return NULL;
+ splr->fromdate = get64bint (cts, seek+12);
+ if (cts->error) return NULL;
+ splr->fromunits = get64bint (cts, seek+20);
+ if (cts->error) return NULL;
+ splr->tovalue = get64bint (cts, seek+28);
+ if (cts->error) return NULL;
+ splr->todate = get64bint (cts, seek+36);
+ if (cts->error) return NULL;
+ splr->tounits = get64bint (cts, seek+44);
+ if (cts->error) return NULL;
+ /* SPLFIELD_PLAYLIST seem to use these unknowns*/
+ splr->unk052 = get32bint (cts, seek+52);
+ if (cts->error) return NULL;
+ splr->unk056 = get32bint (cts, seek+56);
+ if (cts->error) return NULL;
+ splr->unk060 = get32bint (cts, seek+60);
+ if (cts->error) return NULL;
+ splr->unk064 = get32bint (cts, seek+64);
+ if (cts->error) return NULL;
+ splr->unk068 = get32bint (cts, seek+68);
+ if (cts->error) return NULL;
+ }
+ seek += length+4;
+ }
+ else
+ {
+ g_free (splr);
+ splr = NULL;
+ }
+ if (splr)
+ {
+ splrs->rules = g_list_append (splrs->rules, splr);
+ }
+ }
+ result = splrs;
+ }
+ else
+ {
+ if (!cts->error)
+ g_warning (_("Did not find SLst hunk as expected. Trying to continue.\n"));
+ else
+ return NULL;
+ }
+ break;
+ default:
+ g_warning (_("Encountered unknown MHOD type (%d) while parsing the iTunesDB. Ignoring.\n\n"), *mty);
+ break;
+ }
+ *ml = len;
+ return result;
+}
+
+/* Returns the value of a string type mhod. return the length of the
+ mhod *ml, the mhod type *mty, and a string with the entry (in
+ UTF16). After use you must free the string with g_free(). Returns
+ NULL if no string is avaible. *ml is set to -1 in case of error and
+ cts->error is set appropriately. */
+static gunichar2 *get_mhod_string (FContents *cts, glong seek, gint32 *ml, gint32 *mty)
+{
+ gunichar2 *result = NULL;
+
+ *mty = get_mhod_type (cts, seek, ml);
+ if (cts->error) return NULL;
+
+ if (*ml != -1) switch ((enum MHOD_ID)*mty)
+ {
+ case MHOD_ID_TITLE:
+ case MHOD_ID_PATH:
+ case MHOD_ID_ALBUM:
+ case MHOD_ID_ARTIST:
+ case MHOD_ID_GENRE:
+ case MHOD_ID_FDESC:
+ case MHOD_ID_COMMENT:
+ case MHOD_ID_COMPOSER:
+ case MHOD_ID_GROUPING:
+ result = get_mhod (cts, seek, ml, mty);
+ break;
+ case MHOD_ID_SPLPREF:
+ case MHOD_ID_SPLRULES:
+ case MHOD_ID_MHYP:
+ case MHOD_ID_PLAYLIST:
+ /* these do not have a string entry */
+ break;
+ }
+ return result;
+}
+
+
+/* Get a playlist. Returns the position where the next playlist should
+ be. On error -1 is returned and fimp->error is set appropriately. */
+static glong get_playlist (FImport *fimp, glong seek)
+{
+ gint pos_comp (gpointer a, gpointer b)
+ {
+ return ((gint)a - (gint)b);
+ }
+
+ gunichar2 *plname_utf16 = NULL;
+ guint32 i, type, tracknum, mhod_num;
+ glong nextseek;
+ guint32 hlen;
+ Itdb_Playlist *plitem = NULL;
+ FContents *cts;
+
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "mhyp seek: %x\n", (int)seek);
+#endif
+ g_return_val_if_fail (fimp, -1);
+ g_return_val_if_fail (fimp->idtree, -1);
+
+ cts = fimp->itunesdb;
+
+ if (!cmp_n_bytes_seek (cts, "mhyp", seek, 4))
+ {
+ if (cts->error)
+ g_propagate_error (&fimp->error, cts->error);
+ return -1;
+ }
+ hlen = get32lint (cts, seek+4); /* length of header */
+ CHECK_ERROR (fimp, -1);
+ if (hlen == 0)
+ {
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("iTunesDB corrupt: hunk length 0 for hunk at %ld in file '%s'."),
+ seek, cts->filename);
+ return -1;
+ }
+ nextseek = seek + get32lint (cts, seek+8);/* possible begin of next PL */
+ CHECK_ERROR (fimp, -1);
+ mhod_num = get32lint (cts, seek+12); /* number of MHODs we expect */
+ CHECK_ERROR (fimp, -1);
+ tracknum = get32lint (cts, seek+16); /* number of tracks in playlist */
+ CHECK_ERROR (fimp, -1);
+ plitem = itdb_playlist_new (NULL, FALSE);
+ /* Some Playlists have added 256 to their type -- I don't know what
+ it's for, so we just ignore it for now -> & 0xff */
+ plitem->type = get32lint (cts, seek+20) & 0xff;
+ CHECK_ERROR (fimp, -1);
+ plitem->id = get64lint (cts, seek+28);
+ CHECK_ERROR (fimp, -1);
+ plitem->unk036 = get32lint (cts, seek+36);
+ CHECK_ERROR (fimp, -1);
+ plitem->unk040 = get32lint (cts, seek+40);
+ CHECK_ERROR (fimp, -1);
+ plitem->unk044 = get32lint (cts, seek+44);
+ CHECK_ERROR (fimp, -1);
+ for (i=0; i < mhod_num; ++i)
+ {
+ gunichar2 *plname_utf16_maybe;
+ SPLPref *splpref = NULL;
+ SPLRules *splrules = NULL;
+
+ seek += hlen;
+ type = get_mhod_type (cts, seek, &hlen);
+ CHECK_ERROR (fimp, -1);
+ if (hlen != -1) switch ((enum MHOD_ID)type)
+ {
+ case MHOD_ID_PLAYLIST:
+ /* here we could do something about the playlist settings */
+ break;
+ case MHOD_ID_TITLE:
+ plname_utf16_maybe = get_mhod (cts, seek, &hlen, &type);
+ CHECK_ERROR (fimp, -1);
+ if (plname_utf16_maybe)
+ {
+ /* sometimes there seem to be two mhod TITLE headers */
+ g_free (plname_utf16);
+ plname_utf16 = plname_utf16_maybe;
+ }
+ break;
+ case MHOD_ID_SPLPREF:
+ splpref = get_mhod (cts, seek, &hlen, &type);
+ CHECK_ERROR (fimp, -1);
+ if (splpref)
+ {
+ plitem->is_spl = TRUE;
+ memcpy (&plitem->splpref, splpref, sizeof (SPLPref));
+ g_free (splpref);
+ splpref = NULL;
+ }
+ break;
+ case MHOD_ID_SPLRULES:
+ splrules = get_mhod (cts, seek, &hlen, &type);
+ CHECK_ERROR (fimp, -1);
+ if (splrules)
+ {
+ plitem->is_spl = TRUE;
+ memcpy (&plitem->splrules, splrules, sizeof (SPLRules));
+ g_free (splrules);
+ splrules = NULL;
+ }
+ break;
+ case MHOD_ID_PATH:
+ case MHOD_ID_ALBUM:
+ case MHOD_ID_ARTIST:
+ case MHOD_ID_GENRE:
+ case MHOD_ID_FDESC:
+ case MHOD_ID_COMMENT:
+ case MHOD_ID_COMPOSER:
+ case MHOD_ID_GROUPING:
+ /* these are not expected here */
+ break;
+ case MHOD_ID_MHYP:
+ /* this I don't know how to handle */
+ break;
+ }
+ }
+
+ if (plname_utf16)
+ {
+ plitem->name = g_utf16_to_utf8 (plname_utf16, -1, NULL, NULL, NULL);
+ g_free (plname_utf16);
+ }
+ else
+ { /* we did not read a valid mhod TITLE header -> */
+ /* we simply make up our own name */
+ if (plitem->type == ITDB_PL_TYPE_MPL)
+ plitem->name = _("Master-PL");
+ else
+ plitem->name = _("Playlist");
+ }
+
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "pln: %s(%d Itdb_Tracks) \n", plitem->name, (int)tracknum);
+#endif
+
+ /* add new playlist */
+ itdb_playlist_add (fimp->itdb, plitem, -1);
+
+ i=0; /* tracks read */
+ while (i<tracknum)
+ {
+ guint32 len = get32lint (cts, seek+8);
+#if ITUNESDB_DEBUG
+ fprintf(stderr, " %lx: seeking track %d of %d\n", seek, i+1, (int)tracknum);
+#endif
+ CHECK_ERROR (fimp, -1);
+ /* We read the mhip headers and skip everything else (the mhips
+ * seem to come in pairs: mhip/mhod mhip/mhod ...). */
+ if (cmp_n_bytes_seek (cts, "mhyp", seek, 4))
+ { /* This cannot be, let's abort... */
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("iTunesDB corrupt: found mhyp at %ld in file '%s'."),
+ seek, cts->filename);
+ return -1;
+ }
+ if (cmp_n_bytes_seek (cts, "mhip", seek, 4))
+ {
+#if ITUNESDB_DEBUG
+ fprintf(stderr, " %lx: mhit\n", seek);
+#endif
+ Itdb_Track *tr;
+ gint32 pos = -1;
+ guint32 posid;
+ gint32 mhod_type;
+ gint32 mhod_len;
+ guint32 mhit_len;
+ guint32 ref;
+ mhit_len = get32lint(cts, seek+4);
+ CHECK_ERROR (fimp, -1);
+ ref = get32lint(cts, seek+24);
+ CHECK_ERROR (fimp, -1);
+ /* the mhod that follows gives us the position in the
+ playlist (type 100) */
+ mhod_type = get_mhod_type (cts, seek+mhit_len, NULL);
+ CHECK_ERROR (fimp, -1);
+ if (mhod_type == MHOD_ID_PLAYLIST)
+ {
+ posid = (guint32)get_mhod (cts, seek+mhit_len,
+ &mhod_len, &mhod_type);
+ CHECK_ERROR (fimp, -1);
+ /* The posids don't have to be in numeric order, but our
+ database depends on the playlist members being sorted
+ according to the order they appear in the
+ playlist. Therefore we need to find out at which
+ position to insert the track */
+ fimp->pos_glist = g_list_insert_sorted (
+ fimp->pos_glist, (gpointer)posid,
+ (GCompareFunc)pos_comp);
+ pos = g_list_index (fimp->pos_glist, (gpointer)posid);
+ /* For performance reasons set pos to -1 if position is
+ end of list */
+ if (pos == i) pos = -1;
+ }
+ tr = itdb_track_id_tree_by_id (fimp->idtree, ref);
+ if (tr)
+ {
+ itdb_playlist_add_track (plitem, tr, pos);
+ }
+ else
+ g_warning (_("Itdb_Track ID '%d' not found.\n"), ref);
+ ++i;
+ }
+ CHECK_ERROR (fimp, -1);
+ seek += len;
+ }
+ g_list_free (fimp->pos_glist);
+ fimp->pos_glist = NULL;
+ return nextseek;
+}
+
+
+/* returns a pointer to the next header or -1 on error. fimp->error is
+ set appropriately. If no "mhit" header is found at the location
+ specified, -1 is returned but no error is set. */
+static glong get_mhit (FImport *fimp, glong seek)
+{
+ Itdb_Track *track;
+ gchar *entry_utf8;
+ gunichar2 *entry_utf16;
+ gint32 type, zip;
+ struct playcount *playcount;
+ guint32 i, temp, mhod_nums;
+ FContents *cts;
+
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "get_mhit seek: %x\n", (int)seek);
+#endif
+
+ g_return_val_if_fail (fimp, -1);
+
+ cts = fimp->itunesdb;
+
+ if (!cmp_n_bytes_seek (cts, "mhit", seek, 4))
+ {
+ if (cts->error)
+ g_propagate_error (&fimp->error, cts->error);
+ return -1;
+ }
+
+ mhod_nums = get32lint (cts, seek+12);
+ CHECK_ERROR (fimp, -1);
+
+ track = itdb_track_new ();
+
+ track->id = get32lint(cts, seek+16); /* iPod ID */
+ CHECK_ERROR (fimp, -1);
+ track->unk020 = get32lint (cts, seek+20);
+ CHECK_ERROR (fimp, -1);
+ track->unk024 = get32lint (cts, seek+24);
+ CHECK_ERROR (fimp, -1);
+ temp = get32lint (cts, seek+28);
+ CHECK_ERROR (fimp, -1);
+ track->rating = (temp & 0xff000000) >> 24; /* rating */
+ track->compilation = (temp & 0x00ff0000) >> 16;
+ track->type = temp & 0x0000ffff;
+ track->time_added = get32lint(cts, seek+32); /* time added */
+ CHECK_ERROR (fimp, -1);
+ track->size = get32lint(cts, seek+36); /* file size */
+ CHECK_ERROR (fimp, -1);
+ track->tracklen = get32lint(cts, seek+40); /* time */
+ CHECK_ERROR (fimp, -1);
+ track->track_nr = get32lint(cts, seek+44); /* track number */
+ CHECK_ERROR (fimp, -1);
+ track->tracks = get32lint(cts, seek+48); /* nr of tracks */
+ CHECK_ERROR (fimp, -1);
+ track->year = get32lint(cts, seek+52); /* year */
+ CHECK_ERROR (fimp, -1);
+ track->bitrate = get32lint(cts, seek+56); /* bitrate */
+ CHECK_ERROR (fimp, -1);
+ track->samplerate = get32lint(cts,seek+60)>>16; /* sample rate */
+ CHECK_ERROR (fimp, -1);
+ track->volume = get32lint(cts, seek+64); /* volume adjust */
+ CHECK_ERROR (fimp, -1);
+ track->starttime = get32lint (cts, seek+68);
+ CHECK_ERROR (fimp, -1);
+ track->stoptime = get32lint (cts, seek+72);
+ CHECK_ERROR (fimp, -1);
+ track->soundcheck = get32lint (cts, seek+76); /* soundcheck */
+ CHECK_ERROR (fimp, -1);
+ track->playcount = get32lint (cts, seek+80); /* playcount */
+ CHECK_ERROR (fimp, -1);
+ track->unk084 = get32lint (cts, seek+84);
+ CHECK_ERROR (fimp, -1);
+ track->time_played = get32lint(cts, seek+88); /* last time played */
+ CHECK_ERROR (fimp, -1);
+ track->cd_nr = get32lint(cts, seek+92); /* CD nr */
+ CHECK_ERROR (fimp, -1);
+ track->cds = get32lint(cts, seek+96); /* CD nr of.. */
+ CHECK_ERROR (fimp, -1);
+ track->unk100 = get32lint (cts, seek+100);
+ CHECK_ERROR (fimp, -1);
+ track->time_modified = get32lint(cts, seek+104);/* last mod. time */
+ CHECK_ERROR (fimp, -1);
+ track->bookmark_time = get32lint (cts, seek+108); /* time bookmarked */
+ CHECK_ERROR (fimp, -1);
+ track->dbid = get64lint (cts, seek+112);
+ CHECK_ERROR (fimp, -1);
+ temp = get32lint (cts, seek+120);
+ CHECK_ERROR (fimp, -1);
+ track->BPM = temp >> 16;
+ track->app_rating = (temp & 0xff00)>> 8;/* The rating set by * the
+ application, as opposed to
+ the rating set on the iPod
+ itself */
+ track->checked = temp & 0xff; /* Checked/Unchecked: 0/1 */
+ track->unk124 = get32lint (cts, seek+124);
+ CHECK_ERROR (fimp, -1);
+ track->unk128 = get32lint (cts, seek+128);
+ CHECK_ERROR (fimp, -1);
+ track->unk132 = get32lint (cts, seek+132);
+ CHECK_ERROR (fimp, -1);
+ track->unk136 = get32lint (cts, seek+136);
+ CHECK_ERROR (fimp, -1);
+ track->unk140 = get32lint (cts, seek+140);
+ CHECK_ERROR (fimp, -1);
+ track->unk144 = get32lint (cts, seek+144);
+ CHECK_ERROR (fimp, -1);
+ track->unk148 = get32lint (cts, seek+148);
+ CHECK_ERROR (fimp, -1);
+ track->unk152 = get32lint (cts, seek+152);
+ CHECK_ERROR (fimp, -1);
+
+ track->transferred = TRUE; /* track is on iPod! */
+
+#if ITUNESDB_MHIT_DEBUG
+time_t time_mac_to_host (guint32 mactime);
+gchar *time_time_to_string (time_t time);
+#define printf_mhit(sk, str) printf ("%3d: %d (%s)\n", sk, get32lint (file, seek+sk), str);
+#define printf_mhit_time(sk, str) { gchar *buf = time_time_to_string (itunesdb_time_mac_to_host (get32lint (file, seek+sk))); printf ("%3d: %s (%s)\n", sk, buf, str); g_free (buf); }
+ {
+ printf ("\nmhit: seek=%lu\n", seek);
+ printf_mhit ( 4, "header size");
+ printf_mhit ( 8, "mhit size");
+ printf_mhit ( 12, "nr of mhods");
+ printf_mhit ( 16, "iPod ID");
+ printf_mhit ( 20, "?");
+ printf_mhit ( 24, "?");
+ printf (" 28: %u (type)\n", get32lint (file, seek+28) & 0xffffff);
+ printf (" 28: %u (rating)\n", get32lint (file, seek+28) >> 24);
+ printf_mhit ( 32, "timestamp file");
+ printf_mhit_time ( 32, "timestamp file");
+ printf_mhit ( 36, "size");
+ printf_mhit ( 40, "tracklen (ms)");
+ printf_mhit ( 44, "track_nr");
+ printf_mhit ( 48, "total tracks");
+ printf_mhit ( 52, "year");
+ printf_mhit ( 56, "bitrate");
+ printf_mhit ( 60, "sample rate");
+ printf (" 60: %u (sample rate LSB)\n", get32lint (file, seek+60) & 0xffff);
+ printf (" 60: %u (sample rate HSB)\n", (get32lint (file, seek+60) >> 16));
+ printf_mhit ( 64, "?");
+ printf_mhit ( 68, "?");
+ printf_mhit ( 72, "?");
+ printf_mhit ( 76, "?");
+ printf_mhit ( 80, "playcount");
+ printf_mhit ( 84, "?");
+ printf_mhit ( 88, "last played");
+ printf_mhit_time ( 88, "last played");
+ printf_mhit ( 92, "CD");
+ printf_mhit ( 96, "total CDs");
+ printf_mhit (100, "?");
+ printf_mhit (104, "?");
+ printf_mhit_time (104, "?");
+ printf_mhit (108, "?");
+ printf_mhit (112, "?");
+ printf_mhit (116, "?");
+ printf_mhit (120, "?");
+ printf_mhit (124, "?");
+ printf_mhit (128, "?");
+ printf_mhit (132, "?");
+ printf_mhit (136, "?");
+ printf_mhit (140, "?");
+ printf_mhit (144, "?");
+ printf_mhit (148, "?");
+ printf_mhit (152, "?");
+ }
+#undef printf_mhit_time
+#undef printf_mhit
+#endif
+
+ seek += get32lint (cts, seek+4); /* 1st mhod starts here! */
+ CHECK_ERROR (fimp, -1);
+
+ for (i=0; i<mhod_nums; ++i)
+ {
+ entry_utf16 = get_mhod_string (cts, seek, &zip, &type);
+ CHECK_ERROR (fimp, -1);
+ if (entry_utf16 != NULL)
+ {
+ entry_utf8 = g_utf16_to_utf8 (entry_utf16, -1, NULL, NULL, NULL);
+ switch ((enum MHOD_ID)type)
+ {
+ case MHOD_ID_ALBUM:
+ track->album = entry_utf8;
+ break;
+ case MHOD_ID_ARTIST:
+ track->artist = entry_utf8;
+ break;
+ case MHOD_ID_TITLE:
+ track->title = entry_utf8;
+ break;
+ case MHOD_ID_GENRE:
+ track->genre = entry_utf8;
+ break;
+ case MHOD_ID_PATH:
+ track->ipod_path = entry_utf8;
+ break;
+ case MHOD_ID_FDESC:
+ track->fdesc = entry_utf8;
+ break;
+ case MHOD_ID_COMMENT:
+ track->comment = entry_utf8;
+ break;
+ case MHOD_ID_COMPOSER:
+ track->composer = entry_utf8;
+ break;
+ case MHOD_ID_GROUPING:
+ track->grouping = entry_utf8;
+ break;
+ default: /* unknown entry -- discard */
+ g_free (entry_utf8);
+ break;
+ }
+ g_free (entry_utf16);
+ }
+ seek += zip;
+ }
+
+ playcount = playcount_get_next (fimp);
+ if (playcount)
+ {
+ if (playcount->rating != NO_PLAYCOUNT)
+ track->rating = playcount->rating;
+
+ if (playcount->time_played)
+ track->time_played = playcount->time_played;
+
+ if (playcount->bookmark_time)
+ track->bookmark_time = playcount->bookmark_time;
+
+ track->playcount += playcount->playcount;
+ track->recent_playcount = playcount->playcount;
+ g_free (playcount);
+ }
+ itdb_track_add (fimp->itdb, track, -1);
+ return seek;
+}
+
+
+/* Called by read_OTG_playlists(): OTG playlist stored in @cts by
+ * adding a new playlist (named @plname) with the tracks specified in
+ * @cts. If @plname is NULL, a standard name will be substituted */
+/* Returns FALSE on error, TRUE on success. On error @fimp->error will
+ * be set apropriately. */
+static gboolean process_OTG_file (FImport *fimp, FContents *cts,
+ const gchar *plname)
+{
+ guint32 header_length, entry_length, entry_num;
+
+ g_return_val_if_fail (fimp && cts, FALSE);
+ g_return_val_if_fail (fimp->itdb, FALSE);
+
+ if (!plname) plname = _("OTG Playlist");
+
+ if (!cmp_n_bytes_seek (cts, "mhpo", 0, 4))
+ {
+ if (cts->error)
+ {
+ g_propagate_error (&fimp->error, cts->error);
+ }
+ else
+ { /* set error */
+ g_return_val_if_fail (cts->filename, FALSE);
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("Not a OTG playlist file: '%s' (missing mhpo header)."),
+ cts->filename);
+ }
+ return FALSE;
+ }
+ header_length = get32lint (cts, 4);
+ CHECK_ERROR (fimp, FALSE);
+ /* all the headers I know are 0x14 long -- if this one is
+ longer we can simply ignore the additional information */
+ if (header_length < 0x14)
+ {
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("OTG playlist file ('%s'): header length smaller than expected (%d<20)."),
+ cts->filename, header_length);
+ return FALSE;
+ }
+ entry_length = get32lint (cts, 8);
+ CHECK_ERROR (fimp, FALSE);
+ /* all the entries I know are 0x04 long */
+ if (entry_length < 0x04)
+ {
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("OTG playlist file file ('%s'): entry length smaller than expected (%d<4)."),
+ cts->filename, entry_length);
+ return FALSE;
+ }
+ /* number of entries */
+ entry_num = get32lint (cts, 12);
+ CHECK_ERROR (fimp, FALSE);
+
+ if (entry_num > 0)
+ {
+ gint i;
+ Itdb_Playlist *pl;
+
+ pl = itdb_playlist_new (plname, FALSE);
+ /* Add new playlist */
+ itdb_playlist_add (fimp->itdb, pl, -1);
+
+ /* Add items */
+ for (i=0; i<entry_num; ++i)
+ {
+ Itdb_Track *track;
+ guint32 num = get32lint (cts,
+ header_length + entry_length *i);
+ CHECK_ERROR (fimp, FALSE);
+
+ track = g_list_nth_data (fimp->itdb->tracks, num);
+ if (track)
+ {
+ itdb_playlist_add_track (pl, track, -1);
+ }
+ else
+ {
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("OTG playlist file '%s': reference to non-existent track (%d)."),
+ cts->filename, num);
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+
+
+
+/* Add the On-The-Go Playlist(s) to the database */
+/* The OTG-Files are located in the directory given by
+ fimp->itdb->itdb_filename.
+ On error FALSE is returned and fimp->error is set accordingly. */
+static gboolean read_OTG_playlists (FImport *fimp)
+{
+ gchar *db[] = {"OTGPlaylistInfo", NULL};
+ gchar *dirname, *otgname;
+
+ g_return_val_if_fail (fimp, FALSE);
+ g_return_val_if_fail (fimp->itdb, FALSE);
+ g_return_val_if_fail (fimp->itdb->filename, FALSE);
+
+ dirname = g_path_get_dirname (fimp->itdb->filename);
+
+ otgname = itdb_resolve_path (dirname, (const gchar **)db);
+
+
+ /* only parse if "OTGPlaylistInfo" exists */
+ if (otgname)
+ {
+ gchar *filename;
+ gint i=1;
+ do
+ {
+ db[0] = g_strdup_printf ("OTGPlaylistInfo_%d", i);
+ filename = itdb_resolve_path (dirname, (const gchar **)db);
+ g_free (db[0]);
+ if (filename)
+ {
+ FContents *cts = fcontents_read (filename, &fimp->error);
+ if (cts)
+ {
+ gchar *plname = g_strdup_printf (_("OTG Playlist %d"), i);
+ process_OTG_file (fimp, cts, plname);
+ g_free (plname);
+ fcontents_free (cts);
+ }
+ g_free (filename);
+ }
+ if (fimp->error) break;
+ ++i;
+ } while (filename);
+ g_free (otgname);
+ }
+ g_free (dirname);
+ return TRUE;
+}
+
+
+
+static gboolean parse_fimp (FImport *fimp)
+{
+ glong seek=0, pl_mhsd=0;
+ guint32 i, zip, nr_tracks=0, nr_playlists=0;
+ gboolean swapped_mhsd = FALSE;
+ FContents *cts;
+
+ g_return_val_if_fail (fimp, FALSE);
+ g_return_val_if_fail (fimp->itdb, FALSE);
+ g_return_val_if_fail (fimp->itunesdb, FALSE);
+ g_return_val_if_fail (fimp->itunesdb->filename, FALSE);
+
+ cts = fimp->itunesdb;
+
+ if (!cmp_n_bytes_seek (cts, "mhbd", 0, 4))
+ {
+ if (cts->error)
+ {
+ g_propagate_error (&fimp->error, cts->error);
+ }
+ else
+ { /* set error */
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("Not a iTunesDB: '%s' (missing mhdb header)."),
+ cts->filename);
+ }
+ return FALSE;
+ }
+ seek = get32lint (cts, 4);
+ CHECK_ERROR (fimp, FALSE);
+ /* all the headers I know are 0x68 long -- if this one is longer
+ we can could simply ignore the additional information */
+ /* Since we only need data from the first 32 bytes, don't complain
+ * unless it's smaller than that */
+ if (seek < 32)
+ {
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("iTunesDB ('%s'): header length of mhsd hunk smaller than expected (%ld<32). Aborting."),
+ cts->filename, seek);
+ return FALSE;
+ }
+
+ fimp->itdb->version = get32lint (cts, seek+16);
+ CHECK_ERROR (fimp, FALSE);
+ fimp->itdb->id = get64lint (cts, seek+24);
+ CHECK_ERROR (fimp, FALSE);
+
+ for (;;)
+ {
+ if (cmp_n_bytes_seek (cts, "mhsd", seek, 4))
+ { /* mhsd header -> determine start of playlists */
+ guint32 sth;
+ guint32 len;
+ len = get32lint (cts, seek+8);
+ CHECK_ERROR (fimp, FALSE);
+ sth = get32lint (cts, seek+12);
+ CHECK_ERROR (fimp, FALSE);
+ if (sth == 1)
+ { /* OK, tracklist, save start of playlists */
+ if (!swapped_mhsd)
+ pl_mhsd = seek + len;
+ }
+ else if (sth == 2)
+ { /* bad: these are playlists... switch */
+ if (swapped_mhsd)
+ { /* already switched once -> forget it */
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("iTunesDB '%s' corrupt: already found two playlist mhsds -- giving up."),
+ cts->filename);
+ return FALSE;
+ }
+ else
+ {
+ pl_mhsd = seek;
+ seek += len;
+ swapped_mhsd = TRUE;
+ }
+ }
+ else
+ { /* neither playlist nor track MHSD --> skip it */
+ seek += len;
+ }
+ }
+ else
+ { /* if the cmp_n_bytes_seek() failed we must check if it is
+ because of an error */
+ CHECK_ERROR (fimp, FALSE);
+ }
+ if (cmp_n_bytes_seek (cts, "mhlt", seek, 4))
+ { /* mhlt header -> number of tracks */
+ nr_tracks = get32lint (cts, seek+8);
+ CHECK_ERROR (fimp, FALSE);
+ if (nr_tracks == 0)
+ { /* no tracks -- skip directly to next mhsd */
+ break;
+ }
+ }
+ else
+ { /* if the cmp_n_bytes_seek() failed we must check if it is
+ because of an error */
+ CHECK_ERROR (fimp, FALSE);
+ }
+ if (cmp_n_bytes_seek (cts, "mhit", seek, 4))
+ { /* mhit header -> start of tracks*/
+ break;
+ }
+ zip = get32lint (cts, seek+4);
+ CHECK_ERROR (fimp, FALSE);
+ if (zip == 0)
+ {
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("iTunesDB corrupt: hunk length 0 for hunk at %ld in file '%s'."),
+ seek, cts->filename);
+ return FALSE;
+ }
+ seek += zip;
+ }
+ /* now we should be at the first MHIT */
+
+ /* get every file entry */
+ for (i=0; i<nr_tracks; ++i)
+ {
+ seek = get_mhit (fimp, seek);
+ if (fimp->error) return FALSE;
+ if (seek == -1)
+ { /* this should not be -- issue warning */
+ g_warning (_("iTunesDB possibly corrupt: number of tracks (mhit hunks) inconsistent. Trying to continue.\n"));
+ break;
+ }
+ }
+
+ /* next: playlists */
+ seek = pl_mhsd;
+ for (;;)
+ { /* this is all a bit of magic to make sure we can handle
+ slightly "off-standard" iTunesDBs as well. Normally we
+ would expect hunks in the following order: <mhsd type 2>,
+ <mhlp> containing the number of playlists, <mhyp>: first
+ playlist header. Here we just scan everything until we find
+ the first <mhyp> ignoring everything we don't know. */
+ zip = get32lint (cts, seek+4);
+ if (zip == 0)
+ {
+ g_set_error (&fimp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_CORRUPT,
+ _("iTunesDB corrupt: hunk length 0 for hunk at %ld in file '%s'."),
+ seek, cts->filename);
+ return FALSE;
+ }
+ CHECK_ERROR (fimp, FALSE);
+ if (cmp_n_bytes_seek (cts, "mhsd", seek, 4))
+ { /* We just check if it's actually a playlist mhsd (type=2)
+ or not (type = 1, should not be...) */
+ guint32 type;
+ guint32 len = get32lint (cts, seek+8);
+ CHECK_ERROR (fimp, FALSE);
+ type = get32lint (cts, seek+12);
+ CHECK_ERROR (fimp, FALSE);
+ if (type != 2)
+ { /* this is not a playlist MHSD -> skip it */
+ seek += len;
+ continue;
+ }
+ else
+ { /* jump to next hunk */
+ seek += zip;
+ continue;
+ }
+ }
+ else
+ {
+ CHECK_ERROR (fimp, FALSE);
+ }
+ if (cmp_n_bytes_seek (cts, "mhlp", seek, 4))
+ { /* mhlp header -> number of playlists */
+ nr_playlists = get32lint (cts, seek+8);
+ CHECK_ERROR (fimp, FALSE);
+ seek += zip;
+ continue;
+ }
+ else
+ {
+ CHECK_ERROR (fimp, FALSE);
+ }
+ if (cmp_n_bytes_seek (cts, "mhyp", seek, 4))
+ { /* mhyp header -> start of playlists */
+ break;
+ }
+ else
+ {
+ CHECK_ERROR (fimp, FALSE);
+ }
+ seek += zip;
+ }
+
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "iTunesDB part2 starts at: %x\n", (int)seek);
+#endif
+
+ /* Create track-id tree for quicker track lookup */
+ fimp->idtree = itdb_track_id_tree_create (fimp->itdb);
+ for (i=0; i<nr_playlists; ++i)
+ {
+ seek = get_playlist (fimp, seek);
+ if (fimp->error) return FALSE;
+ if (seek == -1)
+ { /* this should not be -- issue warning */
+ g_warning (_("iTunesDB possibly corrupt: number of playlists (mhyp hunks) inconsistent. Trying to continue.\n"));
+ break;
+ }
+ }
+ itdb_track_id_tree_destroy (fimp->idtree);
+ fimp->idtree = NULL;
+
+ return TRUE;
+}
+
+
+/* Parse the Itdb_iTunesDB.
+ Returns a pointer to the Itdb_iTunesDB struct holding the tracks and the
+ playlists.
+ "mp" should point to the mount point of the iPod,
+ e.g. "/mnt/ipod" and be in local encoding */
+Itdb_iTunesDB *itdb_parse (const gchar *mp, GError **error)
+{
+ gchar *filename;
+ Itdb_iTunesDB *itdb = NULL;
+ const gchar *db[] = {"iPod_Control","iTunes","iTunesDB",NULL};
+
+ filename = itdb_resolve_path (mp, db);
+ if (filename)
+ {
+ itdb = itdb_parse_file (filename, error);
+ if (itdb)
+ {
+ itdb->mountpoint = g_strdup (mp);
+ }
+ g_free (filename);
+ }
+ else
+ {
+ gchar *str = g_build_filename (mp, db[0], db[1], db[2], db[3], NULL);
+ g_set_error (error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_NOTFOUND,
+ _("File not found: '%s'."),
+ str);
+ g_free (str);
+ }
+ return itdb;
+}
+
+
+/* Same as itunesdb_parse(), but filename is specified directly. */
+Itdb_iTunesDB *itdb_parse_file (const gchar *filename, GError **error)
+{
+ FImport *fimp;
+ Itdb_iTunesDB *itdb;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (filename, NULL);
+
+ fimp = g_new0 (FImport, 1);
+ itdb = itdb_new ();
+ itdb->filename = g_strdup (filename);
+ fimp->itdb = itdb;
+
+ fimp->itunesdb = fcontents_read (filename, error);
+
+ if (fimp->itunesdb)
+ {
+ if (playcounts_init (fimp))
+ {
+ if (parse_fimp (fimp))
+ {
+ if (read_OTG_playlists (fimp))
+ {
+ success = TRUE;
+ }
+ }
+ }
+ }
+
+ if (!success)
+ {
+ itdb_free (itdb);
+ itdb = NULL;
+ if (fimp->error)
+ g_propagate_error (error, fimp->error);
+ }
+ itdb_free_fimp (fimp);
+ return itdb;
+}
+
+
+/* up to here we had the functions for reading the iTunesDB */
+/* ---------------------------------------------------------------------- */
+/* from here on we have the functions for writing the iTunesDB */
+
+/* will expand @cts when necessary in order to accomodate @len bytes
+ starting at @seek */
+static void wcontents_maybe_expand (WContents *cts, gulong len,
+ gulong seek)
+{
+ g_return_if_fail (cts);
+
+ while (cts->pos+len > cts->total)
+ {
+ cts->total += WCONTENTS_STEPSIZE;
+ cts->contents = g_realloc (cts->contents, cts->total);
+ }
+}
+
+
+/* Write @data, @n bytes long to position @seek. Will always be
+ * successful because glib terminates when out of memory */
+static void put_data_seek (WContents *cts, gchar *data,
+ gulong len, gulong seek)
+{
+ g_return_if_fail (cts);
+ g_return_if_fail (data);
+
+ if (len != 0)
+ {
+ wcontents_maybe_expand (cts, len, seek);
+
+ memcpy (&cts->contents[seek], data, len);
+ /* adjust end position if necessary */
+ if (seek+len > cts->pos)
+ cts->pos = seek+len;
+ }
+}
+
+
+
+/* Write @data, @n bytes long to end of @cts. Will always be
+ * successful because glib terminates when out of memory */
+static void put_data (WContents *cts, gchar *data, gulong len)
+{
+ g_return_if_fail (cts);
+
+ put_data_seek (cts, data, len, cts->pos);
+}
+
+
+/* Write 1-byte integer @n to @cts */
+static void put8int (WContents *cts, guint8 n)
+{
+ put_data (cts, (gchar *)&n, 1);
+}
+
+
+/* Write 2-byte integer @n to @cts in little endian order. */
+static void put16lint (WContents *cts, guint16 n)
+{
+# if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ n = GUINT16_SWAP_LE_BE (n);
+# endif
+ put_data (cts, (gchar *)&n, 2);
+}
+
+
+/* Write 4-byte integer @n to @cts in little endian order. */
+static void put32lint (WContents *cts, guint32 n)
+{
+# if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ n = GUINT32_SWAP_LE_BE (n);
+# endif
+ put_data (cts, (gchar *)&n, 4);
+}
+
+
+/* Append @n times 2-byte-long zeros */
+static void put16_n0 (WContents *cts, gulong n)
+{
+ g_return_if_fail (cts);
+
+ if (n>0)
+ {
+ wcontents_maybe_expand (cts, 2*n, cts->pos);
+ memset (&cts->contents[cts->pos], 0, 2*n);
+ cts->pos += 2*n;
+ }
+}
+
+/* Write 3-byte integer @n to @cts in big endian order. */
+static void put24bint (WContents *cts, guint32 n)
+{
+ gchar buf[3] ;
+ buf[0] = (n >> 16) & 0xff ;
+ buf[1] = (n >> 8) & 0xff ;
+ buf[2] = (n >> 0) & 0xff ;
+ put_data (cts, buf, 3);
+}
+
+
+/* Write 4-byte integer @n to @cts at position @seek in little
+ endian order. */
+static void put32lint_seek (WContents *cts, guint32 n, gulong seek)
+{
+# if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ n = GUINT32_SWAP_LE_BE (n);
+# endif
+ put_data_seek (cts, (gchar *)&n, 4, seek);
+}
+
+
+/* Write 8-byte integer @n to @cts in big little order. */
+static void put64lint (WContents *cts, guint64 n)
+{
+# if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ n = GUINT64_SWAP_LE_BE (n);
+# endif
+ put_data (cts, (gchar *)&n, 8);
+}
+
+
+/* Write 4-byte integer @n to @cts in big endian order. */
+static void put32bint (WContents *cts, guint32 n)
+{
+# if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ n = GUINT32_SWAP_LE_BE (n);
+# endif
+ put_data (cts, (gchar *)&n, 4);
+}
+
+
+/* Write 8-byte integer @n to cts in big endian order. */
+static void put64bint (WContents *cts, guint64 n)
+{
+# if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ n = GUINT64_SWAP_LE_BE (n);
+# endif
+ put_data (cts, (gchar *)&n, 8);
+}
+
+
+#if 0
+/* Write 4-byte integer @n to @cts at position @seek in big endian
+ order. */
+static void put32bint_seek (WContents *cts, guint32 n, gulong seek)
+{
+# if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ n = GUINT32_SWAP_LE_BE (n);
+# endif
+ put_data_seek (cts, (gchar *)&n, 4, seek);
+}
+
+/* Write 8-byte integer @n to @cts at position @seek in big endian
+ order. */
+static void put64bint_seek (WContents *cts, guint64 n, gulong seek)
+{
+# if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ n = GUINT64_SWAP_LE_BE (n);
+# endif
+ put_data_seek (cts, (gchar *)&n, 8, seek);
+}
+#endif
+
+
+/* Append @n times 4-byte-long zeros */
+static void put32_n0 (WContents *cts, gulong n)
+{
+ g_return_if_fail (cts);
+
+ if (n>0)
+ {
+ wcontents_maybe_expand (cts, 4*n, cts->pos);
+ memset (&cts->contents[cts->pos], 0, 4*n);
+ cts->pos += 4*n;
+ }
+}
+
+
+
+/* Write out the mhbd header. Size will be written later */
+static void mk_mhbd (FExport *fexp)
+{
+ WContents *cts;
+
+ g_return_if_fail (fexp);
+ g_return_if_fail (fexp->itdb);
+ g_return_if_fail (fexp->itunesdb);
+
+ cts = fexp->itunesdb;
+
+ put_data (cts, "mhbd", 4);
+ put32lint (cts, 104); /* header size */
+ put32lint (cts, -1); /* size of whole mhdb -- fill in later */
+ put32lint (cts, 1); /* ? */
+ if (fexp->itdb->version < 0x09) fexp->itdb->version = 0x09;
+ /* Version number: 0x01: iTunes 2
+ 0x02: iTunes 3
+ 0x09: iTunes 4.2
+ 0x0a: iTunes 4.5
+ 0x0b: iTunes 4.7
+ 0x0c: iTunes 4.71/4.8 (required for shuffle)
+ 0x0d: iTunes 4.9 */
+ fexp->itdb->version = 0x0d;
+ put32lint (cts, fexp->itdb->version);
+ put32lint (cts, 2); /* 2 children (track list and playlist list) */
+ put64lint (cts, fexp->itdb->id);
+ put32lint (cts, 2); /* ? */
+ put32_n0 (cts, 17); /* dummy space */
+}
+
+/* Fill in the length of a standard header */
+static void fix_header (WContents *cts, gulong header_seek)
+{
+ put32lint_seek (cts, cts->pos-header_seek, header_seek+8);
+}
+
+
+/* Write out the mhsd header. Size will be written later */
+static void mk_mhsd (FExport *fexp, guint32 type)
+{
+ WContents *cts;
+
+ g_return_if_fail (fexp);
+ g_return_if_fail (fexp->itdb);
+ g_return_if_fail (fexp->itunesdb);
+
+ cts = fexp->itunesdb;
+
+ put_data (cts, "mhsd", 4);
+ put32lint (cts, 96); /* Headersize */
+ put32lint (cts, -1); /* size of whole mhsd -- fill in later */
+ put32lint (cts, type); /* type: 1 = track, 2 = playlist */
+ put32_n0 (cts, 20); /* dummy space */
+}
+
+
+/* Write out the mhlt header. */
+static void mk_mhlt (FExport *fexp, guint32 num)
+{
+ WContents *cts;
+
+ g_return_if_fail (fexp);
+ g_return_if_fail (fexp->itdb);
+ g_return_if_fail (fexp->itunesdb);
+
+ cts = fexp->itunesdb;
+
+ put_data (cts, "mhlt", 4);
+ put32lint (cts, 92); /* Headersize */
+ put32lint (cts, num); /* tracks in this itunesdb */
+ put32_n0 (cts, 20); /* dummy space */
+}
+
+
+/* Write out the mhit header. Size will be written later */
+static void mk_mhit (WContents *cts, Itdb_Track *track)
+{
+ g_return_if_fail (cts);
+ g_return_if_fail (track);
+
+ put_data (cts, "mhit", 4);
+ put32lint (cts, 156); /* header size */
+ put32lint (cts, -1); /* size of whole mhit -- fill in later */
+ put32lint (cts, -1); /* nr of mhods in this mhit -- later */
+ put32lint (cts, track->id); /* track index number
+ * */
+ put32lint (cts, track->unk020);
+ put32lint (cts, track->unk024);
+ /* rating, compil., type */
+ put32lint (cts, ((guint32)track->rating << 24) |
+ ((guint32)track->compilation << 16) |
+ ((guint32)track->type & 0x0000ffff));
+
+ put32lint (cts, track->time_added); /* timestamp */
+ put32lint (cts, track->size); /* filesize */
+ put32lint (cts, track->tracklen); /* length of track in ms */
+ put32lint (cts, track->track_nr);/* track number */
+ put32lint (cts, track->tracks); /* number of tracks */
+ put32lint (cts, track->year); /* the year */
+ put32lint (cts, track->bitrate); /* bitrate */
+ put32lint (cts, track->samplerate << 16);
+ put32lint (cts, track->volume); /* volume adjust */
+ put32lint (cts, track->starttime);
+ put32lint (cts, track->stoptime);
+ put32lint (cts, track->soundcheck);
+ put32lint (cts, track->playcount);/* playcount */
+ put32lint (cts, track->unk084);
+ put32lint (cts, track->time_played); /* last time played */
+ put32lint (cts, track->cd_nr); /* CD number */
+ put32lint (cts, track->cds); /* number of CDs */
+ put32lint (cts, track->unk100);
+ put32lint (cts, track->time_modified); /* timestamp */
+ put32lint (cts, track->bookmark_time);
+ put64lint (cts, track->dbid);
+ if (track->checked) put8int (cts, 1);
+ else put8int (cts, 0);
+ put8int (cts, track->app_rating);
+ put16lint (cts, track->BPM);
+ put32lint (cts, track->unk124);
+ put32lint (cts, track->unk128);
+ put32lint (cts, track->unk132);
+ put32lint (cts, track->unk136);
+ put32lint (cts, track->unk140);
+ put32lint (cts, track->unk144);
+ put32lint (cts, track->unk148);
+ put32lint (cts, track->unk152);
+}
+
+
+/* Fill in the missing items of the mhit header:
+ total size and number of mhods */
+static void fix_mhit (WContents *cts, gulong mhit_seek, guint32 mhod_num)
+{
+ g_return_if_fail (cts);
+
+ /* size of whole mhit */
+ put32lint_seek (cts, cts->pos-mhit_seek, mhit_seek+8);
+ /* nr of mhods */
+ put32lint_seek (cts, mhod_num, mhit_seek+12);
+}
+
+
+/* Write out one mhod header.
+ type: see enum of MHMOD_IDs;
+ data: utf8 string for text items
+ position indicator for MHOD_ID_PLAYLIST
+ SPLPref for MHOD_ID_SPLPREF
+ SPLRules for MHOD_ID_SPLRULES */
+static void mk_mhod (WContents *cts, enum MHOD_ID type, void *data)
+{
+ g_return_if_fail (cts);
+
+ switch (type)
+ {
+ case MHOD_ID_TITLE:
+ case MHOD_ID_PATH:
+ case MHOD_ID_ALBUM:
+ case MHOD_ID_ARTIST:
+ case MHOD_ID_GENRE:
+ case MHOD_ID_FDESC:
+ case MHOD_ID_COMMENT:
+ case MHOD_ID_COMPOSER:
+ case MHOD_ID_GROUPING:
+ g_return_if_fail (data);
+ {
+ /* convert to utf16 */
+ gunichar2 *entry_utf16 = g_utf8_to_utf16 ((gchar *)data, -1,
+ NULL, NULL, NULL);
+ guint32 len = utf16_strlen (entry_utf16);
+ fixup_little_utf16 (entry_utf16);
+ put_data (cts, "mhod", 4); /* header */
+ put32lint (cts, 24); /* size of header */
+ put32lint (cts, 2*len+40); /* size of header + body */
+ put32lint (cts, type); /* type of the entry */
+ put32_n0 (cts, 2); /* unknown */
+ /* end of header, start of data */
+ put32lint (cts, 1); /* always 1 for these MHOD_IDs*/
+ put32lint (cts, 2*len); /* size of string */
+ put32_n0 (cts, 2); /* unknown */
+ put_data (cts, (gchar *)entry_utf16, 2*len); /* the string */
+ g_free (entry_utf16);
+ }
+ break;
+ case MHOD_ID_PLAYLIST:
+ put_data (cts, "mhod", 4); /* header */
+ put32lint (cts, 24); /* size of header */
+ put32lint (cts, 44); /* size of header + body */
+ put32lint (cts, type); /* type of the entry */
+ put32_n0 (cts, 2); /* unknown */
+ /* end of header, start of data */
+ put32lint (cts, (guint32)data);/* position of track in playlist */
+ put32_n0 (cts, 4); /* unknown */
+ break;
+ case MHOD_ID_SPLPREF:
+ g_return_if_fail (data);
+ {
+ SPLPref *splp = data;
+ put_data (cts, "mhod", 4); /* header */
+ put32lint (cts, 24); /* size of header */
+ put32lint (cts, 96); /* size of header + body */
+ put32lint (cts, type); /* type of the entry */
+ put32_n0 (cts, 2); /* unknown */
+ /* end of header, start of data */
+ put8int (cts, splp->liveupdate);
+ put8int (cts, splp->checkrules? 1:0);
+ put8int (cts, splp->checklimits);
+ put8int (cts, splp->limittype);
+ put8int (cts, splp->limitsort & 0xff);
+ put8int (cts, 0); /* unknown */
+ put8int (cts, 0); /* unknown */
+ put8int (cts, 0); /* unknown */
+ put32lint (cts, splp->limitvalue);
+ put8int (cts, splp->matchcheckedonly);
+ /* for the following see note at definitions of limitsort
+ types in itunesdb.h */
+ put8int (cts, (splp->limitsort & 0x80000000) ? 1:0);
+ put8int (cts, 0); /* unknown */
+ put8int (cts, 0); /* unknown */
+ put32_n0 (cts, 14); /* unknown */
+ }
+ break;
+ case MHOD_ID_SPLRULES:
+ g_return_if_fail (data);
+ {
+ SPLRules *splrs = data;
+ gulong header_seek = cts->pos; /* needed to fix length */
+ GList *gl;
+ gint numrules = g_list_length (splrs->rules);
+
+ put_data (cts, "mhod", 4); /* header */
+ put32lint (cts, 24); /* size of header */
+ put32lint (cts, -1); /* total length, fix later */
+ put32lint (cts, type); /* type of the entry */
+ put32_n0 (cts, 2); /* unknown */
+ /* end of header, start of data */
+ /* For some reason this is the only part of the iTunesDB
+ that uses big endian */
+ put_data (cts, "SLst", 4); /* header */
+ put32bint (cts, splrs->unk004); /* unknown */
+ put32bint (cts, numrules);
+ put32bint (cts, splrs->match_operator);
+ put32_n0 (cts, 30); /* unknown */
+ /* end of header, now follow the rules */
+ for (gl=splrs->rules; gl; gl=gl->next)
+ {
+ SPLRule *splr = gl->data;
+ gint ft;
+ g_return_if_fail (splr);
+ ft = itdb_splr_get_field_type (splr);
+/* printf ("%p: field: %d ft: %d\n", splr, splr->field, ft);*/
+ itdb_splr_validate (splr);
+ put32bint (cts, splr->field);
+ put32bint (cts, splr->action);
+ put32_n0 (cts, 11); /* unknown */
+ if (ft == splft_string)
+ { /* write string-type rule */
+ gunichar2 *entry_utf16 =
+ g_utf8_to_utf16 (splr->string, -1,NULL,NULL,NULL);
+ gint len = utf16_strlen (entry_utf16);
+ fixup_big_utf16 (entry_utf16);
+ put32bint (cts, 2*len); /* length of string */
+ put_data (cts, (gchar *)entry_utf16, 2*len);
+ g_free (entry_utf16);
+ }
+ else
+ { /* write non-string-type rule */
+ put32bint (cts, 0x44); /* length of data */
+ /* data */
+ put64bint (cts, splr->fromvalue);
+ put64bint (cts, splr->fromdate);
+ put64bint (cts, splr->fromunits);
+ put64bint (cts, splr->tovalue);
+ put64bint (cts, splr->todate);
+ put64bint (cts, splr->tounits);
+ put32bint (cts, splr->unk052);
+ put32bint (cts, splr->unk056);
+ put32bint (cts, splr->unk060);
+ put32bint (cts, splr->unk064);
+ put32bint (cts, splr->unk068);
+ }
+ }
+ /* insert length of mhod junk */
+ fix_header (cts, header_seek);
+ }
+ break;
+ case MHOD_ID_MHYP:
+ g_warning (_("Cannot write mhod of type %d\n"), type);
+ break;
+ }
+}
+
+
+/* Write out the mhlp header. Size will be written later */
+static void mk_mhlp (FExport *fexp)
+{
+ WContents *cts;
+
+ g_return_if_fail (fexp);
+ g_return_if_fail (fexp->itunesdb);
+
+ cts = fexp->itunesdb;
+
+ put_data (cts, "mhlp", 4); /* header */
+ put32lint (cts, 92); /* size of header */
+ /* playlists on iPod (including main!) */
+ put32lint (cts, g_list_length (fexp->itdb->playlists));
+ put32_n0 (cts, 20); /* dummy space */
+}
+
+
+/* Write out the long MHOD_ID_PLAYLIST mhod header.
+ This seems to be an itunespref thing.. dunno know this
+ but if we set everything to 0, itunes doesn't show any data
+ even if you drag an mp3 to your ipod: nothing is shown, but itunes
+ will copy the file!
+ .. so we create a hardcoded-pref.. this will change in future
+ Seems to be a Preferences mhod, every PL has such a thing
+ FIXME !!! */
+static void mk_long_mhod_id_playlist (FExport *fexp, Itdb_Playlist *pl)
+{
+ WContents *cts;
+
+ g_return_if_fail (fexp);
+ g_return_if_fail (fexp->itunesdb);
+ g_return_if_fail (pl);
+
+ cts = fexp->itunesdb;
+
+ put_data (cts, "mhod", 4); /* header */
+ put32lint (cts, 0x18); /* size of header ? */
+ put32lint (cts, 0x0288); /* size of header + body */
+ put32lint (cts, MHOD_ID_PLAYLIST); /* type of the entry */
+ put32_n0 (cts, 6);
+ put32lint (cts, 0x010084); /* ? */
+ put32lint (cts, 0x05); /* ? */
+ put32lint (cts, 0x09); /* ? */
+ put32lint (cts, 0x03); /* ? */
+ put32lint (cts, 0x120001); /* ? */
+ put32_n0 (cts, 3);
+ put32lint (cts, 0xc80002); /* ? */
+ put32_n0 (cts, 3);
+ put32lint (cts, 0x3c000d); /* ? */
+ put32_n0 (cts, 3);
+ put32lint (cts, 0x7d0004); /* ? */
+ put32_n0 (cts, 3);
+ put32lint (cts, 0x7d0003); /* ? */
+ put32_n0 (cts, 3);
+ put32lint (cts, 0x640008); /* ? */
+ put32_n0 (cts, 3);
+ put32lint (cts, 0x640017); /* ? */
+ put32lint (cts, 0x01); /* bool? (visible? / colums?) */
+ put32_n0 (cts, 2);
+ put32lint (cts, 0x500014); /* ? */
+ put32lint (cts, 0x01); /* bool? (visible?) */
+ put32_n0 (cts, 2);
+ put32lint (cts, 0x7d0015); /* ? */
+ put32lint (cts, 0x01); /* bool? (visible?) */
+ put32_n0 (cts, 114);
+}
+
+
+
+/* Header for new PL item */
+/* @pos: position in playlist */
+static void mk_mhip (FExport *fexp, guint32 pos, guint32 id)
+{
+ WContents *cts;
+
+ g_return_if_fail (fexp);
+ g_return_if_fail (fexp->itunesdb);
+
+ cts = fexp->itunesdb;
+
+ put_data (cts, "mhip", 4);
+ put32lint (cts, 76); /* 4 */
+ put32lint (cts, -1); /* fill in later */ /* 8 */
+ put32lint (cts, 1); /* number of children */ /* 12 */
+ put32lint (cts, 0); /* unknown */ /* 16 */
+ put32lint (cts, 0); /* unknown */ /* 20 */
+ put32lint (cts, id); /* id */ /* 24 */
+ put32_n0 (cts, 12); /* 28 */
+}
+
+
+/* Write first mhsd hunk. Return FALSE in case of error and set
+ * fexp->error */
+static gboolean write_mhsd_one(FExport *fexp)
+{
+ GList *gl;
+ gulong mhsd_seek;
+ WContents *cts;
+
+ g_return_val_if_fail (fexp, FALSE);
+ g_return_val_if_fail (fexp->itdb, FALSE);
+ g_return_val_if_fail (fexp->itunesdb, FALSE);
+
+ cts = fexp->itunesdb;
+
+ mhsd_seek = cts->pos; /* get position of mhsd header */
+ mk_mhsd (fexp, 1); /* write header: type 1: tracks */
+ /* write header with nr. of tracks */
+ mk_mhlt (fexp, g_list_length (fexp->itdb->tracks));
+ for (gl=fexp->itdb->tracks; gl; gl=gl->next) /* Write each track */
+ {
+ Itdb_Track *track = gl->data;
+ guint32 mhod_num = 0;
+ gulong mhit_seek = cts->pos;
+ if (!track)
+ {
+ g_set_error (&fexp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_ITDB_CORRUPT,
+ _("Database in memory corrupt (track pointer == NULL). Aborting export."));
+ return FALSE;
+ }
+ mk_mhit (cts, track);
+ if (track->title && *track->title)
+ {
+ mk_mhod (cts, MHOD_ID_TITLE, track->title);
+ ++mhod_num;
+ }
+ if (track->ipod_path && *track->ipod_path)
+ {
+ mk_mhod (cts, MHOD_ID_PATH, track->ipod_path);
+ ++mhod_num;
+ }
+ if (track->album && *track->album)
+ {
+ mk_mhod (cts, MHOD_ID_ALBUM, track->album);
+ ++mhod_num;
+ }
+ if (track->artist && *track->artist)
+ {
+ mk_mhod (cts, MHOD_ID_ARTIST, track->artist);
+ ++mhod_num;
+ }
+ if (track->genre && *track->genre)
+ {
+ mk_mhod (cts, MHOD_ID_GENRE, track->genre);
+ ++mhod_num;
+ }
+ if (track->fdesc && *track->fdesc)
+ {
+ mk_mhod (cts, MHOD_ID_FDESC, track->fdesc);
+ ++mhod_num;
+ }
+ if (track->comment && *track->comment)
+ {
+ mk_mhod (cts, MHOD_ID_COMMENT, track->comment);
+ ++mhod_num;
+ }
+ if (track->composer && *track->composer)
+ {
+ mk_mhod (cts, MHOD_ID_COMPOSER, track->composer);
+ ++mhod_num;
+ }
+ if (track->grouping && *track->grouping)
+ {
+ mk_mhod (cts, MHOD_ID_GROUPING, track->grouping);
+ ++mhod_num;
+ }
+ /* Fill in the missing items of the mhit header */
+ fix_mhit (cts, mhit_seek, mhod_num);
+ }
+ fix_header (cts, mhsd_seek);
+ return TRUE;
+}
+
+/* corresponds to mk_mhyp */
+/* Return FALSE in case of error and set fexp->error */
+static gboolean write_playlist(FExport *fexp, Itdb_Playlist *pl)
+{
+ GList *gl;
+ gulong mhyp_seek, mhip_seek;
+ guint32 i;
+ WContents *cts;
+
+ g_return_val_if_fail (fexp, FALSE);
+ g_return_val_if_fail (fexp->itdb, FALSE);
+ g_return_val_if_fail (fexp->itunesdb, FALSE);
+ g_return_val_if_fail (pl, FALSE);
+
+ cts = fexp->itunesdb;
+ mhyp_seek = cts->pos;
+
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "Playlist: %s (%d tracks)\n", pl->name, g_list_length (pl->members));
+#endif
+
+ put_data (cts, "mhyp", 4); /* header */
+ put32lint (cts, 108); /* length */
+ put32lint (cts, -1); /* size -> later */
+ if (pl->is_spl)
+ put32lint (cts, 4); /* nr of mhods */
+ else
+ put32lint (cts, 2); /* nr of mhods */
+ /* number of tracks in plist */
+ put32lint (cts, g_list_length (pl->members));
+ put32lint (cts, pl->type); /* 1 = main, 0 = visible */
+ put32lint (cts, 0); /* some timestamp */
+ put64lint (cts, pl->id); /* 64 bit ID */
+ put32lint (cts, pl->unk036);
+ put32lint (cts, pl->unk040);
+ put32lint (cts, pl->unk044);
+ put32_n0 (cts, 15); /* ? */
+
+ mk_mhod (cts, MHOD_ID_TITLE, pl->name);
+ mk_long_mhod_id_playlist (fexp, pl);
+
+ if (pl->is_spl)
+ { /* write the smart rules */
+ mk_mhod (cts, MHOD_ID_SPLPREF, &pl->splpref);
+ mk_mhod (cts, MHOD_ID_SPLRULES, &pl->splrules);
+ }
+
+ /* write hard-coded tracks */
+ i=0;
+ for (gl=pl->members; gl; gl=gl->next)
+ {
+ Itdb_Track *track = gl->data;
+ if (!track)
+ {
+ g_set_error (&fexp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_ITDB_CORRUPT,
+ _("Database in memory corrupt (track pointer == NULL). Aborting export."));
+ return FALSE;
+ }
+ mhip_seek = cts->pos;
+ mk_mhip (fexp, i, track->id);
+ mk_mhod (cts, MHOD_ID_PLAYLIST, (void *)i);
+ /* note: with iTunes 4.9 the mhod is counted as a child to
+ mhip, so we fill have put the total length of the mhip and
+ mhod into the mhip header */
+ fix_header (cts, mhip_seek);
+ ++i;
+ }
+ fix_header (cts, mhyp_seek);
+ return TRUE;
+}
+
+
+
+/* Expects the master playlist to be the first in the list */
+/* Return FALSE in case of error and set fexp->error */
+static gboolean write_mhsd_two(FExport *fexp)
+{
+ GList *gl;
+ glong mhsd_seek;
+ WContents *cts;
+
+ g_return_val_if_fail (fexp, FALSE);
+ g_return_val_if_fail (fexp->itdb, FALSE);
+ g_return_val_if_fail (fexp->itunesdb, FALSE);
+
+ cts = fexp->itunesdb;
+ mhsd_seek = cts->pos; /* get position of mhsd header */
+ mk_mhsd (fexp, 2); /* write header: type 2: playlists */
+ /* write header with nr. of playlists */
+ mk_mhlp (fexp);
+ for(gl=fexp->itdb->playlists; gl; gl=gl->next)
+ {
+ Itdb_Playlist *pl = gl->data;
+ if (!pl)
+ {
+ g_set_error (&fexp->error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_ITDB_CORRUPT,
+ _("Database in memory corrupt (playlist pointer == NULL). Aborting export."));
+ return FALSE;
+ }
+ write_playlist (fexp, pl);
+ if (fexp->error) return FALSE;
+ }
+ fix_header (cts, mhsd_seek);
+ return TRUE;
+}
+
+
+/* create a WContents structure */
+static WContents *wcontents_new (const gchar *filename)
+{
+ WContents *cts;
+
+ g_return_val_if_fail (filename, NULL);
+
+ cts = g_new0 (WContents, 1);
+ cts->filename = g_strdup (filename);
+
+ return cts;
+}
+
+
+/* write the contents of WContents. Return FALSE on error and set
+ * cts->error accordingly. */
+static gboolean wcontents_write (WContents *cts)
+{
+ int fd;
+
+ g_return_val_if_fail (cts, FALSE);
+ g_return_val_if_fail (cts->filename, FALSE);
+
+ fd = creat (cts->filename, S_IRWXU|S_IRWXG|S_IRWXO);
+
+ if (fd == -1)
+ {
+ cts->error = g_error_new (G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Opening of '%s' for writing failed."),
+ cts->filename);
+ return FALSE;
+ }
+ if (cts->contents && cts->pos)
+ {
+ ssize_t written = write (fd, cts->contents, cts->pos);
+ if (written == -1)
+ {
+ cts->error = g_error_new (G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Writing to '%s' failed."),
+ cts->filename);
+ close (fd);
+ return FALSE;
+ }
+ }
+ fd = close (fd);
+ if (fd == -1)
+ {
+ cts->error = g_error_new (G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Writing to '%s' failed (%s)."),
+ cts->filename, g_strerror (errno));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Free memory associated with WContents @cts */
+static void wcontents_free (WContents *cts)
+{
+ if (cts)
+ {
+ g_free (cts->filename);
+ g_free (cts->contents);
+ /* must not g_error_free (cts->error) because the error was
+ propagated -> might free the error twice */
+ g_free (cts);
+ }
+}
+
+
+/* reassign the iPod IDs and make sure the itdb->tracks are in the
+ same order as the mpl */
+static void reassign_ids (Itdb_iTunesDB *itdb)
+{
+ guint32 id = 52;
+ GList *gl;
+ Itdb_Playlist *mpl;
+
+ g_return_if_fail (itdb);
+
+ /* copy mpl->members to itdb->tracks to make sure they are in the
+ same order (otherwise On-The-Go Playlists will not show the
+ correct content) */
+ mpl = itdb_playlist_mpl (itdb);
+ g_return_if_fail (mpl);
+ g_return_if_fail (g_list_length (mpl->members) == g_list_length (itdb->tracks));
+ g_list_free (itdb->tracks);
+ itdb->tracks = g_list_copy (mpl->members);
+
+ /* assign unique IDs */
+ for (gl=itdb->tracks; gl; gl=gl->next)
+ {
+ Itdb_Track *track = gl->data;
+ g_return_if_fail (track);
+ track->id = id++;
+ }
+}
+
+
+
+/* Do the actual writing to the iTunesDB */
+/* If @filename==NULL, itdb->filename is tried */
+gboolean itdb_write_file (Itdb_iTunesDB *itdb, const gchar *filename,
+ GError **error)
+{
+ FExport *fexp;
+ gulong mhbd_seek = 0;
+ WContents *cts;
+ gboolean result = TRUE;;
+
+ g_return_val_if_fail (itdb, FALSE);
+ g_return_val_if_fail (filename || itdb->filename, FALSE);
+
+ if (!filename) filename = itdb->filename;
+
+ reassign_ids (itdb);
+
+ fexp = g_new0 (FExport, 1);
+ fexp->itdb = itdb;
+ fexp->itunesdb = wcontents_new (filename);
+ cts = fexp->itunesdb;
+
+ mk_mhbd (fexp);
+ if (write_mhsd_one(fexp))
+ { /* write playlists mhsd */
+ if (write_mhsd_two(fexp))
+ {
+ fix_header (cts, mhbd_seek);
+ }
+ }
+ if (!fexp->error)
+ {
+ if (!wcontents_write (cts))
+ g_propagate_error (&fexp->error, cts->error);
+ }
+ if (fexp->error)
+ {
+ g_propagate_error (error, fexp->error);
+ result = FALSE;
+ }
+ wcontents_free (cts);
+ g_free (fexp);
+ if (result == TRUE)
+ {
+ gchar *fn = g_strdup (filename);
+ g_free (itdb->filename);
+ itdb->filename = fn;
+ }
+ /* make sure all buffers are flushed as some people tend to
+ disconnect as soon as gtkpod returns */
+ sync ();
+
+ return result;
+}
+
+/* Write out an iTunesDB.
+
+ First reassigns unique IDs to all tracks.
+
+ An existing "Play Counts" file is renamed to "Play Counts.bak" if
+ the export was successful.
+
+ An existing "OTGPlaylistInfo" file is removed if the export was
+ successful.
+
+ Returns TRUE on success, FALSE on error, in which case @error is
+ set accordingly.
+
+ @mp must point to the mount point of the iPod, e.g. "/mnt/ipod" and
+ be in local encoding. If mp==NULL, itdb->mountpoint is tried. */
+gboolean itdb_write (Itdb_iTunesDB *itdb, const gchar *mp, GError **error)
+{
+ gchar *itunes_filename, *itunes_path;
+ const gchar *db[] = {"iPod_Control","iTunes",NULL};
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (itdb, FALSE);
+ g_return_val_if_fail (mp || itdb->mountpoint, FALSE);
+
+ if (!mp) mp = itdb->mountpoint;
+
+ itunes_path = itdb_resolve_path (mp, db);
+
+ if(!itunes_path)
+ {
+ gchar *str = g_build_filename (mp, db[0], db[1], db[2], NULL);
+ g_set_error (error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_NOTFOUND,
+ _("Path not found: '%s'."),
+ str);
+ g_free (str);
+ return FALSE;
+ }
+
+ itunes_filename = g_build_filename (itunes_path, "iTunesDB", NULL);
+
+ result = itdb_write_file (itdb, itunes_filename, error);
+
+ g_free(itunes_filename);
+ g_free(itunes_path);
+
+ if (result == TRUE)
+ result = itdb_rename_files (mp, error);
+
+ if (result == TRUE)
+ {
+ gchar *mnp = g_strdup (mp);
+ g_free (itdb->mountpoint);
+ itdb->mountpoint = mnp;
+ }
+
+ /* make sure all buffers are flushed as some people tend to
+ disconnect as soon as gtkpod returns */
+ sync ();
+
+ return result;
+}
+
+
+/* from here on we have the functions for writing the iTunesDB */
+/* -------------------------------------------------------------------- */
+/* up to here we had the functions for writing the iTunesSD */
+
+/*
+| Copyright (C) 2005 Jorg Schuler <jcsjcs at users.sourceforge.net>
+| Part of the gtkpod project.
+|
+| Based on itunessd.c written by Steve Wahl for gtkpod-0.88:
+|
+| Copyright 2005 Steve Wahl <steve at pro-ns dot net>
+|
+| This file contains routines to create the iTunesSD file, as
+| used by the ipod shuffle.
+|
+| Like itunesdb.c, it is derived from the perl script "mktunes.pl"
+| (part of the gnupod-tools collection) written by Adrian
+| Ulrich <pab at blinkenlights.ch>.
+|
+| Small(?) portions derived from itunesdb.c, so Jorg Schuler probably
+| has some copyright ownership in this file as well.
+|
+| The code contained in this file is free software; you can redistribute
+| it and/or modify it under the terms of the GNU Lesser General Public
+| License as published by the Free Software Foundation; either version
+| 2.1 of the License, or (at your option) any later version.
+|
+| This file is distributed in the hope that it will be useful,
+| but WITHOUT ANY WARRANTY; without even the implied warranty of
+| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+| Lesser General Public License for more details.
+|
+| You should have received a copy of the GNU Lesser General Public
+| License along with this code; if not, write to the Free Software
+| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+|
+| iTunes and iPod are trademarks of Apple
+|
+| This product is not supported/written/published by Apple!
+|
+*/
+
+/* notes:
+
+ All software currently seems to write iTunesDB as well as iTunesSD
+ on the iPod shuffle. I assume that reading from the iTunesSD file
+ is not necessary. The iTunesStats file is different, but I leave
+ that for another day.
+
+ The iTunesSD file format is as follows (taken from WikiPodLinux, feb
+ '05):
+
+ Offset Field Bytes Value
+ (hex) (dec)
+
+ iTunesSD header (occurs once, at beginning of file):
+
+ 00 num_songs 3 number of song entries in the file
+
+ 03 unknown 3 always(?) 0x010600
+ 06 header size 3 size of the header (0x12, 18 bytes)
+ 09 unknown 3 possibly zero padding
+
+ iTunesSD song entry format (occurs once for each song)
+
+ 000 size of entry 3 always(?) 0x00022e (558 bytes)
+ 003 unk1 3 unknown, always(?) 0x5aa501
+ 006 starttime 3 Start Time, in 256 ms increments
+ e.g. 60s = 0xea (234 dec)
+ 009 unk2 3 unknown (always 0?)
+ 00C unk3 3 unknown, some relationship to starttime
+ 00F stoptime 3 Stop Time, also in 256 ms increments.
+ Zero means play to end of file.
+ 012 unk4 3 Unknown.
+ 015 unk5 3 Unknown, but associated with stoptime?
+ 018 volume 3 Volume - ranges from 0x00 (-100%) to 0x64
+ (0%) to 0xc8 (100%)
+ 01B file_type 3 0x01 = MP3, 0x02 = AAC, 0x04=WAV
+ 01E unk6 3 unknown (always 0x200?)
+ 021 filename 522 filename of the song, padded at the end
+ with 0's. Note: forward slashes are used
+ here, not colons like in the iTunesDB --
+ for example,
+ "/iPod_Control/Music/F00/Song.mp3"
+ 22B shuffleflag 1 If this value is 0x00, the song will be
+ skipped while the player is in shuffle
+ mode. Any other value will allow it to be
+ played in both normal and shuffle modes.
+ iTunes 4.7.1 sets this to 0 for audio books.
+ 22C bookmarkflag 1 If this flag is 0x00, the song will not be
+ bookmarkable (i.e. its playback position
+ won't be saved when switching to a different
+ song). Any other value wil make it
+ Bookmarkable. Unlike hard drive based iPods,
+ all songs can be marked as bookmarkable,
+ not just .m4b and .aa
+ 22D unknownflag 1 unknown, always? 0x00.
+
+All integers in the iTunesSD file are in BIG endian form...
+
+*/
+
+
+/* Write out an iTunesSD for the Shuffle.
+
+ First reassigns unique IDs to all tracks.
+
+ An existing "Play Counts" file is renamed to "Play Counts.bak" if
+ the export was successful.
+
+ An existing "OTGPlaylistInfo" file is removed if the export was
+ successful.
+
+ Returns TRUE on success, FALSE on error, in which case @error is
+ set accordingly.
+
+ @mp must point to the mount point of the iPod, e.g. "/mnt/ipod" and
+ be in local encoding. If mp==NULL, itdb->mountpoint is tried. */
+
+
+gboolean itdb_shuffle_write (Itdb_iTunesDB *itdb,
+ const gchar *mp, GError **error)
+{
+ gchar *itunes_filename, *itunes_path;
+ const gchar *db[] = {"iPod_Control","iTunes",NULL};
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (itdb, FALSE);
+ g_return_val_if_fail (mp || itdb->mountpoint, FALSE);
+
+ if (!mp) mp = itdb->mountpoint;
+
+ itunes_path = itdb_resolve_path (mp, db);
+
+ if(!itunes_path)
+ {
+ gchar *str = g_build_filename (mp, db[0], db[1], db[2], NULL);
+ g_set_error (error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_NOTFOUND,
+ _("Path not found: '%s'."),
+ str);
+ g_free (str);
+ return FALSE;
+ }
+
+ itunes_filename = g_build_filename (itunes_path, "iTunesSD", NULL);
+
+ result = itdb_shuffle_write_file (itdb, itunes_filename, error);
+
+ g_free(itunes_filename);
+ g_free(itunes_path);
+
+ if (result == TRUE)
+ result = itdb_rename_files (mp, error);
+
+ if (result == TRUE)
+ {
+ gchar *mnp = g_strdup (mp);
+ g_free (itdb->mountpoint);
+ itdb->mountpoint = mnp;
+ }
+
+ /* make sure all buffers are flushed as some people tend to
+ disconnect as soon as gtkpod returns */
+ sync ();
+
+ return result;
+}
+
+
+/* Do the actual writing to the iTunesSD */
+/* If @filename cannot be NULL */
+gboolean itdb_shuffle_write_file (Itdb_iTunesDB *itdb,
+ const gchar *filename, GError **error)
+{
+ gboolean haystack (gchar *fdesc, gchar **desclist)
+ {
+ gchar **dlp;
+ if (!fdesc || !desclist) return FALSE;
+ for (dlp=desclist; *dlp; ++dlp)
+ {
+ if (strstr (fdesc, *dlp)) return TRUE;
+ }
+ return FALSE;
+ }
+
+ FExport *fexp;
+ GList *gl;
+ WContents *cts;
+ gboolean result = TRUE;;
+
+ g_return_val_if_fail (itdb, FALSE);
+ g_return_val_if_fail (filename, FALSE);
+
+ reassign_ids (itdb);
+
+ fexp = g_new0 (FExport, 1);
+ fexp->itdb = itdb;
+ fexp->itunesdb = wcontents_new (filename);
+ cts = fexp->itunesdb;
+
+ put24bint (cts, itdb_tracks_number (itdb));
+ put24bint (cts, 0x010600);
+ put24bint (cts, 0x12); /* size of header */
+ put24bint (cts, 0x0); /* padding? */
+ put24bint (cts, 0x0);
+ put24bint (cts, 0x0);
+
+ for (gl=itdb->tracks; gl; gl=gl->next)
+ {
+ Itdb_Track *tr = gl->data;
+ gchar *path;
+ gunichar2 *path_utf16;
+ guint32 pathlen;
+ gchar *mp3_desc[] = {"MPEG", "MP3", "mpeg", "mp3", NULL};
+ gchar *mp4_desc[] = {"AAC", "MP4", "aac", "mp4", NULL};
+ gchar *wav_desc[] = {"WAV", "wav", NULL};
+
+ g_return_val_if_fail (tr, FALSE);
+
+ put24bint (cts, 0x00022e);
+ put24bint (cts, 0x5aa501);
+ /* starttime is in 256 ms incr. for shuffle */
+ put24bint (cts, tr->starttime / 256);
+ put24bint (cts, 0);
+ put24bint (cts, 0);
+ put24bint (cts, tr->stoptime / 256);
+ put24bint (cts, 0);
+ put24bint (cts, 0);
+ /* track->volume ranges from -255 to +255 */
+ /* we want 0 - 200 */
+ put24bint (cts, ((tr->volume + 255) * 201) / 511);
+
+ /* The next one should be 0x01 for MP3,
+ ** 0x02 for AAC, and 0x04 for WAV, but I can't find
+ ** a suitable indicator within the track structure? */
+ /* JCS: let's do heuristic on tr->fdesc which would contain
+ "MPEG audio file", "AAC audio file", "Protected AAC audio
+ file", "AAC audio book file", "WAV audio file" (or similar
+ if not written by gtkpod */
+
+ if (haystack (tr->fdesc, mp3_desc))
+ put24bint (cts, 0x01);
+ else if (haystack (tr->fdesc, mp4_desc))
+ put24bint (cts, 0x02);
+ else if (haystack (tr->fdesc, wav_desc))
+ put24bint (cts, 0x04);
+ else
+ put24bint (cts, 0x01); /* default to mp3 */
+
+ put24bint (cts, 0x200);
+
+ /* shuffle uses forward slash separator, not colon */
+ path = g_strdup (tr->ipod_path);
+ itdb_filename_ipod2fs (path);
+ path_utf16 = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);
+ pathlen = utf16_strlen (path_utf16);
+ if (pathlen > 261) pathlen = 261;
+ fixup_little_utf16 (path_utf16);
+ put_data (cts, (gchar *)path_utf16, 2*pathlen);
+ /* pad to 522 bytes */
+ put16_n0 (cts, 261-pathlen);
+ g_free(path);
+ g_free(path_utf16);
+
+ /* XXX FIXME: should depend on something, not hardcoded */
+ put8int (cts, 0x1); /* song used in shuffle mode */
+ put8int (cts, 0); /* song will not be bookmarkable */
+ put8int (cts, 0);
+ }
+ if (!fexp->error)
+ {
+ if (!wcontents_write (cts))
+ g_propagate_error (&fexp->error, cts->error);
+ }
+ if (fexp->error)
+ {
+ g_propagate_error (error, fexp->error);
+ result = FALSE;
+ }
+ wcontents_free (cts);
+ g_free (fexp);
+
+ /* make sure all buffers are flushed as some people tend to
+ disconnect as soon as gtkpod returns */
+ sync ();
+
+ return result;
+}
+
+
+
+
+
+
+
+
+
+
+
+/*------------------------------------------------------------------*\
+ * *
+ * Other file/filename stuff *
+ * *
+\*------------------------------------------------------------------*/
+
+
+/* (Renames/removes some files on the iPod (Playcounts, OTG
+ semaphore). May have to be called if you write the iTunesDB not
+ directly to the iPod but to some other location and then manually
+ copy the file from there to the iPod. */
+/* Returns FALSE on error and sets @error accordingly */
+gboolean itdb_rename_files (const gchar *mp, GError **error)
+{
+ const gchar *db_itd[] = {"iPod_Control", "iTunes", NULL};
+ const gchar *db_plc_o[] = {"Play Counts", NULL};
+ const gchar *db_otg[] = {"OTGPlaylistInfo", NULL};
+ gchar *itunesdir;
+ gchar *plcname_o;
+ gchar *plcname_n;
+ gchar *otgname;
+ gboolean result = TRUE;
+
+ itunesdir = itdb_resolve_path (mp, db_itd);
+ if(!itunesdir)
+ {
+ gchar *str = g_build_filename (mp, db_itd[0],
+ db_itd[1], db_itd[2], NULL);
+ g_set_error (error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_NOTFOUND,
+ _("Path not found: '%s'."),
+ str);
+ g_free (str);
+ return FALSE;
+ }
+
+
+ plcname_o = itdb_resolve_path (itunesdir, db_plc_o);
+ plcname_n = g_build_filename (itunesdir,
+ "Play Counts.bak", NULL);
+ otgname = itdb_resolve_path (itunesdir, db_otg);
+
+ /* rename "Play Counts" to "Play Counts.bak" */
+ if (plcname_o)
+ {
+ if (rename (plcname_o, plcname_n) == -1)
+ { /* an error occured */
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Error renaming '%s' to '%s' (%s)."),
+ plcname_o, plcname_n, g_strerror (errno));
+ result = FALSE;
+ }
+ }
+
+ /* remove "OTGPlaylistInfo" (the iPod will remove the remaining
+ * files */
+ if (otgname)
+ {
+ if (unlink (otgname) == -1)
+ {
+ if (error && !*error)
+ { /* don't overwrite previous error */
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Error removing '%s' (%s)."),
+ otgname, g_strerror (errno));
+ }
+ result = FALSE;
+ }
+ }
+
+ g_free (plcname_o);
+ g_free (plcname_n);
+ g_free (otgname);
+ g_free (itunesdir);
+
+ return result;
+}
+
+
+/* Convert string from casual PC file name to iPod iTunesDB format
+ * using ':' instead of slashes
+ */
+void itdb_filename_fs2ipod (gchar *ipod_file)
+{
+ g_strdelimit (ipod_file, G_DIR_SEPARATOR_S, ':');
+}
+
+/* Convert string from iPod iTunesDB format to casual PC file name
+ * using slashes instead of ':'
+ */
+void itdb_filename_ipod2fs (gchar *ipod_file)
+{
+ g_strdelimit (ipod_file, ":", G_DIR_SEPARATOR);
+}
+
+
+
+/* Copy one track to the iPod. The PC filename is @filename
+ and is taken literally.
+ @path is the mountpoint of the iPod (in local encoding).
+
+ If @track->transferred is set to TRUE, nothing is done. Upon
+ successful transfer @track->transferred is set to TRUE.
+
+ For storage, the directories "f00 ... f19" will be
+ cycled through.
+
+ The filename is constructed as "gtkpod"<random number> and copied
+ to @track->ipod_path. If this file already exists, <random number>
+ is adjusted until an unused filename is found.
+
+ If @track->ipod_path is already set, this one will be used
+ instead. If a file with this name already exists, it will be
+ overwritten. */
+gboolean itdb_cp_track_to_ipod (const gchar *mp, Itdb_Track *track,
+ gchar *filename, GError **error)
+{
+ static gint dir_num = -1;
+ gchar *track_db_path, *ipod_fullfile;
+ gboolean success;
+ gint mplen = 0;
+
+ g_return_val_if_fail (mp, FALSE);
+ g_return_val_if_fail (track, FALSE);
+ g_return_val_if_fail (filename, FALSE);
+
+ if(track->transferred) return TRUE; /* nothing to do */
+
+ /* If track->ipod_path exists, we use that one instead. */
+ ipod_fullfile = itdb_filename_on_ipod (mp, track);
+
+ if (!ipod_fullfile)
+ {
+ gchar *dest_components[] = {"iPod_Control", "Music",
+ NULL, NULL, NULL};
+ gchar *parent_dir_filename;
+ gchar *original_suffix;
+ gchar dir_num_str[5];
+ gint32 oops = 0;
+ gint32 rand = g_random_int_range (0, 899999); /* 0 to 900000 */
+
+ if (dir_num == -1) dir_num = g_random_int_range (0, 20);
+ else dir_num = (dir_num + 1) % 20;
+
+ g_snprintf (dir_num_str, 5, "F%02d", dir_num);
+ dest_components[2] = dir_num_str;
+
+ parent_dir_filename =
+ itdb_resolve_path (mp, (const gchar **)dest_components);
+
+ if(parent_dir_filename == NULL)
+ {
+ /* Can't find the F%02d directory */
+ gchar *str = g_build_filename (mp, dest_components[0],
+ dest_components[1],
+ dest_components[2],
+ dest_components[3], NULL);
+ g_set_error (error,
+ ITDB_FILE_ERROR,
+ ITDB_FILE_ERROR_NOTFOUND,
+ _("Path not found: '%s'."),
+ str);
+ g_free (str);
+ return FALSE;
+ }
+
+ /* we need the original suffix of pcfile to construct a correct ipod
+ filename */
+ original_suffix = strrchr (filename, '.');
+ /* If there is no ".mp3", ".m4a" etc, set original_suffix to empty
+ string. Note: the iPod will most certainly ignore this file... */
+ if (!original_suffix) original_suffix = "";
+
+ do
+ { /* we need to loop until we find an unused filename */
+ dest_components[3] =
+ g_strdup_printf("gtkpod%06d%s",
+ rand + oops, original_suffix);
+ ipod_fullfile = itdb_resolve_path (
+ parent_dir_filename,
+ (const gchar **)&dest_components[3]);
+ if(ipod_fullfile)
+ { /* already exists -- try next */
+ g_free(ipod_fullfile);
+ ipod_fullfile = NULL;
+ }
+ else
+ { /* found unused file -- build filename */
+ ipod_fullfile = g_build_filename (parent_dir_filename,
+ dest_components[3], NULL);
+ }
+ g_free (dest_components[3]);
+ ++oops;
+ } while (!ipod_fullfile);
+ g_free(parent_dir_filename);
+ }
+ /* now extract filepath for track->ipod_path from ipod_fullfile */
+ /* ipod_path must begin with a '/' */
+ mplen = strlen (mp); /* length of mountpoint in bytes */
+ if (ipod_fullfile[mplen] == G_DIR_SEPARATOR)
+ {
+ track_db_path = g_strdup (&ipod_fullfile[mplen]);
+ }
+ else
+ {
+ track_db_path = g_strdup_printf ("%c%s", G_DIR_SEPARATOR,
+ &ipod_fullfile[mplen]);
+ }
+ /* convert to iPod type */
+ itdb_filename_fs2ipod (track_db_path);
+
+/* printf ("ff: %s\ndb: %s\n", ipod_fullfile, track_db_path); */
+
+ success = itdb_cp (filename, ipod_fullfile, error);
+ if (success)
+ {
+ track->transferred = TRUE;
+ g_free (track->ipod_path);
+ track->ipod_path = g_strdup (track_db_path);
+ }
+
+ g_free (track_db_path);
+ g_free (ipod_fullfile);
+ return success;
+}
+
+
+/* Return the full iPod filename as stored in @track. Return value
+ must be g_free()d after use.
+ @mp: mount point of the iPod file system (in local encoding)
+ @track: track
+ Return value: full filename to @track on the iPod or NULL if no
+ filename is set in @track.
+
+ NOTE: NULL is returned when the file does not exist.
+
+ NOTE: this code works around a problem on some systems (see
+ itdb_resolve_path() ) and might return a filename with different
+ case than the original filename. Don't copy it back to @track
+ unless you must */
+gchar *itdb_filename_on_ipod (const gchar *mp, Itdb_Track *track)
+{
+ gchar *result = NULL;
+
+ g_return_val_if_fail (track, NULL);
+
+ if(track->ipod_path && *track->ipod_path)
+ {
+ gchar *buf = g_strdup (track->ipod_path);
+ itdb_filename_ipod2fs (buf);
+ result = g_build_filename (mp, buf, NULL);
+ g_free (buf);
+ if (!g_file_test (result, G_FILE_TEST_EXISTS))
+ {
+ gchar **components = g_strsplit (track->ipod_path,":",10);
+ g_free (result);
+ result = itdb_resolve_path (mp, (const gchar **)components);
+ g_strfreev (components);
+ }
+ }
+ return result;
+}
+
+
+/* Copy file "from_file" to "to_file".
+ Returns TRUE on success, FALSE otherwise */
+gboolean itdb_cp (const gchar *from_file, const gchar *to_file,
+ GError **error)
+{
+ gchar *data;
+ glong bread, bwrite;
+ FILE *file_in = NULL;
+ FILE *file_out = NULL;
+
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "Entered itunesdb_cp: '%s', '%s'\n", from_file, to_file);
+#endif
+
+ g_return_val_if_fail (from_file, FALSE);
+ g_return_val_if_fail (to_file, FALSE);
+
+ data = g_malloc (ITUNESDB_COPYBLK);
+
+ file_in = fopen (from_file, "r");
+ if (file_in == NULL)
+ {
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Error opening '%s' for reading (%s)."),
+ from_file, g_strerror (errno));
+ goto err_out;
+ }
+
+ file_out = fopen (to_file, "w");
+ if (file_out == NULL)
+ {
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Error opening '%s' for writing (%s)."),
+ to_file, g_strerror (errno));
+ goto err_out;
+ }
+
+ do {
+ bread = fread (data, 1, ITUNESDB_COPYBLK, file_in);
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "itunesdb_cp: read %ld bytes\n", bread);
+#endif
+ if (bread == 0)
+ {
+ if (feof (file_in) == 0)
+ { /* error -- not end of file! */
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Error while reading from '%s' (%s)."),
+ from_file, g_strerror (errno));
+ goto err_out;
+ }
+ }
+ else
+ {
+ bwrite = fwrite (data, 1, bread, file_out);
+#if ITUNESDB_DEBUG
+ fprintf(stderr, "itunesdb_cp: wrote %ld bytes\n", bwrite);
+#endif
+ if (bwrite != bread)
+ {
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Error while writing to '%s' (%s)."),
+ to_file, g_strerror (errno));
+ goto err_out;
+ }
+ }
+ } while (bread != 0);
+
+ if (fclose (file_in) != 0)
+ {
+ file_in = NULL;
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Error when closing '%s' (%s)."),
+ from_file, g_strerror (errno));
+ goto err_out;
+ }
+ if (fclose (file_out) != 0)
+ {
+ file_out = NULL;
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Error when closing '%s' (%s)."),
+ to_file, g_strerror (errno));
+ goto err_out;
+ }
+ g_free (data);
+ return TRUE;
+
+ err_out:
+ if (file_in) fclose (file_in);
+ if (file_out) fclose (file_out);
+ remove (to_file);
+ g_free (data);
+ return FALSE;
+}
+
+
+
+
+
+
+/*------------------------------------------------------------------*\
+ * *
+ * Timestamp stuff *
+ * *
+\*------------------------------------------------------------------*/
+
+guint64 itdb_time_get_mac_time (void)
+{
+ GTimeVal time;
+
+ g_get_current_time (&time);
+ return itdb_time_host_to_mac (time.tv_sec);
+}
+
+
+/* convert Macintosh timestamp to host system time stamp -- modify
+ * this function if necessary to port to host systems with different
+ * start of Epoch */
+/* A "0" time will not be converted */
+time_t itdb_time_mac_to_host (guint64 mactime)
+{
+ if (mactime != 0) return (time_t)(mactime - 2082844800);
+ else return (time_t)mactime;
+}
+
+
+/* convert host system timestamp to Macintosh time stamp -- modify
+ * this function if necessary to port to host systems with different
+ * start of Epoch */
+guint64 itdb_time_host_to_mac (time_t time)
+{
+ return (guint64)(((gint64)time) + 2082844800);
+}
diff --git a/src/itdb_playlist.c b/src/itdb_playlist.c
new file mode 100644
index 0000000..5c68c60
--- /dev/null
+++ b/src/itdb_playlist.c
@@ -0,0 +1,1290 @@
+/* Time-stamp: <2005-08-27 21:51:32 jcs>
+|
+| Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
+| Part of the gtkpod project.
+|
+| URL: http://www.gtkpod.org/
+| URL: http://gtkpod.sourceforge.net/
+|
+| The code contained in this file is free software; you can redistribute
+| it and/or modify it under the terms of the GNU Lesser General Public
+| License as published by the Free Software Foundation; either version
+| 2.1 of the License, or (at your option) any later version.
+|
+| This file is distributed in the hope that it will be useful,
+| but WITHOUT ANY WARRANTY; without even the implied warranty of
+| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+| Lesser General Public License for more details.
+|
+| You should have received a copy of the GNU Lesser General Public
+| License along with this code; if not, write to the Free Software
+| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+|
+| iTunes and iPod are trademarks of Apple
+|
+| This product is not supported/written/published by Apple!
+|
+| $Id$
+*/
+
+#include "itdb_private.h"
+#include <glib/gi18n-lib.h>
+#include <string.h>
+
+/* spl_action_known(), itb_splr_get_field_type(),
+ * itb_splr_get_action_type() are adapted from source provided by
+ * Samuel "Otto" Wood (sam dot wood at gmail dot com). These part can
+ * also be used under a FreeBSD license. You may also contact Samuel
+ * for a complete copy of his original C++-classes.
+ * */
+
+/* return TRUE if the smart playlist action @action is
+ known. Otherwise a warning is displayed and FALSE is returned. */
+gboolean itdb_spl_action_known (SPLAction action)
+{
+ gboolean result = FALSE;
+
+ switch (action)
+ {
+ case SPLACTION_IS_INT:
+ case SPLACTION_IS_GREATER_THAN:
+ case SPLACTION_IS_NOT_GREATER_THAN:
+ case SPLACTION_IS_LESS_THAN:
+ case SPLACTION_IS_NOT_LESS_THAN:
+ case SPLACTION_IS_IN_THE_RANGE:
+ case SPLACTION_IS_NOT_IN_THE_RANGE:
+ case SPLACTION_IS_IN_THE_LAST:
+ case SPLACTION_IS_STRING:
+ case SPLACTION_CONTAINS:
+ case SPLACTION_STARTS_WITH:
+ case SPLACTION_DOES_NOT_START_WITH:
+ case SPLACTION_ENDS_WITH:
+ case SPLACTION_DOES_NOT_END_WITH:
+ case SPLACTION_IS_NOT_INT:
+ case SPLACTION_IS_NOT_IN_THE_LAST:
+ case SPLACTION_IS_NOT:
+ case SPLACTION_DOES_NOT_CONTAIN:
+ result = TRUE;
+ }
+ if (result == FALSE)
+ { /* New action! */
+ g_warning (_("Unknown action (%d) in smart playlist will be ignored.\n"), action);
+ }
+ return result;
+}
+
+/* return the logic type (string, int, date...) of the action field */
+SPLFieldType itdb_splr_get_field_type (const SPLRule *splr)
+{
+ g_return_val_if_fail (splr != NULL, splft_unknown);
+
+ switch(splr->field)
+ {
+ case SPLFIELD_SONG_NAME:
+ case SPLFIELD_ALBUM:
+ case SPLFIELD_ARTIST:
+ case SPLFIELD_GENRE:
+ case SPLFIELD_KIND:
+ case SPLFIELD_COMMENT:
+ case SPLFIELD_COMPOSER:
+ case SPLFIELD_GROUPING:
+ return(splft_string);
+ case SPLFIELD_BITRATE:
+ case SPLFIELD_SAMPLE_RATE:
+ case SPLFIELD_YEAR:
+ case SPLFIELD_TRACKNUMBER:
+ case SPLFIELD_SIZE:
+ case SPLFIELD_PLAYCOUNT:
+ case SPLFIELD_DISC_NUMBER:
+ case SPLFIELD_BPM:
+ case SPLFIELD_RATING:
+ case SPLFIELD_TIME: /* time is the length of the track in
+ milliseconds */
+ return(splft_int);
+ case SPLFIELD_COMPILATION:
+ return(splft_boolean);
+ case SPLFIELD_DATE_MODIFIED:
+ case SPLFIELD_DATE_ADDED:
+ case SPLFIELD_LAST_PLAYED:
+ return(splft_date);
+ case SPLFIELD_PLAYLIST:
+ return(splft_playlist);
+ }
+ return(splft_unknown);
+}
+
+
+/* return the type (range, date, string...) of the action field */
+SPLActionType itdb_splr_get_action_type (const SPLRule *splr)
+{
+ SPLFieldType fieldType;
+
+ g_return_val_if_fail (splr != NULL, splft_unknown);
+
+ fieldType = itdb_splr_get_field_type (splr);
+
+ switch(fieldType)
+ {
+ case splft_string:
+ switch (splr->action)
+ {
+ case SPLACTION_IS_STRING:
+ case SPLACTION_IS_NOT:
+ case SPLACTION_CONTAINS:
+ case SPLACTION_DOES_NOT_CONTAIN:
+ case SPLACTION_STARTS_WITH:
+ case SPLACTION_DOES_NOT_START_WITH:
+ case SPLACTION_ENDS_WITH:
+ case SPLACTION_DOES_NOT_END_WITH:
+ return splat_string;
+ case SPLACTION_IS_NOT_IN_THE_RANGE:
+ case SPLACTION_IS_INT:
+ case SPLACTION_IS_NOT_INT:
+ case SPLACTION_IS_GREATER_THAN:
+ case SPLACTION_IS_NOT_GREATER_THAN:
+ case SPLACTION_IS_LESS_THAN:
+ case SPLACTION_IS_NOT_LESS_THAN:
+ case SPLACTION_IS_IN_THE_RANGE:
+ case SPLACTION_IS_IN_THE_LAST:
+ case SPLACTION_IS_NOT_IN_THE_LAST:
+ return splat_invalid;
+ default:
+ /* Unknown action type */
+ g_warning ("Unknown action type %d\n\n", splr->action);
+ return splat_unknown;
+ }
+ break;
+
+ case splft_int:
+ switch (splr->action)
+ {
+ case SPLACTION_IS_INT:
+ case SPLACTION_IS_NOT_INT:
+ case SPLACTION_IS_GREATER_THAN:
+ case SPLACTION_IS_NOT_GREATER_THAN:
+ case SPLACTION_IS_LESS_THAN:
+ case SPLACTION_IS_NOT_LESS_THAN:
+ return splat_int;
+ case SPLACTION_IS_NOT_IN_THE_RANGE:
+ case SPLACTION_IS_IN_THE_RANGE:
+ return splat_range_int;
+ case SPLACTION_IS_STRING:
+ case SPLACTION_CONTAINS:
+ case SPLACTION_STARTS_WITH:
+ case SPLACTION_DOES_NOT_START_WITH:
+ case SPLACTION_ENDS_WITH:
+ case SPLACTION_DOES_NOT_END_WITH:
+ case SPLACTION_IS_IN_THE_LAST:
+ case SPLACTION_IS_NOT_IN_THE_LAST:
+ case SPLACTION_IS_NOT:
+ case SPLACTION_DOES_NOT_CONTAIN:
+ return splat_invalid;
+ default:
+ /* Unknown action type */
+ g_warning ("Unknown action type %d\n\n", splr->action);
+ return splat_unknown;
+ }
+ break;
+
+ case splft_boolean:
+ return splat_none;
+
+ case splft_date:
+ switch (splr->action)
+ {
+ case SPLACTION_IS_INT:
+ case SPLACTION_IS_NOT_INT:
+ case SPLACTION_IS_GREATER_THAN:
+ case SPLACTION_IS_NOT_GREATER_THAN:
+ case SPLACTION_IS_LESS_THAN:
+ case SPLACTION_IS_NOT_LESS_THAN:
+ return splat_date;
+ case SPLACTION_IS_IN_THE_LAST:
+ case SPLACTION_IS_NOT_IN_THE_LAST:
+ return splat_inthelast;
+ case SPLACTION_IS_IN_THE_RANGE:
+ case SPLACTION_IS_NOT_IN_THE_RANGE:
+ return splat_range_date;
+ case SPLACTION_IS_STRING:
+ case SPLACTION_CONTAINS:
+ case SPLACTION_STARTS_WITH:
+ case SPLACTION_DOES_NOT_START_WITH:
+ case SPLACTION_ENDS_WITH:
+ case SPLACTION_DOES_NOT_END_WITH:
+ case SPLACTION_IS_NOT:
+ case SPLACTION_DOES_NOT_CONTAIN:
+ return splat_invalid;
+ default:
+ /* Unknown action type */
+ g_warning ("Unknown action type %d\n\n", splr->action);
+ return splat_unknown;
+ }
+ break;
+
+ case splft_playlist:
+ switch (splr->action)
+ {
+ case SPLACTION_IS_INT:
+ case SPLACTION_IS_NOT_INT:
+ return splat_playlist;
+ case SPLACTION_IS_GREATER_THAN:
+ case SPLACTION_IS_NOT_GREATER_THAN:
+ case SPLACTION_IS_LESS_THAN:
+ case SPLACTION_IS_NOT_LESS_THAN:
+ case SPLACTION_IS_IN_THE_LAST:
+ case SPLACTION_IS_NOT_IN_THE_LAST:
+ case SPLACTION_IS_IN_THE_RANGE:
+ case SPLACTION_IS_NOT_IN_THE_RANGE:
+ case SPLACTION_IS_STRING:
+ case SPLACTION_CONTAINS:
+ case SPLACTION_STARTS_WITH:
+ case SPLACTION_DOES_NOT_START_WITH:
+ case SPLACTION_ENDS_WITH:
+ case SPLACTION_DOES_NOT_END_WITH:
+ case SPLACTION_IS_NOT:
+ case SPLACTION_DOES_NOT_CONTAIN:
+ return splat_invalid;
+ default:
+ /* Unknown action type */
+ g_warning ("Unknown action type %d\n\n", splr->action);
+ return splat_unknown;
+ }
+
+ case splft_unknown:
+ /* Unknown action type */
+ g_warning ("Unknown action type %d\n\n", splr->action);
+ return splat_unknown;
+ }
+ return splat_unknown;
+}
+
+/* -------------------------------------------------------------------
+ *
+ * smart playlist stuff, adapted from source provided by Samuel "Otto"
+ * Wood (sam dot wood at gmail dot com). This part can also be used
+ * under a FreeBSD license. You can also contact Samuel for a complete
+ * copy of his original C++-classes.
+ *
+ */
+
+
+/* function to evaluate a rule's truth against a track */
+gboolean itdb_splr_eval (Itdb_iTunesDB *itdb, SPLRule *splr, Itdb_Track *track)
+{
+ SPLFieldType ft;
+ SPLActionType at;
+ gchar *strcomp = NULL;
+ gint64 intcomp = 0;
+ gboolean boolcomp = FALSE;
+ guint32 datecomp = 0;
+ Itdb_Playlist *playcomp = NULL;
+ time_t t;
+ guint64 mactime;
+
+ g_return_val_if_fail (splr != NULL, FALSE);
+ g_return_val_if_fail (track != NULL, FALSE);
+
+ ft = itdb_splr_get_field_type (splr);
+ at = itdb_splr_get_action_type (splr);
+
+ g_return_val_if_fail (at != splat_invalid, FALSE);
+
+ /* find what we need to compare in the track */
+ switch (splr->field)
+ {
+ case SPLFIELD_SONG_NAME:
+ strcomp = track->title;
+ break;
+ case SPLFIELD_ALBUM:
+ strcomp = track->album;
+ break;
+ case SPLFIELD_ARTIST:
+ strcomp = track->artist;
+ break;
+ case SPLFIELD_GENRE:
+ strcomp = track->genre;
+ break;
+ case SPLFIELD_KIND:
+ strcomp = track->fdesc;
+ break;
+ case SPLFIELD_COMMENT:
+ strcomp = track->comment;
+ break;
+ case SPLFIELD_COMPOSER:
+ strcomp = track->composer;
+ break;
+ case SPLFIELD_GROUPING:
+ strcomp = track->grouping;
+ break;
+ case SPLFIELD_BITRATE:
+ intcomp = track->bitrate;
+ break;
+ case SPLFIELD_SAMPLE_RATE:
+ intcomp = track->samplerate;
+ break;
+ case SPLFIELD_YEAR:
+ intcomp = track->year;
+ break;
+ case SPLFIELD_TRACKNUMBER:
+ intcomp = track->track_nr;
+ break;
+ case SPLFIELD_SIZE:
+ intcomp = track->size;
+ break;
+ case SPLFIELD_PLAYCOUNT:
+ intcomp = track->playcount;
+ break;
+ case SPLFIELD_DISC_NUMBER:
+ intcomp = track->cd_nr;
+ break;
+ case SPLFIELD_BPM:
+ intcomp = track->BPM;
+ break;
+ case SPLFIELD_RATING:
+ intcomp = track->rating;
+ break;
+ case SPLFIELD_TIME:
+ intcomp = track->tracklen/1000;
+ break;
+ case SPLFIELD_COMPILATION:
+ boolcomp = track->compilation;
+ break;
+ case SPLFIELD_DATE_MODIFIED:
+ datecomp = track->time_modified;
+ break;
+ case SPLFIELD_DATE_ADDED:
+ datecomp = track->time_added;
+ break;
+ case SPLFIELD_LAST_PLAYED:
+ datecomp = track->time_played;
+ break;
+ case SPLFIELD_PLAYLIST:
+ playcomp = itdb_playlist_by_id (itdb, splr->fromvalue);
+ break;
+ default: /* unknown field type */
+ g_return_val_if_fail (FALSE, FALSE);
+ }
+
+ /* actually do the comparison to our rule */
+ switch (ft)
+ {
+ case splft_string:
+ if(strcomp && splr->string)
+ {
+ gint len1 = strlen (strcomp);
+ gint len2 = strlen (splr->string);
+ switch (splr->action)
+ {
+ case SPLACTION_IS_STRING:
+ return (strcmp (strcomp, splr->string) == 0);
+ case SPLACTION_IS_NOT:
+ return (strcmp (strcomp, splr->string) != 0);
+ case SPLACTION_CONTAINS:
+ return (strstr (strcomp, splr->string) != NULL);
+ case SPLACTION_DOES_NOT_CONTAIN:
+ return (strstr (strcomp, splr->string) == NULL);
+ case SPLACTION_STARTS_WITH:
+ return (strncmp (strcomp, splr->string, len2) == 0);
+ case SPLACTION_ENDS_WITH:
+ if (len2 > len1) return FALSE;
+ return (strncmp (strcomp+len1-len2,
+ splr->string, len2) == 0);
+ case SPLACTION_DOES_NOT_START_WITH:
+ return (strncmp (strcomp, splr->string,
+ strlen (splr->string)) != 0);
+ case SPLACTION_DOES_NOT_END_WITH:
+ if (len2 > len1) return TRUE;
+ return (strncmp (strcomp+len1-len2,
+ splr->string, len2) != 0);
+ };
+ }
+ return FALSE;
+ case splft_int:
+ switch(splr->action)
+ {
+ case SPLACTION_IS_INT:
+ return (intcomp == splr->fromvalue);
+ case SPLACTION_IS_NOT_INT:
+ return (intcomp != splr->fromvalue);
+ case SPLACTION_IS_GREATER_THAN:
+ return (intcomp > splr->fromvalue);
+ case SPLACTION_IS_LESS_THAN:
+ return (intcomp < splr->fromvalue);
+ case SPLACTION_IS_IN_THE_RANGE:
+ return ((intcomp <= splr->fromvalue &&
+ intcomp >= splr->tovalue) ||
+ (intcomp >= splr->fromvalue &&
+ intcomp <= splr->tovalue));
+ case SPLACTION_IS_NOT_IN_THE_RANGE:
+ return ((intcomp < splr->fromvalue &&
+ intcomp < splr->tovalue) ||
+ (intcomp > splr->fromvalue &&
+ intcomp > splr->tovalue));
+ }
+ return FALSE;
+ case splft_boolean:
+ switch (splr->action)
+ {
+ case SPLACTION_IS_INT: /* aka "is set" */
+ return (boolcomp != 0);
+ case SPLACTION_IS_NOT_INT: /* aka "is not set" */
+ return (boolcomp == 0);
+ }
+ return FALSE;
+ case splft_date:
+ switch (splr->action)
+ {
+ case SPLACTION_IS_INT:
+ return (datecomp == splr->fromvalue);
+ case SPLACTION_IS_NOT_INT:
+ return (datecomp != splr->fromvalue);
+ case SPLACTION_IS_GREATER_THAN:
+ return (datecomp > splr->fromvalue);
+ case SPLACTION_IS_LESS_THAN:
+ return (datecomp < splr->fromvalue);
+ case SPLACTION_IS_NOT_GREATER_THAN:
+ return (datecomp <= splr->fromvalue);
+ case SPLACTION_IS_NOT_LESS_THAN:
+ return (datecomp >= splr->fromvalue);
+ case SPLACTION_IS_IN_THE_LAST:
+ time (&t);
+ t += (splr->fromdate * splr->fromunits);
+ mactime = itdb_time_host_to_mac (t);
+ return (datecomp > mactime);
+ case SPLACTION_IS_NOT_IN_THE_LAST:
+ time (&t);
+ t += (splr->fromdate * splr->fromunits);
+ mactime = itdb_time_host_to_mac (t);
+ return (datecomp <= mactime);
+ case SPLACTION_IS_IN_THE_RANGE:
+ return ((datecomp <= splr->fromvalue &&
+ datecomp >= splr->tovalue) ||
+ (datecomp >= splr->fromvalue &&
+ datecomp <= splr->tovalue));
+ case SPLACTION_IS_NOT_IN_THE_RANGE:
+ return ((datecomp < splr->fromvalue &&
+ datecomp < splr->tovalue) ||
+ (datecomp > splr->fromvalue &&
+ datecomp > splr->tovalue));
+ }
+ return FALSE;
+ case splft_playlist:
+ /* if we didn't find the playlist, just exit instead of
+ dealing with it */
+ if (playcomp == NULL) return FALSE;
+
+ switch(splr->action)
+ {
+ case SPLACTION_IS_INT: /* is this track in this playlist? */
+ return (itdb_playlist_contains_track (playcomp, track));
+ case SPLACTION_IS_NOT_INT:/* NOT in this playlist? */
+ return (!itdb_playlist_contains_track (playcomp, track));
+ }
+ return FALSE;
+ case splft_unknown:
+ g_return_val_if_fail (ft != splft_unknown, FALSE);
+ return FALSE;
+ default: /* new type: warning to change this code */
+ g_return_val_if_fail (FALSE, FALSE);
+ return FALSE;
+ }
+ /* we should never make it out of the above switches alive */
+ g_return_val_if_fail (FALSE, FALSE);
+ return FALSE;
+}
+
+/* local functions to help with the sorting of the list of tracks so
+ * that we can do limits */
+static gint compTitle (Itdb_Track *a, Itdb_Track *b)
+{
+ return strcmp (a->title, b->title);
+}
+static gint compAlbum (Itdb_Track *a, Itdb_Track *b)
+{
+ return strcmp (a->album, b->album);
+}
+static gint compArtist (Itdb_Track *a, Itdb_Track *b)
+{
+ return strcmp (a->artist, b->artist);
+}
+static gint compGenre (Itdb_Track *a, Itdb_Track *b)
+{
+ return strcmp (a->genre, b->genre);
+}
+static gint compMostRecentlyAdded (Itdb_Track *a, Itdb_Track *b)
+{
+ return b->time_added - a->time_added;
+}
+static gint compLeastRecentlyAdded (Itdb_Track *a, Itdb_Track *b)
+{
+ return a->time_added - b->time_added;
+}
+static gint compMostOftenPlayed (Itdb_Track *a, Itdb_Track *b)
+{
+ return b->time_added - a->time_added;
+}
+static gint compLeastOftenPlayed (Itdb_Track *a, Itdb_Track *b)
+{
+ return a->time_added - b->time_added;
+}
+static gint compMostRecentlyPlayed (Itdb_Track *a, Itdb_Track *b)
+{
+ return b->time_played - a->time_played;
+}
+static gint compLeastRecentlyPlayed (Itdb_Track *a, Itdb_Track *b)
+{
+ return a->time_played - b->time_played;
+}
+static gint compHighestRating (Itdb_Track *a, Itdb_Track *b)
+{
+ return b->rating - a->rating;
+}
+static gint compLowestRating (Itdb_Track *a, Itdb_Track *b)
+{
+ return a->rating - b->rating;
+}
+
+/* Randomize the order of the members of the GList @list */
+/* Returns a pointer to the new start of the list */
+static GList *randomize_glist (GList *list)
+{
+ gint32 nr = g_list_length (list);
+
+ while (nr > 1)
+ {
+ /* get random element among the first nr members */
+ gint32 rand = g_random_int_range (0, nr);
+ GList *gl = g_list_nth (list, rand);
+ /* remove it and add it at the end */
+ list = g_list_remove_link (list, gl);
+ list = g_list_concat (list, gl);
+ --nr;
+ }
+ return list;
+}
+
+
+/* Randomizes a playlist */
+void itdb_playlist_randomize (Itdb_Playlist *pl)
+{
+ g_return_if_fail (pl);
+
+ pl->members = randomize_glist (pl->members);
+}
+
+
+void itdb_spl_update (Itdb_iTunesDB *itdb, Itdb_Playlist *spl)
+{
+ GList *gl;
+ GList *sel_tracks = NULL;
+
+ g_return_if_fail (spl);
+ g_return_if_fail (itdb);
+
+ /* we only can populate smart playlists */
+ if (!spl->is_spl) return;
+
+ /* clear this playlist */
+ g_list_free (spl->members);
+ spl->members = NULL;
+ spl->num = 0;
+
+ for (gl=itdb->tracks; gl ; gl=gl->next)
+ {
+ Itdb_Track *t = gl->data;
+ g_return_if_fail (t);
+ /* skip non-checked songs if we have to do so (this takes care
+ of *all* the match_checked functionality) */
+ if (spl->splpref.matchcheckedonly && (t->checked == 0))
+ continue;
+ /* first, match the rules */
+ if (spl->splpref.checkrules)
+ { /* if we are set to check the rules */
+ /* start with true for "match all",
+ start with false for "match any" */
+ gboolean matchrules;
+ GList *gl;
+
+ if (spl->splrules.match_operator == SPLMATCH_AND)
+ matchrules = TRUE;
+ else matchrules = FALSE;
+ /* assume everything matches with no rules */
+ if (spl->splrules.rules == NULL) matchrules = TRUE;
+ /* match all rules */
+ for (gl=spl->splrules.rules; gl; gl=gl->next)
+ {
+ SPLRule* splr = gl->data;
+ gboolean ruletruth = itdb_splr_eval (itdb, splr, t);
+ if (spl->splrules.match_operator == SPLMATCH_AND)
+ {
+ if (!ruletruth)
+ { /* one rule did not match -- we can stop */
+ matchrules = FALSE;
+ break;
+ }
+ }
+ else if (spl->splrules.match_operator == SPLMATCH_OR)
+ {
+ if (ruletruth)
+ { /* one rule matched -- we can stop */
+ matchrules = TRUE;
+ break;
+ }
+ }
+ }
+ if (matchrules)
+ { /* we have a track that matches the ruleset, append to
+ * playlist for now*/
+ sel_tracks = g_list_append (sel_tracks, t);
+ }
+ }
+ else
+ { /* we aren't checking the rules, so just append to
+ playlist */
+ sel_tracks = g_list_append (sel_tracks, t);
+ }
+ }
+ /* no reason to go on if nothing matches so far */
+ if (g_list_length (sel_tracks) == 0) return;
+
+ /* do the limits */
+ if (spl->splpref.checklimits)
+ {
+ /* use a double because we may need to deal with fractions
+ * here */
+ gdouble runningtotal = 0;
+ guint32 trackcounter = 0;
+ guint32 tracknum = g_list_length (sel_tracks);
+
+/* printf("limitsort: %d\n", spl->splpref.limitsort); */
+
+ /* limit to (number) (type) selected by (sort) */
+ /* first, we sort the list */
+ switch(spl->splpref.limitsort)
+ {
+ case LIMITSORT_RANDOM:
+ sel_tracks = randomize_glist (sel_tracks);
+ break;
+ case LIMITSORT_SONG_NAME:
+ sel_tracks = g_list_sort (sel_tracks, (GCompareFunc)compTitle);
+ break;
+ case LIMITSORT_ALBUM:
+ sel_tracks = g_list_sort (sel_tracks, (GCompareFunc)compAlbum);
+ break;
+ case LIMITSORT_ARTIST:
+ sel_tracks = g_list_sort (sel_tracks, (GCompareFunc)compArtist);
+ break;
+ case LIMITSORT_GENRE:
+ sel_tracks = g_list_sort (sel_tracks, (GCompareFunc)compGenre);
+ break;
+ case LIMITSORT_MOST_RECENTLY_ADDED:
+ sel_tracks = g_list_sort (sel_tracks,
+ (GCompareFunc)compMostRecentlyAdded);
+ break;
+ case LIMITSORT_LEAST_RECENTLY_ADDED:
+ sel_tracks = g_list_sort (sel_tracks,
+ (GCompareFunc)compLeastRecentlyAdded);
+ break;
+ case LIMITSORT_MOST_OFTEN_PLAYED:
+ sel_tracks = g_list_sort (sel_tracks,
+ (GCompareFunc)compMostOftenPlayed);
+ break;
+ case LIMITSORT_LEAST_OFTEN_PLAYED:
+ sel_tracks = g_list_sort (sel_tracks,
+ (GCompareFunc)compLeastOftenPlayed);
+ break;
+ case LIMITSORT_MOST_RECENTLY_PLAYED:
+ sel_tracks = g_list_sort (sel_tracks,
+ (GCompareFunc)compMostRecentlyPlayed);
+ break;
+ case LIMITSORT_LEAST_RECENTLY_PLAYED:
+ sel_tracks = g_list_sort (sel_tracks,
+ (GCompareFunc)compLeastRecentlyPlayed);
+ break;
+ case LIMITSORT_HIGHEST_RATING:
+ sel_tracks = g_list_sort (sel_tracks,
+ (GCompareFunc)compHighestRating);
+ break;
+ case LIMITSORT_LOWEST_RATING:
+ sel_tracks = g_list_sort (sel_tracks,
+ (GCompareFunc)compLowestRating);
+ break;
+ default:
+ g_warning ("Programming error: should not reach this point (default of switch (spl->splpref.limitsort)\n");
+ break;
+ }
+ /* now that the list is sorted in the order we want, we
+ take the top X tracks off the list and insert them into
+ our playlist */
+
+ while ((runningtotal < spl->splpref.limitvalue) &&
+ (trackcounter < tracknum))
+ {
+ gdouble currentvalue=0;
+ Itdb_Track *t = g_list_nth_data (sel_tracks, trackcounter);
+
+/* printf ("track: %d runningtotal: %lf, limitvalue: %d\n", */
+/* trackcounter, runningtotal, spl->splpref.limitvalue); */
+
+ /* get the next song's value to add to running total */
+ switch (spl->splpref.limittype)
+ {
+ case LIMITTYPE_MINUTES:
+ currentvalue = (double)(t->tracklen)/(60*1000);
+ break;
+ case LIMITTYPE_HOURS:
+ currentvalue = (double)(t->tracklen)/(60*60*1000);
+ break;
+ case LIMITTYPE_MB:
+ currentvalue = (double)(t->size)/(1024*1024);
+ break;
+ case LIMITTYPE_GB:
+ currentvalue = (double)(t->size)/(1024*1024*1024);
+ break;
+ case LIMITTYPE_SONGS:
+ currentvalue = 1;
+ break;
+ default:
+ g_warning ("Programming error: should not reach this point (default of switch (spl->splpref.limittype)\n");
+ break;
+ }
+ /* check to see that we won't actually exceed the
+ * limitvalue */
+ if (runningtotal + currentvalue <=
+ spl->splpref.limitvalue)
+ {
+ runningtotal += currentvalue;
+ /* Add the playlist entry */
+ itdb_playlist_add_track (spl, t, -1);
+ }
+ /* increment the track counter so we can look at the next
+ track */
+ trackcounter++;
+/* printf (" track: %d runningtotal: %lf, limitvalue: %d\n", */
+/* trackcounter, runningtotal, spl->splpref.limitvalue); */
+ } /* end while */
+ /* no longer needed */
+ g_list_free (sel_tracks);
+ sel_tracks = NULL;
+ } /* end if limits enabled */
+ else
+ { /* no limits, so stick everything that matched the rules into
+ the playlist */
+ spl->members = sel_tracks;
+ spl->num = g_list_length (sel_tracks);
+ sel_tracks = NULL;
+ }
+}
+
+
+
+
+/* update all smart playlists */
+void itdb_spl_update_all (Itdb_iTunesDB *itdb)
+{
+ void spl_update (Itdb_Playlist *playlist, Itdb_iTunesDB *itdb)
+ {
+ g_return_if_fail (playlist);
+ itdb_spl_update (itdb, playlist);
+ }
+
+ g_return_if_fail (itdb);
+
+ g_list_foreach (itdb->playlists, (GFunc)spl_update, itdb);
+}
+
+
+
+/* end of code based on Samuel Wood's work */
+/* ------------------------------------------------------------------- */
+
+
+/* Validate a rule */
+void itdb_splr_validate (SPLRule *splr)
+{
+ SPLActionType at;
+
+ g_return_if_fail (splr != NULL);
+
+ at = itdb_splr_get_action_type (splr);
+
+ g_return_if_fail (at != splat_unknown);
+
+ switch (at)
+ {
+ case splat_int:
+ case splat_playlist:
+ case splat_date:
+ splr->fromdate = 0;
+ splr->fromunits = 1;
+ splr->tovalue = splr->fromvalue;
+ splr->todate = 0;
+ splr->tounits = 1;
+ break;
+ case splat_range_int:
+ case splat_range_date:
+ splr->fromdate = 0;
+ splr->fromunits = 1;
+ splr->todate = 0;
+ splr->tounits = 1;
+ break;
+ case splat_inthelast:
+ splr->fromvalue = SPLDATE_IDENTIFIER;
+ splr->tovalue = SPLDATE_IDENTIFIER;
+ break;
+ case splat_none:
+ case splat_string:
+ splr->fromvalue = 0;
+ splr->fromdate = 0;
+ splr->fromunits = 0;
+ splr->tovalue = 0;
+ splr->todate = 0;
+ splr->tounits = 0;
+ break;
+ case splat_invalid:
+ case splat_unknown:
+ g_return_if_fail (FALSE);
+ break;
+ }
+
+}
+
+
+/* Free memory of SPLRule @splr */
+static void itdb_splr_free (SPLRule *splr)
+{
+ if (splr)
+ {
+ g_free (splr->string);
+ g_free (splr);
+ }
+}
+
+/* remove @splr from playlist @pl */
+void itdb_splr_remove (Itdb_Playlist *pl, SPLRule *splr)
+{
+ g_return_if_fail (pl);
+ g_return_if_fail (splr);
+
+ pl->splrules.rules = g_list_remove (pl->splrules.rules, splr);
+ itdb_splr_free (splr);
+}
+
+/* add smart rule @splr to playlist @pl at position @pos */
+void itdb_splr_add (Itdb_Playlist *pl, SPLRule *splr, gint pos)
+{
+ g_return_if_fail (pl);
+ g_return_if_fail (splr);
+
+ pl->splrules.rules = g_list_insert (pl->splrules.rules,
+ splr, pos);
+}
+
+
+/* Create new default rule */
+SPLRule *itdb_splr_new (void)
+{
+ SPLRule *splr = g_new0 (SPLRule, 1);
+
+ splr->field = SPLFIELD_ARTIST;
+ splr->action = SPLACTION_CONTAINS;
+ splr->fromvalue = 0;
+ splr->fromdate = 0;
+ splr->fromunits = 0;
+ splr->tovalue = 0;
+ splr->todate = 0;
+ splr->tounits = 0;
+
+ return splr;
+}
+
+
+/* create a new smart rule and insert it at position @pos of playlist
+ * @pl. A pointer to the newly created rule is returned. */
+SPLRule *itdb_splr_add_new (Itdb_Playlist *pl, gint pos)
+{
+ SPLRule *splr;
+
+ g_return_val_if_fail (pl, NULL);
+
+ splr = itdb_splr_new ();
+ itdb_splr_add (pl, splr, pos);
+ return splr;
+}
+
+/* Duplicate SPLRule @splr */
+static SPLRule *splr_duplicate (SPLRule *splr)
+{
+ SPLRule *dup = NULL;
+ if (splr)
+ {
+ dup = g_malloc (sizeof (SPLRule));
+ memcpy (dup, splr, sizeof (SPLRule));
+
+ /* Now copy the strings */
+ dup->string = g_strdup (splr->string);
+ }
+ return dup;
+}
+
+
+/* Duplicate an existing playlist. pl_dup->id is set to zero, so that
+ * it will be set to a unique value when adding it to the itdb. */
+Itdb_Playlist *itdb_playlist_duplicate (Itdb_Playlist *pl)
+{
+ Itdb_Playlist *pl_dup;
+ GList *gl;
+
+ g_return_val_if_fail (pl, NULL);
+ g_return_val_if_fail (!pl->userdata || pl->userdata_duplicate, NULL);
+
+ pl_dup = g_new0 (Itdb_Playlist, 1);
+ memcpy (pl_dup, pl, sizeof (Itdb_Playlist));
+ /* clear list heads */
+ pl_dup->members = NULL;
+ pl_dup->splrules.rules = NULL;
+
+ /* clear itdb pointer */
+ pl_dup->itdb = NULL;
+
+ /* Now copy strings */
+ pl_dup->name = g_strdup (pl->name);
+
+ /* Copy members */
+ pl_dup->members = g_list_copy (pl->members);
+
+ /* Copy rules */
+ for (gl=pl->splrules.rules; gl; gl=gl->next)
+ {
+ SPLRule *splr_dup = splr_duplicate (gl->data);
+ pl_dup->splrules.rules = g_list_append (
+ pl_dup->splrules.rules, splr_dup);
+ }
+
+ /* Set id to 0, so it will be set to a unique value when adding
+ * this playlist to a itdb */
+ pl_dup->id = 0;
+
+ /* Copy userdata */
+ if (pl->userdata)
+ pl_dup->userdata = pl->userdata_duplicate (pl->userdata);
+
+ return pl_dup;
+}
+
+
+/* copy all relevant information for smart playlist from playlist @src
+ to playlist @dest. Already available information is
+ overwritten/deleted. */
+void itdb_spl_copy_rules (Itdb_Playlist *dest, Itdb_Playlist *src)
+{
+ GList *gl;
+
+ g_return_if_fail (dest);
+ g_return_if_fail (src);
+ g_return_if_fail (dest->is_spl);
+ g_return_if_fail (src->is_spl);
+
+ /* remove existing rules */
+ g_list_foreach (dest->splrules.rules, (GFunc)(itdb_splr_free), NULL);
+ g_list_free (dest->splrules.rules);
+
+ /* copy general spl settings */
+ memcpy (&dest->splpref, &src->splpref, sizeof (SPLPref));
+ memcpy (&dest->splrules, &src->splrules, sizeof (SPLRules));
+ dest->splrules.rules = NULL;
+
+ /* Copy rules */
+ for (gl=src->splrules.rules; gl; gl=gl->next)
+ {
+ SPLRule *splr_dup = splr_duplicate (gl->data);
+ dest->splrules.rules = g_list_append (
+ dest->splrules.rules, splr_dup);
+ }
+}
+
+
+
+/* Generate a new playlist structure. If @spl is TRUE, a smart
+ * playlist is generated. pl->id is set when adding to an itdb by
+ * itdb_playlist_add()*/
+Itdb_Playlist *itdb_playlist_new (const gchar *title, gboolean spl)
+{
+ Itdb_Playlist *pl = g_new0 (Itdb_Playlist, 1);
+
+ pl->type = ITDB_PL_TYPE_NORM;
+ pl->name = g_strdup (title);
+ pl->is_spl = spl;
+ if (spl)
+ {
+ pl->splpref.liveupdate = TRUE;
+ pl->splpref.checkrules = TRUE;
+ pl->splpref.checklimits = FALSE;
+ pl->splpref.limittype = LIMITTYPE_HOURS;
+ pl->splpref.limitsort = LIMITSORT_RANDOM;
+ pl->splpref.limitvalue = 2;
+ pl->splpref.matchcheckedonly = FALSE;
+ pl->splrules.match_operator = SPLMATCH_AND;
+ /* add at least one rule */
+ itdb_splr_add_new (pl, 0);
+ }
+ return pl;
+}
+
+
+/* Free the memory taken by playlist @pl. */
+void itdb_playlist_free (Itdb_Playlist *pl)
+{
+ g_return_if_fail (pl);
+
+ g_free (pl->name);
+ g_list_free (pl->members);
+ g_list_foreach (pl->splrules.rules,
+ (GFunc)(itdb_splr_free), NULL);
+ g_list_free (pl->splrules.rules);
+ if (pl->userdata && pl->userdata_destroy)
+ (*pl->userdata_destroy) (pl->userdata);
+ g_free (pl);
+}
+
+
+
+/* add playlist @pl to the database @itdb at position @pos (-1 for
+ * "append to end"). A unique id is created if pl->id is equal to
+ * zero. */
+/* a critical message is logged if either itdb or pl is NULL */
+void itdb_playlist_add (Itdb_iTunesDB *itdb, Itdb_Playlist *pl, gint32 pos)
+{
+ g_return_if_fail (itdb);
+ g_return_if_fail (pl);
+ g_return_if_fail (!pl->userdata || pl->userdata_duplicate);
+
+ pl->itdb = itdb;
+
+ /* set unique ID when not yet set */
+ if (pl->id == 0)
+ {
+ GRand *grand = g_rand_new ();
+ GList *gl;
+ guint64 id;
+ do
+ {
+ id = ((guint64)g_rand_int (grand) << 32) |
+ ((guint64)g_rand_int (grand));
+ /* check if id is really random (with 100 playlists the
+ * chance to create a duplicate is 1 in
+ * 184,467,440,737,095,516.16) */
+ for (gl=itdb->playlists; id && gl; gl=gl->next)
+ {
+ Itdb_Playlist *g_pl = gl->data;
+ g_return_if_fail (g_pl);
+ if (id == g_pl->id) id = 0;
+ }
+ } while (id == 0);
+ pl->id = id;
+ }
+ if (pos == -1) itdb->playlists = g_list_append (itdb->playlists, pl);
+ else itdb->playlists = g_list_insert (itdb->playlists, pl, pos);
+}
+
+
+
+/* move playlist @pl to position @pos */
+void itdb_playlist_move (Itdb_Playlist *pl, guint32 pos)
+{
+ Itdb_iTunesDB *itdb;
+
+ g_return_if_fail (pl);
+ itdb = pl->itdb;
+ g_return_if_fail (itdb);
+
+ itdb->playlists = g_list_remove (itdb->playlists, pl);
+ itdb->playlists = g_list_insert (itdb->playlists, pl, pos);
+}
+
+
+/* Remove playlist @pl and free memory */
+void itdb_playlist_remove (Itdb_Playlist *pl)
+{
+ Itdb_iTunesDB *itdb;
+
+ g_return_if_fail (pl);
+ itdb = pl->itdb;
+ g_return_if_fail (itdb);
+
+ itdb->playlists = g_list_remove (itdb->playlists, pl);
+ itdb_playlist_free (pl);
+}
+
+
+/* Remove playlist @pl but do not free memory */
+/* pl->itdb is set to NULL */
+void itdb_playlist_unlink (Itdb_Playlist *pl)
+{
+ Itdb_iTunesDB *itdb;
+
+ g_return_if_fail (pl);
+ itdb = pl->itdb;
+ g_return_if_fail (itdb);
+
+ itdb->playlists = g_list_remove (itdb->playlists, pl);
+ pl->itdb = NULL;
+}
+
+
+/* Return TRUE if the playlist @pl exists, FALSE otherwise */
+gboolean itdb_playlist_exists (Itdb_iTunesDB *itdb, Itdb_Playlist *pl)
+{
+ g_return_val_if_fail (itdb, FALSE);
+ g_return_val_if_fail (pl, FALSE);
+
+ if (g_list_find (itdb->playlists, pl)) return TRUE;
+ else return FALSE;
+}
+
+
+/* add @track to playlist @pl position @pos (-1 for "append to
+ * end") */
+/* a critical message is logged if either @itdb, @pl or @track is
+ NULL */
+void itdb_playlist_add_track (Itdb_Playlist *pl,
+ Itdb_Track *track, gint32 pos)
+{
+ g_return_if_fail (pl);
+ g_return_if_fail (pl->itdb);
+ g_return_if_fail (track);
+
+ track->itdb = pl->itdb;
+
+ if (pos == -1) pl->members = g_list_append (pl->members, track);
+ else pl->members = g_list_insert (pl->members, track, pos);
+}
+
+
+
+/* Remove track @track from playlist *pl. If @pl == NULL remove from
+ * master playlist. */
+void itdb_playlist_remove_track (Itdb_Playlist *pl, Itdb_Track *track)
+{
+ g_return_if_fail (track);
+
+ if (pl == NULL)
+ pl = itdb_playlist_mpl (track->itdb);
+
+ g_return_if_fail (pl);
+
+ pl->members = g_list_remove (pl->members, track);
+}
+
+
+/* Returns the playlist with the ID @id or NULL if the ID cannot be
+ * found. */
+Itdb_Playlist *itdb_playlist_by_id (Itdb_iTunesDB *itdb, guint64 id)
+{
+ GList *gl;
+
+ g_return_val_if_fail (itdb, NULL);
+
+ for (gl=itdb->playlists; gl; gl=gl->next)
+ {
+ Itdb_Playlist *pl = gl->data;
+ if (pl->id == id) return pl;
+ }
+ return NULL;
+}
+
+
+/* Return playlist at position @num in @itdb */
+Itdb_Playlist *itdb_playlist_by_nr (Itdb_iTunesDB *itdb, guint32 num)
+{
+ Itdb_Playlist *pl;
+ g_return_val_if_fail (itdb, NULL);
+ pl = g_list_nth_data (itdb->playlists, num);
+ g_return_val_if_fail (pl, NULL);
+ return pl;
+}
+
+
+/* Return first playlist with name @name. */
+Itdb_Playlist *itdb_playlist_by_name (Itdb_iTunesDB *itdb, gchar *name)
+{
+ GList *gl;
+ g_return_val_if_fail (itdb, NULL);
+ g_return_val_if_fail (name, NULL);
+
+ for (gl=itdb->playlists; gl; gl=gl->next)
+ {
+ Itdb_Playlist *pl = gl->data;
+ g_return_val_if_fail (pl, NULL);
+ if (pl->name && (strcmp (pl->name, name) == 0))
+ return pl;
+ }
+ return NULL;
+}
+
+
+/* return the master playlist of @itdb */
+Itdb_Playlist *itdb_playlist_mpl (Itdb_iTunesDB *itdb)
+{
+ Itdb_Playlist *pl;
+
+ g_return_val_if_fail (itdb, NULL);
+
+ pl = g_list_nth_data (itdb->playlists, 0);
+ g_return_val_if_fail (pl, NULL);
+
+ /* mpl is guaranteed to be at first position... */
+ g_return_val_if_fail (pl->type == ITDB_PL_TYPE_MPL, NULL);
+
+ return pl;
+}
+
+
+/* checks if @track is in playlist @pl. TRUE, if yes, FALSE
+ otherwise. If @pl is NULL, the */
+gboolean itdb_playlist_contains_track (Itdb_Playlist *pl, Itdb_Track *tr)
+{
+ g_return_val_if_fail (tr, FALSE);
+
+ if (pl == NULL)
+ pl = itdb_playlist_mpl (tr->itdb);
+
+ g_return_val_if_fail (pl, FALSE);
+
+ if (g_list_find (pl->members, tr)) return TRUE;
+ else return FALSE;
+}
+
+
+/* returns in how many playlists (other than the MPL) @track is a
+ member of */
+guint32 itdb_playlist_contain_track_number (Itdb_Track *tr)
+{
+ Itdb_iTunesDB *itdb;
+ guint32 num = 0;
+ GList *gl;
+
+ g_return_val_if_fail (tr, 0);
+ itdb = tr->itdb;
+ g_return_val_if_fail (itdb, 0);
+
+ /* start with 2nd playlist (skip MPL) */
+ gl = g_list_nth (itdb->playlists, 1);
+ while (gl)
+ {
+ g_return_val_if_fail (gl->data, num);
+ if (itdb_playlist_contains_track (gl->data, tr)) ++num;
+ gl = gl->next;
+ }
+ return num;
+}
+
+
+
+/* return number of tracks in playlist */
+guint32 itdb_playlist_tracks_number (Itdb_Playlist *pl)
+{
+ g_return_val_if_fail (pl, 0);
+
+ return g_list_length (pl->members);
+}
diff --git a/src/itdb_private.h b/src/itdb_private.h
new file mode 100644
index 0000000..72e8558
--- /dev/null
+++ b/src/itdb_private.h
@@ -0,0 +1,100 @@
+/* Time-stamp: <2005-07-09 15:48:29 jcs>
+|
+| Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
+| Part of the gtkpod project.
+|
+| URL: http://www.gtkpod.org/
+| URL: http://gtkpod.sourceforge.net/
+|
+| The code contained in this file is free software; you can redistribute
+| it and/or modify it under the terms of the GNU Lesser General Public
+| License as published by the Free Software Foundation; either version
+| 2.1 of the License, or (at your option) any later version.
+|
+| This file is distributed in the hope that it will be useful,
+| but WITHOUT ANY WARRANTY; without even the implied warranty of
+| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+| Lesser General Public License for more details.
+|
+| You should have received a copy of the GNU Lesser General Public
+| License along with this code; if not, write to the Free Software
+| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+|
+| iTunes and iPod are trademarks of Apple
+|
+| This product is not supported/written/published by Apple!
+|
+| $Id$
+*/
+
+#ifndef __ITDB_PRIVATE_H__
+#define __ITDB_PRIVATE_H__
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "itdb.h"
+
+/* keeps the contents of one disk file (read) */
+typedef struct
+{
+ gchar *filename;
+ gchar *contents;
+ gsize length;
+ GError *error;
+} FContents;
+
+
+/* struct used to hold all necessary information when importing a
+ Itdb_iTunesDB */
+typedef struct
+{
+ Itdb_iTunesDB *itdb;
+ FContents *itunesdb;
+ GList *pos_glist; /* temporary list to store position indicators */
+ GList *playcounts; /* contents of Play Counts file */
+ GTree *idtree; /* temporary tree with track id tree */
+ GError *error; /* where to report errors to */
+} FImport;
+
+/* data of playcounts GList above */
+struct playcount {
+ guint32 playcount;
+ guint32 time_played;
+ guint32 bookmark_time;
+ gint32 rating;
+ gint32 unk16;
+};
+
+/* value to indicate that playcount was not set in struct playcount
+ above */
+#define NO_PLAYCOUNT (-1)
+
+
+/* keeps the contents of the output file (write) */
+typedef struct
+{
+ gchar *filename;
+ gchar *contents; /* pointer to contents */
+ gulong pos; /* current write position ("end of file") */
+ gulong total; /* current total size of *contents array */
+ GError *error; /* place to report errors to */
+} WContents;
+
+/* size of memory by which the total size of above WContents gets
+ * increased (1.5 MB) */
+#define WCONTENTS_STEPSIZE 1572864
+
+/* struct used to hold all necessary information when exporting a
+ * Itdb_iTunesDB */
+typedef struct
+{
+ Itdb_iTunesDB *itdb;
+ WContents *itunesdb;
+ GError *error; /* where to report errors to */
+} FExport;
+
+
+gboolean itdb_spl_action_known (SPLAction action);
+#endif
diff --git a/src/itdb_track.c b/src/itdb_track.c
new file mode 100644
index 0000000..bad9f8e
--- /dev/null
+++ b/src/itdb_track.c
@@ -0,0 +1,205 @@
+/* Time-stamp: <2005-06-17 22:12:16 jcs>
+|
+| Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
+| Part of the gtkpod project.
+|
+| URL: http://www.gtkpod.org/
+| URL: http://gtkpod.sourceforge.net/
+|
+| The code contained in this file is free software; you can redistribute
+| it and/or modify it under the terms of the GNU Lesser General Public
+| License as published by the Free Software Foundation; either version
+| 2.1 of the License, or (at your option) any later version.
+|
+| This file is distributed in the hope that it will be useful,
+| but WITHOUT ANY WARRANTY; without even the implied warranty of
+| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+| Lesser General Public License for more details.
+|
+| You should have received a copy of the GNU Lesser General Public
+| License along with this code; if not, write to the Free Software
+| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+|
+| iTunes and iPod are trademarks of Apple
+|
+| This product is not supported/written/published by Apple!
+|
+| $Id$
+*/
+
+#include "itdb_private.h"
+#include <string.h>
+
+/* Generate a new Itdb_Track structure */
+Itdb_Track *itdb_track_new (void)
+{
+ Itdb_Track *track = g_new0 (Itdb_Track, 1);
+
+ track->unk020 = 1;
+ return track;
+}
+
+
+/* Add @track to @itdb->tracks at position @pos (or at the end if pos
+ is -1). Application is responsible to also add it to the master
+ playlist. */
+void itdb_track_add (Itdb_iTunesDB *itdb, Itdb_Track *track, gint32 pos)
+{
+ g_return_if_fail (itdb);
+ g_return_if_fail (track);
+ g_return_if_fail (!track->userdata || track->userdata_duplicate);
+
+ track->itdb = itdb;
+
+ if (pos == -1) itdb->tracks = g_list_append (itdb->tracks, track);
+ else itdb->tracks = g_list_insert (itdb->tracks, track, pos);
+}
+
+/* Free the memory taken by @track */
+void itdb_track_free (Itdb_Track *track)
+{
+ g_return_if_fail (track);
+
+ g_free (track->album);
+ g_free (track->artist);
+ g_free (track->title);
+ g_free (track->genre);
+ g_free (track->comment);
+ g_free (track->composer);
+ g_free (track->fdesc);
+ g_free (track->grouping);
+ g_free (track->ipod_path);
+ if (track->userdata && track->userdata_destroy)
+ (*track->userdata_destroy) (track->userdata);
+ g_free (track);
+}
+
+/* Remove track @track and free memory */
+void itdb_track_remove (Itdb_Track *track)
+{
+ Itdb_iTunesDB *itdb;
+
+ g_return_if_fail (track);
+ itdb = track->itdb;
+ g_return_if_fail (itdb);
+
+ itdb->tracks = g_list_remove (itdb->tracks, track);
+ itdb_track_free (track);
+}
+
+/* Remove track @track but do not free memory */
+/* track->itdb is set to NULL */
+void itdb_track_unlink (Itdb_Track *track)
+{
+ Itdb_iTunesDB *itdb;
+
+ g_return_if_fail (track);
+ itdb = track->itdb;
+ g_return_if_fail (itdb);
+
+ itdb->tracks = g_list_remove (itdb->tracks, track);
+ track->itdb = NULL;
+}
+
+/* Duplicate an existing track */
+Itdb_Track *itdb_track_duplicate (Itdb_Track *tr)
+{
+ Itdb_Track *tr_dup;
+
+ g_return_val_if_fail (tr, NULL);
+ g_return_val_if_fail (!tr->userdata || tr->userdata_duplicate, NULL);
+
+ tr_dup = g_new0 (Itdb_Track, 1);
+ memcpy (tr_dup, tr, sizeof (Itdb_Track));
+
+ /* clear itdb pointer */
+ tr_dup->itdb = NULL;
+
+ /* copy strings */
+ tr_dup->album = g_strdup (tr->album);
+ tr_dup->artist = g_strdup (tr->artist);
+ tr_dup->title = g_strdup (tr->title);
+ tr_dup->genre = g_strdup (tr->genre);
+ tr_dup->comment = g_strdup (tr->comment);
+ tr_dup->composer = g_strdup (tr->composer);
+ tr_dup->fdesc = g_strdup (tr->fdesc);
+ tr_dup->grouping = g_strdup (tr->grouping);
+ tr_dup->ipod_path = g_strdup (tr->ipod_path);
+
+ /* Copy userdata */
+ if (tr->userdata)
+ tr_dup->userdata = tr->userdata_duplicate (tr->userdata);
+
+ return tr_dup;
+}
+
+
+
+/* Returns the track with the ID @id or NULL if the ID cannot be
+ * found. */
+/* Looking up tracks by ID is not really a good idea because the IDs
+ are created by itdb just before export. The functions are here
+ because they are needed during import of the iTunesDB which is
+ referencing tracks by IDs */
+/* This function is very slow -- if you need to lookup many IDs use
+ the functions itdb_track_id_tree_create(),
+ itdb_track_id_tree_destroy(), and itdb_track_id_tree_by_id() below. */
+Itdb_Track *itdb_track_by_id (Itdb_iTunesDB *itdb, guint32 id)
+{
+ GList *gl;
+
+ g_return_val_if_fail (itdb, NULL);
+
+ for (gl=itdb->tracks; gl; gl=gl->next)
+ {
+ Itdb_Track *track = gl->data;
+ if (track->id == id) return track;
+ }
+ return NULL;
+}
+
+static gint track_id_compare (gconstpointer a, gconstpointer b)
+{
+ if (*(guint32*) a == *(guint32*) b)
+ return 0;
+ if (*(guint32*) a > *(guint32*) b)
+ return 1;
+ return -1;
+}
+
+
+/* Creates a balanced-binary tree for quick ID lookup that is used in
+ itdb_track_by_id_tree() function below */
+GTree *itdb_track_id_tree_create (Itdb_iTunesDB *itdb)
+{
+ GTree *idtree;
+ GList *gl;
+
+ g_return_val_if_fail (itdb, NULL);
+
+ idtree = g_tree_new (track_id_compare);
+
+ for (gl=itdb->tracks; gl; gl=gl->next)
+ {
+ Itdb_Track *tr = gl->data;
+ g_return_val_if_fail (tr, NULL);
+ g_tree_insert (idtree, &tr->id, tr);
+ }
+ return idtree;
+}
+
+/* free memory of @idtree */
+void itdb_track_id_tree_destroy (GTree *idtree)
+{
+ g_return_if_fail (idtree);
+
+ g_tree_destroy (idtree);
+}
+
+/* lookup track by @id using @idtree for quicker reference */
+Itdb_Track *itdb_track_id_tree_by_id (GTree *idtree, guint32 id)
+{
+ g_return_val_if_fail (idtree, NULL);
+
+ return (Itdb_Track *)g_tree_lookup (idtree, &id);
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..a436696
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,7 @@
+noinst_PROGRAMS=test-itdb
+
+INCLUDES=$(LIBGPOD_CFLAGS) -I$(top_srcdir)/src -DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\"
+LIBS=$(LIBGPOD_LIBS)
+
+test_itdb_SOURCES = itdb_main.c
+test_itdb_LDADD = $(top_builddir)/src/libgpod.la
diff --git a/tests/itdb_main.c b/tests/itdb_main.c
new file mode 100644
index 0000000..fd81cff
--- /dev/null
+++ b/tests/itdb_main.c
@@ -0,0 +1,88 @@
+/*
+| Copyright (C) 2002-2003 Jorg Schuler <jcsjcs at users.sourceforge.net>
+| Part of the gtkpod project.
+|
+| URL: http://gtkpod.sourceforge.net/
+|
+| This program is free software; you can redistribute it and/or modify
+| it under the terms of the GNU General Public License as published by
+| the Free Software Foundation; either version 2 of the License, or
+| (at your option) any later version.
+|
+| This program is distributed in the hope that it will be useful,
+| but WITHOUT ANY WARRANTY; without even the implied warranty of
+| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+| GNU General Public License for more details.
+|
+| You should have received a copy of the GNU General Public License
+| along with this program; if not, write to the Free Software
+| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+|
+| iTunes and iPod are trademarks of Apple
+|
+| This product is not supported/written/published by Apple!
+|
+| $Id$
+*/
+/*
+ * Initial main.c file generated by Glade. Edit as required.
+ * Glade will not overwrite this file.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <libintl.h>
+
+#include "itdb.h"
+
+int
+main (int argc, char *argv[])
+{
+#ifdef ENABLE_NLS
+ bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+#endif
+
+ GError *error=NULL;
+ Itdb_iTunesDB *itdb;
+
+ if (argc == 2)
+ itdb = itdb_parse_file (argv[1], &error);
+ else itdb = itdb_parse_file ("/home/jcs/.gtkpod/iTunesDB20050102",
+ &error);
+
+ printf ("%p\n", itdb);
+ if (error)
+ {
+ if (error->message)
+ puts (error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ if (itdb)
+ {
+ printf ("tracks: %d\n", g_list_length (itdb->tracks));
+ printf ("playlists: %d\n", g_list_length (itdb->playlists));
+
+ itdb_write_file (itdb, "/home/jcs/.gtkpod/iTunesDB", &error);
+ if (error)
+ {
+ if (error->message)
+ puts (error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+ }
+
+ itdb_free (itdb);
+
+ /* all the cleanup is already done in gtkpod_main_quit() in misc.c */
+ return 0;
+}