Merge branch 'master' of /home/gustavo/Development/git/lightmediascanner
authorbarbieri <barbieri@gmail.com>
Mon, 10 Dec 2007 21:05:52 +0000 (21:05 +0000)
committerbarbieri <barbieri@gmail.com>
Mon, 10 Dec 2007 21:05:52 +0000 (21:05 +0000)
* 'master' of /home/gustavo/Development/git/lightmediascanner: (60 commits)
  Modules are optional, don't fail when they fail.
  Adding support to ogg
  Implement lms_check()
  Refactor lightmediascanner_process.c
  Add missing new lines to messages
  Avoid breaking strict-aliasing rules.
  Use correct pointer types for iconv, avoid warnings.
  Move process stuff into own file, will add lms_check() and use part of it.
  Charset conversion support.
  Change plugin API to take a "context" instead of "db".
  Remove unused API macros.
  Cache lms_db_* per DB instance instead of globally.
  Provide data caching based on DB instance.
  Fix copy&paste legacy: s/ldi/ldv/g in lightmediascanner_db_video.c
  id3lib now uses playcnt.
  Add playcnt to audios table.
  PLS Parser.
  Also index audios by trackno.
  M3U parser.
  Fix memory leak and possible buffer overrun.
  ...

51 files changed:
lightmediascanner/.gitignore [new file with mode: 0644]
lightmediascanner/AUTHORS [new file with mode: 0644]
lightmediascanner/COPYING [new file with mode: 0644]
lightmediascanner/ChangeLog [new file with mode: 0644]
lightmediascanner/INSTALL [new file with mode: 0644]
lightmediascanner/Makefile.am [new file with mode: 0644]
lightmediascanner/NEWS [new file with mode: 0644]
lightmediascanner/README [new file with mode: 0644]
lightmediascanner/autogen.sh [new file with mode: 0755]
lightmediascanner/configure.ac [new file with mode: 0644]
lightmediascanner/lightmediascanner.pc.in [new file with mode: 0644]
lightmediascanner/m4/Makefile.am [new file with mode: 0644]
lightmediascanner/m4/ac-plugins.m4 [new file with mode: 0644]
lightmediascanner/m4/as-expand.m4 [new file with mode: 0644]
lightmediascanner/src/Makefile.am [new file with mode: 0644]
lightmediascanner/src/bin/.gitignore [new file with mode: 0644]
lightmediascanner/src/bin/Makefile.am [new file with mode: 0644]
lightmediascanner/src/bin/test.c [new file with mode: 0644]
lightmediascanner/src/lib/Makefile.am [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner.h [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_charset_conv.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_charset_conv.h [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_check.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_db.h [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_db_audio.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_db_common.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_db_image.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_db_playlist.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_db_private.h [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_db_video.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_plugin.h [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_private.h [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_process.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_utils.c [new file with mode: 0644]
lightmediascanner/src/lib/lightmediascanner_utils.h [new file with mode: 0644]
lightmediascanner/src/plugins/Makefile.am [new file with mode: 0644]
lightmediascanner/src/plugins/dummy/Makefile.am [new file with mode: 0644]
lightmediascanner/src/plugins/dummy/dummy.c [new file with mode: 0644]
lightmediascanner/src/plugins/id3lib/Makefile.am [new file with mode: 0644]
lightmediascanner/src/plugins/id3lib/id3lib.cpp [new file with mode: 0644]
lightmediascanner/src/plugins/jpeg/Makefile.am [new file with mode: 0644]
lightmediascanner/src/plugins/jpeg/jpeg.c [new file with mode: 0644]
lightmediascanner/src/plugins/m3u/Makefile.am [new file with mode: 0644]
lightmediascanner/src/plugins/m3u/m3u.c [new file with mode: 0644]
lightmediascanner/src/plugins/ogg/Makefile.am [new file with mode: 0644]
lightmediascanner/src/plugins/ogg/ogg.c [new file with mode: 0644]
lightmediascanner/src/plugins/pls/Makefile.am [new file with mode: 0644]
lightmediascanner/src/plugins/pls/pls.c [new file with mode: 0644]
lightmediascanner/src/plugins/video-dummy/Makefile.am [new file with mode: 0644]
lightmediascanner/src/plugins/video-dummy/video-dummy.c [new file with mode: 0644]

diff --git a/lightmediascanner/.gitignore b/lightmediascanner/.gitignore
new file mode 100644 (file)
index 0000000..5efcf6d
--- /dev/null
@@ -0,0 +1,26 @@
+*~
+*.o
+*.so
+.deps
+.libs
+*.la
+*.lo
+Makefile.in
+Makefile
+aclocal.m4
+autom4te.cache
+compile
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+lightmediascanner.pc
diff --git a/lightmediascanner/AUTHORS b/lightmediascanner/AUTHORS
new file mode 100644 (file)
index 0000000..53ff931
--- /dev/null
@@ -0,0 +1 @@
+Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
diff --git a/lightmediascanner/COPYING b/lightmediascanner/COPYING
new file mode 100644 (file)
index 0000000..8add30a
--- /dev/null
@@ -0,0 +1,504 @@
+                 GNU LESSER GENERAL PUBLIC LICENSE
+                      Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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 Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                           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 Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+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 other code 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.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  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, whereas the latter must
+be combined with the library in order to run.
+\f
+                 GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser 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.
+\f
+  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.
+\f
+  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.
+\f
+  6. As an exception to the Sections above, you may also combine 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) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) 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.
+
+    d) 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.
+
+    e) 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 materials to be 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.
+\f
+  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 with
+this License.
+\f
+  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 Lesser 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.
+\f
+  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
+\f
+           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 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 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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/lightmediascanner/ChangeLog b/lightmediascanner/ChangeLog
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lightmediascanner/INSTALL b/lightmediascanner/INSTALL
new file mode 100644 (file)
index 0000000..5458714
--- /dev/null
@@ -0,0 +1,234 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
+2006 Free Software Foundation, Inc.
+
+This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+Briefly, the shell commands `./configure; make; make install' should
+configure, build, and install this package.  The following
+more-detailed instructions are generic; see the `README' file for
+instructions specific to this package.
+
+   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 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.
+
+     Running `configure' might take a while.  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=c99 CFLAGS=-g 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 can use 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 `..'.
+
+   With a non-GNU `make', it is safer 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' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc.  You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' 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 option `--target=TYPE' 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
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for `CONFIG_SHELL' due to
+an Autoconf bug.  Until the bug is fixed you can use this workaround:
+
+     CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+`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/lightmediascanner/Makefile.am b/lightmediascanner/Makefile.am
new file mode 100644 (file)
index 0000000..a8f976f
--- /dev/null
@@ -0,0 +1,22 @@
+MAINTAINERCLEANFILES = \
+       Makefile.in \
+       aclocal.m4 \
+       compile \
+       config.guess \
+       config.h.in \
+       config.sub \
+       configure \
+       depcomp \
+       install-sh \
+       ltmain.sh \
+       missing
+
+SUBDIRS = src m4
+
+EXTRA_DIST = \
+       README \
+       AUTHORS \
+       COPYING
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = lightmediascanner.pc
diff --git a/lightmediascanner/NEWS b/lightmediascanner/NEWS
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lightmediascanner/README b/lightmediascanner/README
new file mode 100644 (file)
index 0000000..524425d
--- /dev/null
@@ -0,0 +1,17 @@
+                         LIGHT MEDIA SCANNER
+                         ===================
+
+Lightweight media scanner meant to be used in not-so-powerful devices,
+like embedded systems or old machines.
+
+Provides an optimized way to recursively scan directories, handling
+the parser in a child process, avoiding breaks of the main process
+when parsers break (quite common with such bad libs and tags).
+
+Parsers are plugins in the form of shared objects, so it's easy to add
+new without having to recompiling the scanner.
+
+The scanner will use SQLite3 to store file-mtime association, avoiding
+parsing files that are already up-to-date. This SQLite connection and
+the file id within the master table 'files' are handled to plugins for
+relationship with other tables.
diff --git a/lightmediascanner/autogen.sh b/lightmediascanner/autogen.sh
new file mode 100755 (executable)
index 0000000..5bbd4d9
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+rm -rf autom4te.cache
+rm -f aclocal.m4 ltmain.sh
+
+echo "Running aclocal..." ; aclocal $ACLOCAL_FLAGS -I m4 || exit 1
+echo "Running autoheader..." ; autoheader || exit 1
+echo "Running autoconf..." ; autoconf || exit 1
+echo "Running libtoolize..." ; (libtoolize --copy --automake || glibtoolize --automake) || exit 1
+echo "Running automake..." ; automake --add-missing --copy --gnu || exit 1
+
+if [ -z "$NOCONFIGURE" ]; then
+       ./configure "$@"
+fi
diff --git a/lightmediascanner/configure.ac b/lightmediascanner/configure.ac
new file mode 100644 (file)
index 0000000..8b613e1
--- /dev/null
@@ -0,0 +1,107 @@
+AC_INIT(lightmediascanner, 0.1.0.1, gustavo.barbieri@openbossa.org)
+AC_PREREQ(2.52)
+AC_CONFIG_SRCDIR(configure.ac)
+AC_CANONICAL_BUILD
+AC_CANONICAL_HOST
+AC_ISC_POSIX
+
+AM_INIT_AUTOMAKE(1.6 dist-bzip2)
+AM_CONFIG_HEADER(config.h)
+AC_SUBST(ACLOCAL_AMFLAGS, "-I m4")
+_XTERM_COLORS
+
+AC_PROG_CC
+AM_PROG_CC_STDC
+AM_PROG_CC_C_O
+AC_HEADER_STDC
+AC_C_CONST
+
+define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl
+AC_PROG_LIBTOOL
+
+VMAJ=`echo $PACKAGE_VERSION | awk -F. '{printf("%s", $1);}'`
+VMIN=`echo $PACKAGE_VERSION | awk -F. '{printf("%s", $2);}'`
+VMIC=`echo $PACKAGE_VERSION | awk -F. '{printf("%s", $3);}'`
+SNAP=`echo $PACKAGE_VERSION | awk -F. '{printf("%s", $4);}'`
+version_info=`expr $VMAJ + $VMIN`":$VMIC:$VMIN"
+AC_SUBST(version_info)
+
+pluginsdir="${libdir}/lightmediascanner/plugins"
+
+AC_SUBST(pluginsdir)
+AS_AC_EXPAND(PLUGINSDIR, $pluginsdir)
+AC_DEFINE_UNQUOTED(PLUGINSDIR, ["$PLUGINSDIR"], [Where plugins are installed.])
+
+AC_CHECK_FUNCS(realpath)
+
+# required modules
+PKG_CHECK_MODULES(SQLITE3, [sqlite3 >= 3.3])
+
+# plugins checks
+# plugins checks
+
+AM_CONDITIONAL(HAVE_ID3LIB, false)
+define([CHECK_MODULE_ID3LIB],
+[
+        AC_LMS_CHECK_PKG(ID3LIB, id3lib, [], [ID3LIB=false])
+])
+
+AM_CONDITIONAL(HAVE_VORBIS, false)
+define([CHECK_MODULE_OGG],
+[
+        AC_LMS_CHECK_PKG(VORBIS, vorbis, [], [OGG=false])
+])
+
+# plugins declarations
+AC_LMS_OPTIONAL_MODULE([dummy], true)
+AC_LMS_OPTIONAL_MODULE([jpeg], true)
+AC_LMS_OPTIONAL_MODULE([id3lib], true, [CHECK_MODULE_ID3LIB])
+AC_LMS_OPTIONAL_MODULE([video-dummy], true)
+AC_LMS_OPTIONAL_MODULE([m3u], true)
+AC_LMS_OPTIONAL_MODULE([ogg], true, [CHECK_MODULE_OGG])
+AC_LMS_OPTIONAL_MODULE([pls], true)
+
+AC_OUTPUT([
+lightmediascanner.pc
+Makefile
+m4/Makefile
+src/Makefile
+src/bin/Makefile
+src/lib/Makefile
+src/plugins/Makefile
+src/plugins/dummy/Makefile
+src/plugins/jpeg/Makefile
+src/plugins/id3lib/Makefile
+src/plugins/video-dummy/Makefile
+src/plugins/m3u/Makefile
+src/plugins/ogg/Makefile
+src/plugins/pls/Makefile
+])
+
+
+# report
+txt_strip() {
+        echo "[$]@" | sed -e 's/^[[ \t]]*\([[^ \t]]*\)[[ \t]]*$/\1/g'
+}
+
+MODS=""
+for mod in $OPTIONAL_MODULES; do
+        MODS="$MODS ${COLOR_HGREEN}+$mod${COLOR_END}"
+done
+MODS=$(txt_strip $MODS)
+
+UNUSED_MODS=""
+for mod in $UNUSED_OPTIONAL_MODULES; do
+        UNUSED_MODS="$UNUSED_MODS ${COLOR_HRED}-$mod${COLOR_END}"
+done
+UNUSED_MODS=$(txt_strip $UNUSED_MODS)
+
+cat << SUMMARY_EOF
+
+Summary:
+ * project........: $PACKAGE $VERSION
+ * prefix.........: $(txt_strip $prefix)
+ * CFLAGS.........: $(txt_strip $CFLAGS)
+SUMMARY_EOF
+
+echo -e " * modules........: $MODS $UNUSED_MODS"
diff --git a/lightmediascanner/lightmediascanner.pc.in b/lightmediascanner/lightmediascanner.pc.in
new file mode 100644 (file)
index 0000000..9dad103
--- /dev/null
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: lightmediascanner
+Description: Lightweight Media Scanner
+Version: @VERSION@
+Libs: -L${libdir} -llightmediascanner -lsqlite3
+Cflags: -I${includedir}
diff --git a/lightmediascanner/m4/Makefile.am b/lightmediascanner/m4/Makefile.am
new file mode 100644 (file)
index 0000000..c187774
--- /dev/null
@@ -0,0 +1,4 @@
+MAINTAINERCLEANFILES = Makefile.in
+EXTRA_DIST = \
+       as-expand.m4 \
+       ac-plugins.m4
diff --git a/lightmediascanner/m4/ac-plugins.m4 b/lightmediascanner/m4/ac-plugins.m4
new file mode 100644 (file)
index 0000000..dd4f6a9
--- /dev/null
@@ -0,0 +1,169 @@
+dnl _XTERM_COLORS
+define([_XTERM_COLORS],
+[
+        # Check for XTerm and define some colors
+        if test "x$TERM" = "xxterm"; then
+                COLOR_PREF="\0033\0133"
+                COLOR_H="${COLOR_PREF}1m"
+                COLOR_HGREEN="${COLOR_PREF}1;32m"
+                COLOR_HRED="${COLOR_PREF}1;31m"
+                COLOR_GREEN="${COLOR_PREF}32m"
+                COLOR_RED="${COLOR_PREF}31m"
+                COLOR_YELLOW="${COLOR_PREF}1;33m"
+                COLOR_END="${COLOR_PREF}0m"
+        else
+                COLOR_H=""
+                COLOR_HGREEN=""
+                COLOR_HRED=""
+                COLOR_GREEN=""
+                COLOR_RED=""
+                COLOR_YELLOW=""
+                COLOR_END=""
+        fi
+])
+
+dnl AC_LMS_CHECK_PKG(name, lib [>= version], [action-if, [action-not]])
+dnl   improved version of PKG_CHECK_MODULES, it does the same checking
+dnl   and defines HAVE_[name]=yes/no and also exports
+dnl   [name]_CFLAGS and [name]_LIBS.
+dnl
+dnl   if action-not isn't provided, AC_MSG_ERROR will be used.
+dnl
+dnl   Checks:
+dnl       lib >= version
+dnl
+dnl   Provides:
+dnl       - HAVE_[name]=yes|no
+dnl       - [name]_CFLAGS: if HAVE_[name]=yes
+dnl       - [name]_LIBS: if HAVE_[name]=yes
+dnl       - [name]_VERSION: if HAVE_[name]=yes
+dnl
+AC_DEFUN([AC_LMS_CHECK_PKG],
+[
+# ----------------------------------------------------------------------
+# BEGIN: Check library with pkg-config: $1 (pkg-config=$2)
+#
+
+        PKG_CHECK_MODULES([$1], [$2],
+                          [
+                                HAVE_[$1]=yes
+                                [pkg_name]=$(echo "[$2]" | cut -d\   -f1)
+                                [$1]_VERSION=$($PKG_CONFIG --modversion $pkg_name)
+                                AC_SUBST([$1]_VERSION)
+                                AC_SUBST([$1]_CFLAGS)
+                                AC_SUBST([$1]_LIBS)
+                                ifelse([$3], , :, [$3])
+                          ],
+                          [
+                                HAVE_[$1]=no
+                                ifelse([$4], , AC_MSG_ERROR(you need [$2] development installed!), AC_MSG_RESULT(no); [$4])
+                          ])
+        AM_CONDITIONAL(HAVE_[$1], test x$HAVE_[$1] = xyes)
+        AC_SUBST(HAVE_[$1])
+        if test x$HAVE_[$1] = xyes; then
+                AC_DEFINE_UNQUOTED(HAVE_[$1], 1, Package [$1] ($2) found.)
+        fi
+
+#
+# END: Check library with pkg-config: $1 (pkg-config=$2)
+# ----------------------------------------------------------------------
+])
+
+dnl AC_LMS_OPTIONAL_MODULE(name, [initial-status, [check-if-enabled]])
+dnl   Defines configure argument --<enable|disable>-[name] to enable an
+dnl   optional module called 'name'.
+dnl
+dnl   If initial-status is true, then it's enabled by default and option
+dnl   will be called --disable-[name], otherwise it's disabled and option
+dnl   is --enable-[name].
+dnl
+dnl   If module is enabled, then check-if-enabled will be executed. This
+dnl   may change the contents of shell variable NAME (uppercase version of
+dnl   name, with underscores instead of dashed) to something different than
+dnl   "true" to disable module.
+dnl
+dnl Parameters:
+dnl     - name: module name to use. It will be converted to have dashes (-)
+dnl          instead of underscores, and will be in lowercase.
+dnl     - initial-status: true or false, states if module is enabled or
+dnl          disabled by default.
+dnl     - check-if-enabled: macro to be expanded inside check for enabled
+dnl           module.
+dnl
+dnl Provides:
+dnl     - USE_MODULE_[name]=true|false [make, shell]
+dnl     - USE_MODULE_[name]=1 if enabled [config.h]
+dnl
+AC_DEFUN([AC_LMS_OPTIONAL_MODULE],
+[
+# ----------------------------------------------------------------------
+# BEGIN: Check for optional module: $1 (default: $2)
+#
+        m4_pushdef([MODNAME], [m4_bpatsubst(m4_toupper([$1]), -, _)])dnl
+        m4_pushdef([modname_opt], [m4_bpatsubst(m4_tolower([$1]), _, -)])
+        m4_pushdef([INITVAL], [m4_default([$2], [false])])dnl
+        m4_pushdef([ENABLE_HELP], AS_HELP_STRING([--enable-modname_opt],
+                               [enable optional module modname_opt. Default is disabled.])
+                )dnl
+        m4_pushdef([DISABLE_HELP], AS_HELP_STRING([--disable-modname_opt],
+                               [disable optional module modname_opt. Default is enabled.])
+                )dnl
+        m4_pushdef([HELP_STR], m4_if(INITVAL, [true], [DISABLE_HELP], [ENABLE_HELP]))dnl
+        m4_pushdef([NOT_INITVAL], m4_if(INITVAL, [true], [false], [true]))dnl
+
+        USING_MODULES=1
+
+        MODNAME=INITVAL
+        AC_ARG_ENABLE(modname_opt, HELP_STR, [MODNAME=${enableval:-NOT_INITVAL}])
+        if test x[$]MODNAME = xyes || test x[$]MODNAME = x1; then
+                MODNAME=true
+        fi
+        if test x[$]MODNAME = xno || test x[$]MODNAME = x0; then
+                MODNAME=false
+        fi
+
+        USE_MODULE_[]MODNAME=[$]MODNAME
+
+        _XTERM_COLORS
+
+        # Check list for optional module $1
+        if test x[$]MODNAME = xtrue; then
+                ifelse([$3], , , [
+echo
+echo "checking optional module modname_opt:"
+# BEGIN: User checks
+$3
+# END: User checks
+if test x[$]MODNAME = xfalse; then
+        echo -e "optional module modname_opt ${COLOR_HRED}failed${COLOR_END} checks."
+else
+        echo -e "optional module modname_opt passed checks."
+fi
+echo
+])
+
+                if test x[$]MODNAME = xfalse; then
+                        echo -e "${COLOR_YELLOW}Warning:${COLOR_END} optional module ${COLOR_H}modname_opt${COLOR_END} disabled by extra checks."
+                fi
+        fi
+
+        # Check if user checks succeeded
+        if test x[$]MODNAME = xtrue; then
+                [OPTIONAL_MODULES]="$[OPTIONAL_MODULES] modname_opt"
+                AC_DEFINE_UNQUOTED(USE_MODULE_[]MODNAME, 1, Use module modname_opt)
+        else
+                [UNUSED_OPTIONAL_MODULES]="$[UNUSED_OPTIONAL_MODULES] modname_opt"
+        fi
+
+        AM_CONDITIONAL(USE_MODULE_[]MODNAME, test x[$]MODNAME = xtrue)
+        AC_SUBST(USE_MODULE_[]MODNAME)
+
+        m4_popdef([HELP_STR])dnl
+        m4_popdef([DISABLE_HELP])dnl
+        m4_popdef([ENABLE_HELP])dnl
+        m4_popdef([INITVAL])dnl
+        m4_popdef([MODNAME])
+#
+# END: Check for optional module: $1 ($2)
+# ----------------------------------------------------------------------
+])
diff --git a/lightmediascanner/m4/as-expand.m4 b/lightmediascanner/m4/as-expand.m4
new file mode 100644 (file)
index 0000000..7b7e4a8
--- /dev/null
@@ -0,0 +1,43 @@
+dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR)
+dnl
+dnl example
+dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir)
+dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local
+
+AC_DEFUN([AS_AC_EXPAND],
+[
+  EXP_VAR=[$1]
+  FROM_VAR=[$2]
+
+  dnl first expand prefix and exec_prefix if necessary
+  prefix_save=$prefix
+  exec_prefix_save=$exec_prefix
+
+  dnl if no prefix given, then use /usr/local, the default prefix
+  if test "x$prefix" = "xNONE"; then
+    prefix=$ac_default_prefix
+  fi
+  dnl if no exec_prefix given, then use prefix
+  if test "x$exec_prefix" = "xNONE"; then
+    exec_prefix=$prefix
+  fi
+
+  full_var="$FROM_VAR"
+  dnl loop until it doesn't change anymore
+  while true; do
+    new_full_var="`eval echo $full_var`"
+    dnl if test "x$new_full_var" = "x$full_var"; then
+    if test "x${new_full_var:0:1}" != "x\$"; then
+        break;
+    fi
+    full_var=$new_full_var
+  done
+
+  dnl clean up
+  full_var=$new_full_var
+  AC_SUBST([$1], "$full_var")
+
+  dnl restore prefix and exec_prefix
+  prefix=$prefix_save
+  exec_prefix=$exec_prefix_save
+])
diff --git a/lightmediascanner/src/Makefile.am b/lightmediascanner/src/Makefile.am
new file mode 100644 (file)
index 0000000..e55545e
--- /dev/null
@@ -0,0 +1,3 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+SUBDIRS = lib bin plugins
diff --git a/lightmediascanner/src/bin/.gitignore b/lightmediascanner/src/bin/.gitignore
new file mode 100644 (file)
index 0000000..9daeafb
--- /dev/null
@@ -0,0 +1 @@
+test
diff --git a/lightmediascanner/src/bin/Makefile.am b/lightmediascanner/src/bin/Makefile.am
new file mode 100644 (file)
index 0000000..d6b7e4f
--- /dev/null
@@ -0,0 +1,12 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/src/lib
+
+noinst_PROGRAMS = \
+       test
+
+
+test_SOURCES = test.c
+test_LDADD = $(top_builddir)/src/lib/liblightmediascanner.la @SQLITE3_LIBS@
+test_DEPENDENCIES = $(top_builddir)/src/lib/liblightmediascanner.la
diff --git a/lightmediascanner/src/bin/test.c b/lightmediascanner/src/bin/test.c
new file mode 100644 (file)
index 0000000..cb7ab52
--- /dev/null
@@ -0,0 +1,83 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <lightmediascanner.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+void
+usage(const char *prgname)
+{
+    fprintf(stderr,
+            "Usage:\n"
+            "\t%s <commit-interval> <slave-timeout> <db-path> <parser> "
+            "<charset> <scan-path>\n"
+            "\n",
+            prgname);
+}
+
+int
+main(int argc, char *argv[])
+{
+    char *db_path, *parser_name, *charset, *scan_path;
+    lms_t *lms;
+    lms_plugin_t *parser;
+    int commit_interval, slave_timeout;
+
+    if (argc < 6) {
+        usage(argv[0]);
+        return 1;
+    }
+
+    commit_interval = atoi(argv[1]);
+    slave_timeout = atoi(argv[2]);
+    db_path = argv[3];
+    parser_name = argv[4];
+    charset = argv[5];
+    scan_path = argv[6];
+
+    lms = lms_new(db_path);
+    if (!lms) {
+        fprintf(stderr,
+                "ERROR: could not create light media scanner for DB \"%s\".\n",
+                db_path);
+        return -1;
+    }
+
+    lms_set_commit_interval(lms, commit_interval);
+    lms_set_slave_timeout(lms, slave_timeout);
+
+    parser = lms_parser_find_and_add(lms, parser_name);
+    if (!parser) {
+        fprintf(stderr, "ERROR: could not create parser \"%s\".\n",
+                parser_name);
+        lms_free(lms);
+        return -2;
+    }
+
+    if (lms_charset_add(lms, charset) != 0) {
+        fprintf(stderr, "ERROR: could not add charset '%s'\n", charset);
+        lms_free(lms);
+        return -3;
+    }
+
+    if (lms_check(lms, scan_path) != 0) {
+        fprintf(stderr, "ERROR: checking \"%s\".\n", scan_path);
+        lms_free(lms);
+        return -4;
+    }
+
+    if (lms_process(lms, scan_path) != 0) {
+        fprintf(stderr, "ERROR: processing \"%s\".\n", scan_path);
+        lms_free(lms);
+        return -5;
+    }
+
+    if (lms_free(lms) != 0) {
+        fprintf(stderr, "ERROR: could not close light media scanner.\n");
+        return -6;
+    }
+
+    return 0;
+}
diff --git a/lightmediascanner/src/lib/Makefile.am b/lightmediascanner/src/lib/Makefile.am
new file mode 100644 (file)
index 0000000..6c30db5
--- /dev/null
@@ -0,0 +1,28 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib
+
+include_HEADERS = \
+       lightmediascanner.h \
+       lightmediascanner_plugin.h \
+       lightmediascanner_utils.h \
+       lightmediascanner_db.h \
+       lightmediascanner_charset_conv.h
+noinst_HEADERS = lightmediascanner_private.h lightmediascanner_db_private.h
+
+lib_LTLIBRARIES = liblightmediascanner.la
+
+liblightmediascanner_la_SOURCES = \
+       lightmediascanner.c \
+       lightmediascanner_utils.c \
+       lightmediascanner_charset_conv.c \
+       lightmediascanner_process.c \
+       lightmediascanner_check.c \
+       lightmediascanner_db_common.c \
+       lightmediascanner_db_image.c \
+       lightmediascanner_db_audio.c \
+       lightmediascanner_db_video.c \
+       lightmediascanner_db_playlist.c
+
+liblightmediascanner_la_LIBADD = -ldl @SQLITE3_LIBS@
+liblightmediascanner_la_LDFLAGS = -version-info @version_info@
diff --git a/lightmediascanner/src/lib/lightmediascanner.c b/lightmediascanner/src/lib/lightmediascanner.c
new file mode 100644 (file)
index 0000000..e8c9a67
--- /dev/null
@@ -0,0 +1,353 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lightmediascanner.h"
+#include "lightmediascanner_private.h"
+#include "lightmediascanner_plugin.h"
+
+#define DEFAULT_SLAVE_TIMEOUT 1000
+#define DEFAULT_COMMIT_INTERVAL 100
+
+static int
+_parser_load(struct parser *p, const char *so_path)
+{
+    lms_plugin_t *(*plugin_open)(void);
+    char *errmsg;
+
+    memset(p, 0, sizeof(*p));
+
+    p->dl_handle = dlopen(so_path, RTLD_NOW | RTLD_LOCAL);
+    errmsg = dlerror();
+    if (errmsg) {
+        fprintf(stderr, "ERROR: could not dlopen() %s\n", errmsg);
+        return -1;
+    }
+
+    plugin_open = dlsym(p->dl_handle, "lms_plugin_open");
+    errmsg = dlerror();
+    if (errmsg) {
+        fprintf(stderr, "ERROR: could not find plugin entry point %s\n",
+                errmsg);
+        return -2;
+    }
+
+    p->so_path = strdup(so_path);
+    if (!p->so_path) {
+        perror("strdup");
+        return -3;
+    }
+
+    p->plugin = plugin_open();
+    if (!p->plugin) {
+        fprintf(stderr, "ERROR: plugin \"%s\" failed to init.\n", so_path);
+        return -4;
+    }
+
+    return 0;
+}
+
+static int
+_parser_unload(struct parser *p)
+{
+    int r;
+
+    r = 0;
+    if (p->plugin) {
+        if (p->plugin->close(p->plugin) != 0) {
+            fprintf(stderr, "ERROR: plugin \"%s\" failed to deinit.\n",
+                    p->so_path);
+            r -= 1;
+        }
+    }
+
+    if (p->dl_handle) {
+        char *errmsg;
+
+        dlclose(p->dl_handle);
+        errmsg = dlerror();
+        if (errmsg) {
+            fprintf(stderr, "ERROR: could not dlclose() plugin \"%s\": %s\n",
+                    errmsg, p->so_path);
+            r -= 1;
+        }
+    }
+
+    if (p->so_path)
+        free(p->so_path);
+
+    return r;
+}
+
+
+/***********************************************************************
+ * Public API.
+ ***********************************************************************/
+lms_t *
+lms_new(const char *db_path)
+{
+    lms_t *lms;
+
+    lms = calloc(1, sizeof(lms_t));
+    if (!lms) {
+        perror("calloc");
+        return NULL;
+    }
+
+    lms->cs_conv = lms_charset_conv_new();
+    if (!lms->cs_conv) {
+        free(lms);
+        return NULL;
+    }
+
+    lms->commit_interval = DEFAULT_COMMIT_INTERVAL;
+    lms->slave_timeout = DEFAULT_SLAVE_TIMEOUT;
+    lms->db_path = strdup(db_path);
+    if (!lms->db_path) {
+        perror("strdup");
+        lms_charset_conv_free(lms->cs_conv);
+        free(lms);
+        return NULL;
+    }
+
+    return lms;
+}
+
+int
+lms_free(lms_t *lms)
+{
+    int i;
+
+    if (!lms)
+        return 0;
+
+    if (lms->is_processing)
+        return -1;
+
+    if (lms->parsers) {
+        for (i = 0; i < lms->n_parsers; i++)
+            _parser_unload(lms->parsers + i);
+
+        free(lms->parsers);
+    }
+
+    free(lms->db_path);
+    lms_charset_conv_free(lms->cs_conv);
+    free(lms);
+    return 0;
+}
+
+lms_plugin_t *
+lms_parser_add(lms_t *lms, const char *so_path)
+{
+    struct parser *parser;
+
+    if (!lms)
+        return NULL;
+
+    if (!so_path)
+        return NULL;
+
+    if (lms->is_processing) {
+        fprintf(stderr, "ERROR: do not add parsers while it's processing.\n");
+        return NULL;
+    }
+
+    lms->parsers = realloc(lms->parsers,
+                           (lms->n_parsers + 1) * sizeof(struct parser));
+    if (!lms->parsers) {
+        perror("realloc");
+        return NULL;
+    }
+
+    parser = lms->parsers + lms->n_parsers;
+    if (_parser_load(parser, so_path) != 0) {
+        _parser_unload(parser);
+        return NULL;
+    }
+
+    lms->n_parsers++;
+    return parser->plugin;
+}
+
+lms_plugin_t *
+lms_parser_find_and_add(lms_t *lms, const char *name)
+{
+    char so_path[PATH_MAX];
+
+    if (!lms)
+        return NULL;
+    if (!name)
+        return NULL;
+
+    snprintf(so_path, sizeof(so_path), "%s/%s.so", PLUGINSDIR, name);
+    return lms_parser_add(lms, so_path);
+}
+
+int
+lms_parser_del_int(lms_t *lms, int i)
+{
+    struct parser *parser;
+
+    parser = lms->parsers + i;
+    _parser_unload(parser);
+    lms->n_parsers--;
+
+    if (lms->n_parsers == 0) {
+        free(lms->parsers);
+        lms->parsers = NULL;
+        return 0;
+    } else {
+        int dif;
+
+        dif = lms->n_parsers - i;
+        if (dif)
+            lms->parsers = memmove(parser, parser + 1,
+                                   dif * sizeof(struct parser));
+
+        lms->parsers = realloc(lms->parsers,
+                               lms->n_parsers * sizeof(struct parser));
+        if (!lms->parsers) {
+            lms->n_parsers = 0;
+            return -1;
+        }
+
+        return 0;
+    }
+}
+
+int
+lms_parser_del(lms_t *lms, lms_plugin_t *handle)
+{
+    int i;
+
+    if (!lms)
+        return -1;
+    if (!handle)
+        return -2;
+    if (!lms->parsers)
+        return -3;
+    if (lms->is_processing) {
+        fprintf(stderr, "ERROR: do not del parsers while it's processing.\n");
+        return -4;
+    }
+
+    for (i = 0; i < lms->n_parsers; i++)
+        if (lms->parsers[i].plugin == handle)
+            return lms_parser_del_int(lms, i);
+
+    return -3;
+}
+
+int
+lms_is_processing(const lms_t *lms)
+{
+    if (!lms) {
+        fprintf(stderr, "ERROR: lms_is_processing(NULL)\n");
+        return -1;
+    }
+
+    return lms->is_processing;
+}
+
+const char *
+lms_get_db_path(const lms_t *lms)
+{
+    if (!lms) {
+        fprintf(stderr, "ERROR: lms_get_db_path(NULL)\n");
+        return NULL;
+    }
+
+    return lms->db_path;
+}
+
+int
+lms_get_slave_timeout(const lms_t *lms)
+{
+    if (!lms) {
+        fprintf(stderr, "ERROR: lms_get_slave_timeout(NULL)\n");
+        return -1;
+    }
+
+    return lms->slave_timeout;
+}
+
+void lms_set_slave_timeout(lms_t *lms, int ms)
+{
+    if (!lms) {
+        fprintf(stderr, "ERROR: lms_set_slave_timeout(NULL, %d)\n", ms);
+        return;
+    }
+
+    lms->slave_timeout = ms;
+}
+
+unsigned int
+lms_get_commit_interval(const lms_t *lms)
+{
+    if (!lms) {
+        fprintf(stderr, "ERROR: lms_get_commit_interval(NULL)\n");
+        return (unsigned int)-1;
+    }
+
+    return lms->commit_interval;
+}
+
+void
+lms_set_commit_interval(lms_t *lms, unsigned int transactions)
+{
+    if (!lms) {
+        fprintf(stderr, "ERROR: lms_set_commit_interval(NULL, %u)\n",
+                transactions);
+        return;
+    }
+
+    lms->commit_interval = transactions;
+}
+
+int
+lms_charset_add(lms_t *lms, const char *charset)
+{
+    if (!lms) {
+        fprintf(stderr, "ERROR: lms_charset_add(NULL)\n");
+        return -1;
+    }
+
+    return lms_charset_conv_add(lms->cs_conv, charset);
+}
+
+int
+lms_charset_del(lms_t *lms, const char *charset)
+{
+    if (!lms) {
+        fprintf(stderr, "ERROR: lms_charset_del(NULL)\n");
+        return -1;
+    }
+
+    return lms_charset_conv_del(lms->cs_conv, charset);
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner.h b/lightmediascanner/src/lib/lightmediascanner.h
new file mode 100644 (file)
index 0000000..19cb15c
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifndef _LIGHTMEDIASCANNER_H_
+#define _LIGHTMEDIASCANNER_H_ 1
+
+#ifdef API
+#undef API
+#endif
+
+#ifdef __GNUC__
+# if __GNUC__ >= 4
+#  define API __attribute__ ((visibility("default")))
+#  define GNUC_NULL_TERMINATED __attribute__((__sentinel__))
+# else
+#  define API
+#  define GNUC_NULL_TERMINATED
+# endif
+# if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96)
+#  define GNUC_PURE __attribute__((__pure__))
+#  define GNUC_MALLOC __attribute__((__malloc__))
+#  define GNUC_CONST __attribute__((__const__))
+#  define GNUC_UNUSED __attribute__((__unused__))
+# else
+#  define GNUC_PURE
+#  define GNUC_MALLOC
+#  define GNUC_NORETURN
+#  define GNUC_CONST
+#  define GNUC_UNUSED
+# endif
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#  define GNUC_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#  define GNUC_NON_NULL(...) __attribute__((nonnull(__VA_ARGS__)))
+# else
+#  define GNUC_WARN_UNUSED_RESULT
+#  define GNUC_NON_NULL(...)
+# endif
+#else
+#  define API
+#  define GNUC_NULL_TERMINATED
+#  define GNUC_PURE
+#  define GNUC_MALLOC
+#  define GNUC_CONST
+#  define GNUC_UNUSED
+#  define GNUC_WARN_UNUSED_RESULT
+#  define GNUC_NON_NULL(...)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    typedef struct lms lms_t;
+    typedef struct lms_plugin lms_plugin_t;
+
+    API lms_t *lms_new(const char *db_path) GNUC_MALLOC GNUC_WARN_UNUSED_RESULT;
+    API int lms_free(lms_t *lms) GNUC_NON_NULL(1);
+    API int lms_process(lms_t *lms, const char *top_path) GNUC_NON_NULL(1, 2);
+    API int lms_check(lms_t *lms, const char *top_path) GNUC_NON_NULL(1, 2);
+    API const char *lms_get_db_path(const lms_t *lms) GNUC_NON_NULL(1);
+    API int lms_is_processing(const lms_t *lms) GNUC_PURE GNUC_NON_NULL(1);
+    API int lms_get_slave_timeout(const lms_t *lms) GNUC_NON_NULL(1);
+    API void lms_set_slave_timeout(lms_t *lms, int ms) GNUC_NON_NULL(1);
+    API unsigned int lms_get_commit_interval(const lms_t *lms) GNUC_NON_NULL(1);
+    API void lms_set_commit_interval(lms_t *lms, unsigned int transactions) GNUC_NON_NULL(1);
+
+    API lms_plugin_t *lms_parser_add(lms_t *lms, const char *so_path) GNUC_NON_NULL(1, 2);
+    API lms_plugin_t *lms_parser_find_and_add(lms_t *lms, const char *name) GNUC_NON_NULL(1, 2);
+    API int lms_parser_del(lms_t *lms, lms_plugin_t *handle) GNUC_NON_NULL(1, 2);
+
+    API int lms_charset_add(lms_t *lms, const char *charset) GNUC_NON_NULL(1, 2);
+    API int lms_charset_del(lms_t *lms, const char *charset) GNUC_NON_NULL(1, 2);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _LIGHTMEDIASCANNER_H_ */
diff --git a/lightmediascanner/src/lib/lightmediascanner_charset_conv.c b/lightmediascanner/src/lib/lightmediascanner_charset_conv.c
new file mode 100644 (file)
index 0000000..26d9e8b
--- /dev/null
@@ -0,0 +1,289 @@
+#include "lightmediascanner_charset_conv.h"
+#include <iconv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+struct lms_charset_conv {
+    iconv_t check;
+    iconv_t fallback;
+    unsigned int size;
+    iconv_t *convs;
+    char **names;
+};
+
+lms_charset_conv_t *
+lms_charset_conv_new(void)
+{
+    lms_charset_conv_t *lcc;
+
+    lcc = malloc(sizeof(*lcc));
+    if (!lcc) {
+        perror("malloc");
+        return NULL;
+    }
+
+    lcc->check = iconv_open("UTF-8", "UTF-8");
+    if (lcc->check == (iconv_t)-1) {
+        perror("ERROR: could not create conversion checker");
+        goto error_check;
+    }
+
+    lcc->fallback = iconv_open("UTF-8//IGNORE", "UTF-8");
+    if (lcc->fallback == (iconv_t)-1) {
+        perror("ERROR: could not create conversion fallback");
+        goto error_fallback;
+    }
+
+    lcc->size = 0;
+    lcc->convs = NULL;
+    lcc->names = NULL;
+    return lcc;
+
+  error_fallback:
+    iconv_close(lcc->check);
+  error_check:
+    free(lcc);
+
+    return NULL;
+}
+
+void
+lms_charset_conv_free(lms_charset_conv_t *lcc)
+{
+    int i;
+
+    if (!lcc)
+        return;
+
+    iconv_close(lcc->check);
+    iconv_close(lcc->fallback);
+
+    for (i = 0; i < lcc->size; i++) {
+        iconv_close(lcc->convs[i]);
+        free(lcc->names[i]);
+    }
+
+    if (lcc->convs)
+        free(lcc->convs);
+    if (lcc->names)
+        free(lcc->names);
+    free(lcc);
+}
+
+int
+lms_charset_conv_add(lms_charset_conv_t *lcc, const char *charset)
+{
+    iconv_t cd, *convs;
+    char **names;
+    int idx, ns;
+
+    if (!lcc)
+        return -1;
+
+    if (!charset)
+        return -2;
+
+    cd = iconv_open("UTF-8", charset);
+    if (cd == (iconv_t)-1) {
+        fprintf(stderr, "ERROR: could not add conversion charset '%s': %s\n",
+                charset, strerror(errno));
+        return -3;
+    }
+
+    idx = lcc->size;
+    ns = lcc->size + 1;
+
+    convs = realloc(lcc->convs, ns * sizeof(*convs));
+    if (!convs)
+        goto realloc_error;
+    lcc->convs = convs;
+    lcc->convs[idx] = cd;
+
+    names = realloc(lcc->names, ns * sizeof(*names));
+    if (!names)
+        goto realloc_error;
+    lcc->names = names;
+    lcc->names[idx] = strdup(charset);
+    if (!lcc->names[idx])
+        goto realloc_error;
+
+    lcc->size = ns;
+    return 0;
+
+  realloc_error:
+    perror("realloc");
+    iconv_close(cd);
+    return -4;
+}
+
+static int
+_find(const lms_charset_conv_t *lcc, const char *charset)
+{
+    int i;
+
+    for (i = 0; i < lcc->size; i++)
+        if (strcmp(lcc->names[i], charset) == 0)
+            return i;
+
+    return -1;
+}
+
+int
+lms_charset_conv_del(lms_charset_conv_t *lcc, const char *charset)
+{
+    iconv_t *convs;
+    char **names;
+    int idx;
+
+    if (!lcc)
+        return -1;
+
+    if (!charset)
+        return -2;
+
+    idx = _find(lcc, charset);
+    if (idx < 0) {
+        fprintf(stderr, "ERROR: could not find charset '%s'\n", charset);
+        return -3;
+    }
+
+    iconv_close(lcc->convs[idx]);
+    free(lcc->names[idx]);
+
+    lcc->size--;
+    for (; idx < lcc->size; idx++) {
+        lcc->convs[idx] = lcc->convs[idx + 1];
+        lcc->names[idx] = lcc->names[idx + 1];
+    }
+
+    convs = realloc(lcc->convs, lcc->size * sizeof(*convs));
+    if (convs)
+        lcc->convs = convs;
+    else
+        perror("could not realloc 'convs'");
+
+    names = realloc(lcc->names, lcc->size * sizeof(*names));
+    if (names)
+        lcc->names = names;
+    else
+        perror("could not realloc 'names'");
+
+    return 0;
+}
+
+static int
+_check(lms_charset_conv_t *lcc, const char *istr, unsigned int ilen, char *ostr, unsigned int olen)
+{
+    char *inbuf, *outbuf;
+    size_t r, inlen, outlen;
+
+    inbuf = (char *)istr;
+    inlen = ilen;
+    outbuf = ostr;
+    outlen = olen;
+
+    iconv(lcc->check, NULL, NULL, NULL, NULL);
+    r = iconv(lcc->check, &inbuf, &inlen, &outbuf, &outlen);
+    if (r == (size_t)-1)
+        return -1;
+    else
+        return 0;
+}
+
+static int
+_conv(iconv_t cd, char **p_str, unsigned int *p_len, char *ostr, unsigned int olen)
+{
+    char *inbuf, *outbuf;
+    size_t r, inlen, outlen;
+
+    inbuf = *p_str;
+    inlen = *p_len;
+    outbuf = ostr;
+    outlen = olen;
+
+    iconv(cd, NULL, NULL, NULL, NULL);
+    r = iconv(cd, &inbuf, &inlen, &outbuf, &outlen);
+    if (r == (size_t)-1)
+        return -1;
+
+    *p_len = olen - outlen;
+    free(*p_str);
+    *p_str = ostr;
+
+    outbuf = realloc(*p_str, *p_len + 1);
+    if (!outbuf)
+        perror("realloc");
+    else
+        *p_str = outbuf;
+
+    (*p_str)[*p_len] = '\0';
+
+    return 0;
+}
+
+int
+lms_charset_conv(lms_charset_conv_t *lcc, char **p_str, unsigned int *p_len)
+{
+    char *outstr;
+    int i, outlen;
+
+    if (!lcc)
+        return -1;
+    if (!p_str)
+        return -2;
+    if (!p_len)
+        return -3;
+    if (!*p_str || !*p_len)
+        return 0;
+
+    outlen = 2 * *p_len;
+    outstr = malloc(outlen + 1);
+    if (!outstr) {
+        perror("malloc");
+        return -4;
+    }
+
+    if (_check(lcc, *p_str, *p_len, outstr, outlen) == 0) {
+        free(outstr);
+        return 0;
+    }
+
+    for (i = 0; i < lcc->size; i++)
+        if (_conv(lcc->convs[i], p_str, p_len, outstr, outlen) == 0)
+            return 0;
+
+    fprintf(stderr,
+            "WARNING: could not convert '%*s' to any charset, use fallback\n",
+            *p_len, *p_str);
+    i = _conv(lcc->fallback, p_str, p_len, outstr, outlen);
+    if (i < 0) {
+        memset(*p_str, '?', *p_len);
+        free(outstr);
+    }
+    return i;
+}
+
+int
+lms_charset_conv_check(lms_charset_conv_t *lcc, const char *str, unsigned int len)
+{
+    char *outstr;
+    int r, outlen;
+
+    if (!lcc)
+        return -1;
+    if (!str || !len)
+        return 0;
+
+    outlen = 2 * len;
+    outstr = malloc(outlen);
+    if (!outstr) {
+        perror("malloc");
+        return -2;
+    }
+
+    r = _check(lcc, str, len, outstr, outlen);
+    free(outstr);
+    return r;
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner_charset_conv.h b/lightmediascanner/src/lib/lightmediascanner_charset_conv.h
new file mode 100644 (file)
index 0000000..14a8fed
--- /dev/null
@@ -0,0 +1,79 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifndef _LIGHTMEDIASCANNER_CHARSET_CONV_H_
+#define _LIGHTMEDIASCANNER_CHARSET_CONV_H_ 1
+
+#ifdef GNUC_MALLOC
+#undef GNUC_MALLOC
+#endif
+#ifdef GNUC_WARN_UNUSED_RESULT
+#undef GNUC_WARN_UNUSED_RESULT
+#endif
+#ifdef GNUC_NON_NULL
+#undef GNUC_NON_NULL
+#endif
+#ifdef API
+#undef API
+#endif
+
+#ifdef __GNUC__
+# if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96)
+#  define GNUC_MALLOC __attribute__((__malloc__))
+# else
+#  define GNUC_MALLOC
+# endif
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#  define GNUC_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#  define GNUC_NON_NULL(...) __attribute__((nonnull(__VA_ARGS__)))
+# else
+#  define GNUC_WARN_UNUSED_RESULT
+#  define GNUC_NON_NULL(...)
+# endif
+# if __GNUC__ >= 4
+#  define API __attribute__ ((visibility("default")))
+# else
+#  define API
+# endif
+#else
+#  define GNUC_MALLOC
+#  define GNUC_WARN_UNUSED_RESULT
+#  define GNUC_NON_NULL(...)
+#  define API
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    typedef struct lms_charset_conv lms_charset_conv_t;
+
+    API lms_charset_conv_t *lms_charset_conv_new(void) GNUC_MALLOC GNUC_WARN_UNUSED_RESULT;
+    API void lms_charset_conv_free(lms_charset_conv_t *lcc) GNUC_NON_NULL(1);
+    API int lms_charset_conv_add(lms_charset_conv_t *lcc, const char *charset) GNUC_NON_NULL(1, 2);
+    API int lms_charset_conv_del(lms_charset_conv_t *lcc, const char *charset) GNUC_NON_NULL(1, 2);
+
+    API int lms_charset_conv(lms_charset_conv_t *lcc, char **p_str, unsigned int *p_len) GNUC_NON_NULL(1, 2, 3);
+    API int lms_charset_conv_check(lms_charset_conv_t *lcc, const char *str, unsigned int len) GNUC_NON_NULL(1, 2);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _LIGHTMEDIASCANNER_CHARSET_CONV_H_ */
diff --git a/lightmediascanner/src/lib/lightmediascanner_check.c b/lightmediascanner/src/lib/lightmediascanner_check.c
new file mode 100644 (file)
index 0000000..ddf4139
--- /dev/null
@@ -0,0 +1,655 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _GNU_SOURCE
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <signal.h>
+#include <time.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lightmediascanner.h"
+#include "lightmediascanner_private.h"
+#include "lightmediascanner_db_private.h"
+
+struct master_db {
+    sqlite3 *handle;
+    sqlite3_stmt *get_files;
+};
+
+struct slave_db {
+    sqlite3 *handle;
+    sqlite3_stmt *transaction_begin;
+    sqlite3_stmt *transaction_commit;
+    sqlite3_stmt *delete_file_info;
+    sqlite3_stmt *update_file_info;
+};
+
+
+/***********************************************************************
+ * Master-Slave communication.
+ ***********************************************************************/
+
+struct comm_finfo {
+    int path_len;
+    int base;
+    int64_t id;
+    time_t mtime;
+    time_t dtime;
+    size_t size;
+    unsigned int flags;
+#define COMM_FINFO_FLAG_OUTDATED 1
+};
+
+static int
+_master_send_file(const struct fds *master, const struct lms_file_info finfo, unsigned int flags)
+{
+    struct comm_finfo ci;
+
+    ci.path_len = finfo.path_len;
+    ci.base = finfo.base;
+    ci.id = finfo.id;
+    ci.mtime = finfo.mtime;
+    ci.dtime = finfo.dtime;
+    ci.size = finfo.size;
+    ci.flags = flags;
+
+    if (write(master->w, &ci, sizeof(ci)) < 0) {
+        perror("write");
+        return -1;
+    }
+
+    if (write(master->w, finfo.path, finfo.path_len) < 0) {
+        perror("write");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+_master_send_finish(const struct fds *master)
+{
+    struct comm_finfo ci = {-1, -1, -1, -1, -1, -1, 0};
+
+    if (write(master->w, &ci, sizeof(ci)) < 0) {
+        perror("write");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+_master_recv_reply(const struct fds *master, struct pollfd *pfd, int *reply, int timeout)
+{
+    int r;
+
+    r = poll(pfd, 1, timeout);
+    if (r < 0) {
+        perror("poll");
+        return -1;
+    }
+
+    if (r == 0)
+        return 1;
+
+    if (read(master->r, reply, sizeof(*reply)) != sizeof(*reply)) {
+        perror("read");
+        return -2;
+    }
+
+    return 0;
+}
+
+static int
+_slave_send_reply(const struct fds *slave, int reply)
+{
+    if (write(slave->w, &reply, sizeof(reply)) == 0) {
+        perror("write");
+        return -1;
+    }
+    return 0;
+}
+
+static int
+_slave_recv_file(const struct fds *slave, struct lms_file_info *finfo, unsigned int *flags)
+{
+    struct comm_finfo ci;
+    static char path[PATH_SIZE + 1];
+    int r;
+
+    r = read(slave->r, &ci, sizeof(ci));
+    if (r != sizeof(ci)) {
+        perror("read");
+        return -1;
+    }
+
+    finfo->path_len = ci.path_len;
+    finfo->base = ci.base;
+    finfo->id = ci.id;
+    finfo->mtime = ci.mtime;
+    finfo->dtime = ci.dtime;
+    finfo->size = ci.size;
+    finfo->path = NULL;
+    *flags = ci.flags;
+
+    if (ci.path_len == -1)
+        return 0;
+
+    if (ci.path_len > PATH_SIZE) {
+        fprintf(stderr, "ERROR: path too long (%d/%d)\n",
+                ci.path_len, PATH_SIZE);
+        return -2;
+    }
+
+    r = read(slave->r, path, ci.path_len);
+    if (r != ci.path_len) {
+        fprintf(stderr, "ERROR: could not read whole path %d/%d\n",
+                r, ci.path_len);
+        return -3;
+    }
+
+    path[ci.path_len] = 0;
+    finfo->path = path;
+    return 0;
+}
+
+
+/***********************************************************************
+ * Slave-side.
+ ***********************************************************************/
+
+static int
+_slave_db_compile_all_stmts(struct slave_db *db)
+{
+    sqlite3 *handle;
+
+    handle = db->handle;
+
+    db->transaction_begin = lms_db_compile_stmt_begin_transaction(handle);
+    if (!db->transaction_begin)
+        return -1;
+
+    db->transaction_commit = lms_db_compile_stmt_end_transaction(handle);
+    if (!db->transaction_commit)
+        return -2;
+
+    db->delete_file_info = lms_db_compile_stmt_delete_file_info(handle);
+    if (!db->delete_file_info)
+        return -3;
+
+    db->update_file_info = lms_db_compile_stmt_update_file_info(handle);
+    if (!db->update_file_info)
+        return -4;
+
+    return 0;
+}
+
+static struct slave_db *
+_slave_db_open(const char *db_path)
+{
+    struct slave_db *db;
+
+    db = calloc(1, sizeof(*db));
+    if (!db) {
+        perror("calloc");
+        return NULL;
+    }
+
+    if (sqlite3_open(db_path, &db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not open DB \"%s\": %s\n",
+                db_path, sqlite3_errmsg(db->handle));
+        goto error;
+    }
+
+    return db;
+
+  error:
+    sqlite3_close(db->handle);
+    free(db);
+    return NULL;
+}
+
+static int
+_slave_db_close(struct slave_db *db)
+{
+    if (db->transaction_begin)
+        lms_db_finalize_stmt(db->transaction_begin, "transaction_begin");
+
+    if (db->transaction_commit)
+        lms_db_finalize_stmt(db->transaction_commit, "transaction_commit");
+
+    if (db->delete_file_info)
+        lms_db_finalize_stmt(db->delete_file_info, "delete_file_info");
+
+    if (db->update_file_info)
+        lms_db_finalize_stmt(db->update_file_info, "update_file_info");
+
+    if (sqlite3_close(db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: clould not close DB (slave): %s\n",
+                sqlite3_errmsg(db->handle));
+        return -1;
+    }
+    free(db);
+
+    return 0;
+}
+
+static int
+_init_sync_send(struct fds *fds)
+{
+    return _slave_send_reply(fds, 0);
+}
+
+static int
+_slave_work_int(lms_t *lms, struct fds *fds, struct slave_db *db)
+{
+    struct lms_file_info finfo;
+    void **parser_match;
+    unsigned int counter, flags;
+    int r;
+
+    parser_match = malloc(lms->n_parsers * sizeof(*parser_match));
+    if (!parser_match) {
+        perror("malloc");
+        return -6;
+    }
+
+    _init_sync_send(fds);
+
+    counter = 0;
+    lms_db_begin_transaction(db->transaction_begin);
+
+    while (((r = _slave_recv_file(fds, &finfo, &flags)) == 0) &&
+           finfo.path_len > 0) {
+        r = lms_db_update_file_info(db->update_file_info, &finfo);
+        if (r < 0)
+            fprintf(stderr, "ERROR: could not update path in DB\n");
+        else if (flags & COMM_FINFO_FLAG_OUTDATED) {
+            int used;
+
+            used = lms_parsers_check_using(lms, parser_match, &finfo);
+            if (!used)
+                r = 0;
+            else {
+                r = lms_parsers_run(lms, db->handle, parser_match, &finfo);
+                if (r < 0) {
+                    fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
+                            getpid(), finfo.path);
+                    lms_db_delete_file_info(db->delete_file_info, &finfo);
+                }
+            }
+        }
+
+        _slave_send_reply(fds, r);
+        counter++;
+        if (counter > lms->commit_interval) {
+            lms_db_end_transaction(db->transaction_commit);
+            lms_db_begin_transaction(db->transaction_begin);
+            counter = 0;
+        }
+    }
+
+    free(parser_match);
+    lms_db_end_transaction(db->transaction_commit);
+
+    return r;
+}
+
+static int
+_slave_work(lms_t *lms, struct fds *fds)
+{
+    struct slave_db *db;
+    int r;
+
+    db = _slave_db_open(lms->db_path);
+    if (!db)
+        return -1;
+
+    if (lms_parsers_setup(lms, db->handle) != 0) {
+        fprintf(stderr, "ERROR: could not setup parsers.\n");
+        r = -2;
+        goto end;
+    }
+
+    if (_slave_db_compile_all_stmts(db) != 0) {
+        fprintf(stderr, "ERROR: could not compile statements.\n");
+        r = -3;
+        goto end;
+    }
+
+    if (lms_parsers_start(lms, db->handle) != 0) {
+        fprintf(stderr, "ERROR: could not start parsers.\n");
+        r = -4;
+        goto end;
+    }
+    if (lms->n_parsers < 1) {
+        fprintf(stderr, "ERROR: no parser could be started, exit.\n");
+        r = -5;
+        goto end;
+    }
+
+    r = _slave_work_int(lms, fds, db);
+
+  end:
+    lms_parsers_finish(lms, db->handle);
+    _slave_db_close(db);
+    _init_sync_send(fds);
+
+    return r;
+}
+
+
+/***********************************************************************
+ * Master-side.
+ ***********************************************************************/
+
+static int
+_master_db_compile_all_stmts(struct master_db *db)
+{
+    sqlite3 *handle;
+
+    handle = db->handle;
+
+    db->get_files = lms_db_compile_stmt_get_files(handle);
+    if (!db->get_files)
+        return -1;
+
+    return 0;
+}
+
+static struct master_db *
+_master_db_open(const char *db_path)
+{
+    struct master_db *db;
+
+    db = calloc(1, sizeof(*db));
+    if (!db) {
+        perror("calloc");
+        return NULL;
+    }
+
+    if (sqlite3_open(db_path, &db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not open DB \"%s\": %s\n",
+                db_path, sqlite3_errmsg(db->handle));
+        goto error;
+    }
+
+    if (lms_db_create_core_tables_if_required(db->handle) != 0) {
+        fprintf(stderr, "ERROR: could not setup tables and indexes.\n");
+        goto error;
+    }
+
+    if (_master_db_compile_all_stmts(db) != 0) {
+        fprintf(stderr, "ERROR: could not compile statements.\n");
+        goto error;
+    }
+
+    return db;
+
+  error:
+    sqlite3_close(db->handle);
+    free(db);
+    return NULL;
+}
+
+static int
+_master_db_close(struct master_db *db)
+{
+    if (db->get_files)
+        lms_db_finalize_stmt(db->get_files, "get_files");
+
+    if (sqlite3_close(db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: clould not close DB (master): %s\n",
+                sqlite3_errmsg(db->handle));
+        return -1;
+    }
+    free(db);
+
+    return 0;
+}
+
+static void
+_calc_base(struct lms_file_info *finfo)
+{
+    int i;
+
+    for (i = finfo->path_len - 1; i >= 0; i--)
+        if (finfo->path[i] == '/') {
+            finfo->base = i;
+            return;
+        }
+}
+
+static inline void
+_update_finfo_from_stmt(struct lms_file_info *finfo, sqlite3_stmt *stmt)
+{
+    finfo->id = sqlite3_column_int64(stmt, 0);
+    finfo->path = sqlite3_column_blob(stmt, 1);
+    finfo->path_len = sqlite3_column_bytes(stmt, 1);
+    finfo->base = 0;
+    finfo->mtime = sqlite3_column_int(stmt, 2);
+    finfo->dtime = sqlite3_column_int(stmt, 3);
+    finfo->size = sqlite3_column_int(stmt, 4);
+}
+
+static inline void
+_update_finfo_from_stat(struct lms_file_info *finfo, const struct stat *st)
+{
+    finfo->mtime = st->st_mtime;
+    finfo->size = st->st_size;
+    finfo->dtime = 0;
+}
+
+static int
+_check_row(struct master_db *db, struct pinfo *pinfo)
+{
+    struct lms_file_info finfo;
+    struct stat st;
+    unsigned int flags;
+    int r, reply;
+
+    _update_finfo_from_stmt(&finfo, db->get_files);
+
+    flags = 0;
+    if (stat(finfo.path, &st) == 0) {
+        if (st.st_mtime == finfo.mtime && st.st_size == finfo.size) {
+            if (finfo.dtime == 0)
+                return 0;
+            else
+                finfo.dtime = 0;
+        } else {
+            _update_finfo_from_stat(&finfo, &st);
+            flags |= COMM_FINFO_FLAG_OUTDATED;
+        }
+    } else {
+        if (finfo.dtime)
+            return 0;
+        else
+            finfo.dtime = time(NULL);
+    }
+
+    _calc_base(&finfo);
+
+    if (_master_send_file(&pinfo->master, finfo, flags) != 0)
+        return -1;
+
+    r = _master_recv_reply(&pinfo->master, &pinfo->poll, &reply,
+                           pinfo->lms->slave_timeout);
+    if (r < 0)
+        return -2;
+    else if (r == 1) {
+        fprintf(stderr, "ERROR: slave took too long, restart %d\n",
+                pinfo->child);
+        if (lms_restart_slave(pinfo, _slave_work) != 0)
+            return -3;
+        return 1;
+    } else {
+        if (reply < 0) {
+            /* XXX callback library users to inform error. */
+            fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
+                    getpid(), finfo.path);
+            return (-reply) << 8;
+        } else
+            return reply;
+    }
+}
+
+static int
+_init_sync_wait(struct pinfo *pinfo, int restart)
+{
+    int r, reply;
+
+    do {
+        r = _master_recv_reply(&pinfo->master, &pinfo->poll, &reply,
+                               pinfo->lms->slave_timeout);
+        if (r < 0)
+            return -1;
+        else if (r == 1 && restart) {
+            fprintf(stderr, "ERROR: slave took too long, restart %d\n",
+                    pinfo->child);
+            if (lms_restart_slave(pinfo, _slave_work) != 0)
+                return -2;
+        }
+    } while (r != 0 && restart);
+
+    return r;
+}
+
+static int
+_master_dummy_send_finish(const struct fds *master)
+{
+    return 0;
+}
+
+static int
+_check(struct pinfo *pinfo, int len, char *path)
+{
+    char query[PATH_SIZE + 2];
+    struct master_db *db;
+    int r, ret;
+
+    db = _master_db_open(pinfo->lms->db_path);
+    if (!db)
+        return -1;
+
+    memcpy(query, path, len);
+    query[len] = '%';
+    query[len + 1] = '\0';
+    ret = lms_db_get_files(db->get_files, query, len + 1);
+    if (ret != 0)
+        goto end;
+
+    if (lms_create_slave(pinfo, _slave_work) != 0) {
+        ret = -2;
+        goto end;
+    }
+    _init_sync_wait(pinfo, 1);
+
+    do {
+        r = sqlite3_step(db->get_files);
+        if (r == SQLITE_ROW) {
+            if (_check_row(db, pinfo) < 0) {
+                fprintf(stderr, "ERROR: could not check row.\n");
+                ret = -1;
+                goto finish_slave;
+            }
+        } else if (r != SQLITE_DONE) {
+            fprintf(stderr, "ERROR: could not begin transaction: %s\n",
+                    sqlite3_errmsg(db->handle));
+            ret = -2;
+            goto finish_slave;
+        }
+    } while (r != SQLITE_DONE);
+    ret = 0;
+
+  finish_slave:
+    _master_send_finish(&pinfo->master);
+    _init_sync_wait(pinfo, 0);
+    lms_finish_slave(pinfo, _master_dummy_send_finish);
+
+  end:
+    lms_db_reset_stmt(db->get_files);
+    _master_db_close(db);
+
+    return ret;
+}
+
+int
+lms_check(lms_t *lms, const char *top_path)
+{
+    struct pinfo pinfo;
+    int r;
+    char path[PATH_SIZE];
+
+    if (!lms) {
+        r = -1;
+        goto end;
+    }
+
+    if (!top_path) {
+        r = -2;
+        goto end;
+    }
+
+    if (lms->is_processing) {
+        fprintf(stderr, "ERROR: is already processing.\n");
+        r = -3;
+        goto end;
+    }
+
+    if (!lms->parsers) {
+        fprintf(stderr, "ERROR: no plugins registered.\n");
+        r = -4;
+        goto end;
+    }
+
+    pinfo.lms = lms;
+
+    if (lms_create_pipes(&pinfo) != 0) {
+        r = -5;
+        goto end;
+    }
+
+    if (realpath(top_path, path) == NULL) {
+        perror("realpath");
+        r = -6;
+        goto close_pipes;
+    }
+
+    lms->is_processing = 1;
+    r = _check(&pinfo, strlen(path), path);
+    lms->is_processing = 0;
+
+  close_pipes:
+    lms_close_pipes(&pinfo);
+  end:
+    return r;
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner_db.h b/lightmediascanner/src/lib/lightmediascanner_db.h
new file mode 100644 (file)
index 0000000..edd12f6
--- /dev/null
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifndef _LIGHTMEDIASCANNER_DB_H_
+#define _LIGHTMEDIASCANNER_DB_H_ 1
+
+#ifdef API
+#undef API
+#endif
+
+#ifdef __GNUC__
+# if __GNUC__ >= 4
+#  define API __attribute__ ((visibility("default")))
+# else
+#  define API
+# endif
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#  define GNUC_NON_NULL(...) __attribute__((nonnull(__VA_ARGS__)))
+# else
+#  define GNUC_NON_NULL(...)
+# endif
+#else
+#  define API
+#  define GNUC_NON_NULL(...)
+#endif
+
+#include <lightmediascanner_plugin.h>
+#include <lightmediascanner_utils.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    /* Image Records */
+    struct lms_gps_info {
+        double latitude;
+        double longitude;
+        double altitude;
+    };
+
+    struct lms_image_info {
+        int64_t id;
+        struct lms_string_size title;
+        struct lms_string_size artist;
+        unsigned int date;
+        unsigned short width;
+        unsigned short height;
+        unsigned short orientation;
+        struct lms_gps_info gps;
+    };
+
+    typedef struct lms_db_image lms_db_image_t;
+
+    API lms_db_image_t *lms_db_image_new(sqlite3 *db) GNUC_NON_NULL(1);
+    API int lms_db_image_start(lms_db_image_t *ldi) GNUC_NON_NULL(1);
+    API int lms_db_image_free(lms_db_image_t *ldi) GNUC_NON_NULL(1);
+    API int lms_db_image_add(lms_db_image_t *ldi, struct lms_image_info *info) GNUC_NON_NULL(1, 2);
+
+    /* Audio Records */
+    struct lms_audio_info {
+        int64_t id;
+        struct lms_string_size title;
+        struct lms_string_size artist;
+        struct lms_string_size album;
+        struct lms_string_size genre;
+        unsigned int playcnt;
+        unsigned char trackno;
+        unsigned char rating;
+    };
+
+    typedef struct lms_db_audio lms_db_audio_t;
+
+    API lms_db_audio_t *lms_db_audio_new(sqlite3 *db) GNUC_NON_NULL(1);
+    API int lms_db_audio_start(lms_db_audio_t *lda) GNUC_NON_NULL(1);
+    API int lms_db_audio_free(lms_db_audio_t *lda) GNUC_NON_NULL(1);
+    API int lms_db_audio_add(lms_db_audio_t *lda, struct lms_audio_info *info) GNUC_NON_NULL(1, 2);
+
+    /* Video Records */
+    struct lms_video_info {
+        int64_t id;
+        struct lms_string_size title;
+        struct lms_string_size artist;
+    };
+
+    typedef struct lms_db_video lms_db_video_t;
+
+    API lms_db_video_t *lms_db_video_new(sqlite3 *db) GNUC_NON_NULL(1);
+    API int lms_db_video_start(lms_db_video_t *ldv) GNUC_NON_NULL(1);
+    API int lms_db_video_free(lms_db_video_t *ldv) GNUC_NON_NULL(1);
+    API int lms_db_video_add(lms_db_video_t *ldv, struct lms_video_info *info) GNUC_NON_NULL(1, 2);
+
+    /* Playlist Records */
+    struct lms_playlist_info {
+        int64_t id;
+        struct lms_string_size title;
+        unsigned int n_entries;
+    };
+
+    typedef struct lms_db_playlist lms_db_playlist_t;
+
+    API lms_db_playlist_t *lms_db_playlist_new(sqlite3 *db) GNUC_NON_NULL(1);
+    API int lms_db_playlist_start(lms_db_playlist_t *ldp) GNUC_NON_NULL(1);
+    API int lms_db_playlist_free(lms_db_playlist_t *ldp) GNUC_NON_NULL(1);
+    API int lms_db_playlist_add(lms_db_playlist_t *ldp, struct lms_playlist_info *info) GNUC_NON_NULL(1, 2);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _LIGHTMEDIASCANNER_DB_H_ */
diff --git a/lightmediascanner/src/lib/lightmediascanner_db_audio.c b/lightmediascanner/src/lib/lightmediascanner_db_audio.c
new file mode 100644 (file)
index 0000000..5a26d0f
--- /dev/null
@@ -0,0 +1,622 @@
+#include <lightmediascanner_db.h>
+#include "lightmediascanner_db_private.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+struct lms_db_audio {
+    sqlite3 *db;
+    sqlite3_stmt *insert_audio;
+    sqlite3_stmt *insert_artist;
+    sqlite3_stmt *insert_album;
+    sqlite3_stmt *insert_genre;
+    sqlite3_stmt *get_artist;
+    sqlite3_stmt *get_album;
+    sqlite3_stmt *get_genre;
+    unsigned int _references;
+    unsigned int _is_started:1;
+};
+
+static struct lms_db_cache _cache = {0, NULL};
+
+static int
+_db_create(sqlite3 *db, const char *name, const char *sql)
+{
+    char *err;
+    int r;
+
+    r = sqlite3_exec(db, sql, NULL, NULL, &err);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create \"%s\": %s\n", name, err);
+        sqlite3_free(err);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+_db_table_updater_audios_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
+    int ret;
+
+    ret = _db_create(db, "audios",
+        "CREATE TABLE IF NOT EXISTS audios ("
+        "id INTEGER PRIMARY KEY, "
+        "title TEXT, "
+        "album_id INTEGER, "
+        "genre_id INTEGER, "
+        "trackno INTEGER, "
+        "rating INTEGER, "
+        "playcnt INTEGER"
+        ")");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audios_title_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audios_title_idx ON audios (title)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audios_album_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audios_album_idx ON audios (album_id)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audios_genre_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audios_genre_idx ON audios (genre_id)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audios_trackno_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audios_trackno_idx ON audios (trackno)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audios_playcnt_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audios_playcnt_idx ON audios (playcnt)");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_audios_on_files_deleted "
+        "DELETE ON files FOR EACH ROW BEGIN"
+        "   DELETE FROM audios WHERE id = OLD.id; END;");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_files_on_audios_deleted "
+        "DELETE ON audios FOR EACH ROW BEGIN"
+        " DELETE FROM files WHERE id = OLD.id; END;");
+
+  done:
+    return ret;
+}
+
+static lms_db_table_updater_t _db_table_updater_audios[] = {
+    _db_table_updater_audios_0
+};
+
+static int
+_db_table_updater_audio_artists_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
+    int ret;
+
+    ret = _db_create(db, "audio_artists",
+        "CREATE TABLE IF NOT EXISTS audio_artists ("
+        "id INTEGER PRIMARY KEY, "
+        "name TEXT UNIQUE"
+        ")");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_artists_name_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audio_artists_name_idx ON audio_artists (name)");
+
+  done:
+    return ret;
+}
+
+static lms_db_table_updater_t _db_table_updater_audio_artists[] = {
+    _db_table_updater_audio_artists_0
+};
+
+static int
+_db_table_updater_audio_albums_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
+    int ret;
+
+    ret = _db_create(db, "audio_albums",
+        "CREATE TABLE IF NOT EXISTS audio_albums ("
+        "id INTEGER PRIMARY KEY, "
+        "artist_id INTEGER, "
+        "name TEXT"
+        ")");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_albums_name_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audio_albums_name_idx ON audio_albums (name)");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_albums_artist_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audio_albums_artist_idx ON audio_albums (artist_id)");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_audios_on_albums_deleted "
+        "DELETE ON audio_albums FOR EACH ROW BEGIN"
+        " DELETE FROM audios WHERE album_id = OLD.id; END;");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_audio_albums_on_artists_deleted "
+        "DELETE ON audio_artists FOR EACH ROW BEGIN"
+        " DELETE FROM audio_albums WHERE artist_id = OLD.id; END;");
+
+  done:
+    return ret;
+}
+
+static lms_db_table_updater_t _db_table_updater_audio_albums[] = {
+    _db_table_updater_audio_albums_0
+};
+
+static int
+_db_table_updater_audio_genres_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
+    int ret;
+
+    ret = _db_create(db, "audio_genres",
+        "CREATE TABLE IF NOT EXISTS audio_genres ("
+        "id INTEGER PRIMARY KEY, "
+        "name TEXT UNIQUE"
+        ")");
+    if (ret != 0)
+        goto done;
+
+    ret = _db_create(db, "audio_genres_name_idx",
+        "CREATE INDEX IF NOT EXISTS "
+        "audio_albums_name_idx ON audio_albums (name)");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_audios_on_genres_deleted "
+        "DELETE ON audio_genres FOR EACH ROW BEGIN"
+        " DELETE FROM audios WHERE genre_id = OLD.id; END;");
+
+  done:
+    return ret;
+}
+
+static lms_db_table_updater_t _db_table_updater_audio_genres[] = {
+    _db_table_updater_audio_genres_0
+};
+
+#define _DB_T_UPDATE(db, name, array)                                   \
+    lms_db_table_update_if_required(db, name, LMS_ARRAY_SIZE(array), array)
+
+static int
+_db_create_tables_if_required(sqlite3 *db)
+{
+    int ret;
+
+    ret = _DB_T_UPDATE(db, "audios", _db_table_updater_audios);
+    if (ret != 0)
+        goto done;
+
+    ret = _DB_T_UPDATE(db, "audio_artists", _db_table_updater_audio_artists);
+    if (ret != 0)
+        goto done;
+
+    ret = _DB_T_UPDATE(db, "audio_albums", _db_table_updater_audio_albums);
+    if (ret != 0)
+        goto done;
+
+    ret = _DB_T_UPDATE(db, "audio_genres", _db_table_updater_audio_genres);
+
+  done:
+    return ret;
+}
+
+#undef _DB_T_UPDATE
+
+lms_db_audio_t *
+lms_db_audio_new(sqlite3 *db)
+{
+    lms_db_audio_t *lda;
+    void *p;
+
+    if (lms_db_cache_get(&_cache, db, &p) == 0) {
+        lda = p;
+        lda->_references++;
+        return lda;
+    }
+
+    if (!db)
+        return NULL;
+
+    if (_db_create_tables_if_required(db) != 0) {
+        fprintf(stderr, "ERROR: could not create tables.\n");
+        return NULL;
+    }
+
+    lda = calloc(1, sizeof(lms_db_audio_t));
+    lda->_references = 1;
+    lda->db = db;
+
+    if (lms_db_cache_add(&_cache, db, lda) != 0) {
+        lms_db_audio_free(lda);
+        return NULL;
+    }
+
+    return lda;
+}
+
+int
+lms_db_audio_start(lms_db_audio_t *lda)
+{
+    if (!lda)
+        return -1;
+    if (lda->_is_started)
+        return 0;
+
+    lda->insert_audio = lms_db_compile_stmt(lda->db,
+        "INSERT OR REPLACE INTO audios "
+        "(id, title, album_id, genre_id, trackno, rating, playcnt) "
+        "VALUES (?, ?, ?, ?, ?, ?, ?)");
+    if (!lda->insert_audio)
+        return -2;
+
+    lda->insert_artist = lms_db_compile_stmt(lda->db,
+        "INSERT INTO audio_artists (name) VALUES (?)");
+    if (!lda->insert_artist)
+        return -3;
+
+    lda->insert_album = lms_db_compile_stmt(lda->db,
+        "INSERT INTO audio_albums (artist_id, name) VALUES (?, ?)");
+    if (!lda->insert_album)
+        return -4;
+
+    lda->insert_genre = lms_db_compile_stmt(lda->db,
+        "INSERT INTO audio_genres (name) VALUES (?)");
+    if (!lda->insert_genre)
+        return -5;
+
+    lda->get_artist = lms_db_compile_stmt(lda->db,
+        "SELECT id FROM audio_artists WHERE name = ? LIMIT 1");
+    if (!lda->get_artist)
+        return -6;
+
+    lda->get_album = lms_db_compile_stmt(lda->db,
+        "SELECT id FROM audio_albums WHERE name = ? AND artist_id = ? LIMIT 1");
+    if (!lda->get_album)
+        return -7;
+
+    lda->get_genre = lms_db_compile_stmt(lda->db,
+        "SELECT id FROM audio_genres WHERE name = ? LIMIT 1");
+    if (!lda->get_genre)
+        return -8;
+
+    lda->_is_started = 1;
+    return 0;
+}
+
+int
+lms_db_audio_free(lms_db_audio_t *lda)
+{
+    int r;
+
+    if (!lda)
+        return -1;
+    if (lda->_references == 0) {
+        fprintf(stderr, "ERROR: over-called lms_db_audio_free(%p)\n", lda);
+        return -1;
+    }
+
+    lda->_references--;
+    if (lda->_references > 0)
+        return 0;
+
+    if (lda->insert_audio)
+        lms_db_finalize_stmt(lda->insert_audio, "insert_audio");
+
+    if (lda->insert_artist)
+        lms_db_finalize_stmt(lda->insert_artist, "insert_artist");
+
+    if (lda->insert_album)
+        lms_db_finalize_stmt(lda->insert_album, "insert_album");
+
+    if (lda->insert_genre)
+        lms_db_finalize_stmt(lda->insert_genre, "insert_genre");
+
+    if (lda->get_artist)
+        lms_db_finalize_stmt(lda->get_artist, "get_artist");
+
+    if (lda->get_album)
+        lms_db_finalize_stmt(lda->get_album, "get_album");
+
+    if (lda->get_genre)
+        lms_db_finalize_stmt(lda->get_genre, "get_genre");
+
+    r = lms_db_cache_del(&_cache, lda->db, lda);
+    free(lda);
+
+    return r;
+}
+
+static int
+_db_get_id_by_name(sqlite3_stmt *stmt, const struct lms_string_size *name, int64_t *id)
+{
+    int r, ret;
+
+    ret = lms_db_bind_text(stmt, 1, name->str, name->len);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r == SQLITE_DONE) {
+        ret = 1;
+        goto done;
+    }
+
+    if (r != SQLITE_ROW) {
+        fprintf(stderr, "ERROR: could not get id by name: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -2;
+        goto done;
+    }
+
+    *id = sqlite3_column_int64(stmt, 0);
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+
+}
+static int
+_db_insert_name(sqlite3_stmt *stmt, const struct lms_string_size *name, int64_t *id)
+{
+    int r, ret;
+
+    ret = lms_db_bind_text(stmt, 1, name->str, name->len);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert name: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -2;
+        goto done;
+    }
+
+    *id = sqlite3_last_insert_rowid(sqlite3_db_handle(stmt));
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+static int
+_db_get_artist(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *artist_id)
+{
+    return _db_get_id_by_name(lda->get_artist, &info->artist, artist_id);
+}
+
+static int
+_db_insert_artist(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *artist_id)
+{
+    int r;
+
+    if (!info->artist.str) /* fast path for unknown artist */
+        return 1;
+
+    r =_db_get_artist(lda, info, artist_id);
+    if (r == 0)
+        return 0;
+    else if (r < 0)
+        return -1;
+
+    return _db_insert_name(lda->insert_artist, &info->artist, artist_id);
+}
+
+static int
+_db_get_album(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *artist_id, int64_t *album_id)
+{
+    sqlite3_stmt *stmt;
+    int r, ret;
+
+    stmt = lda->get_album;
+
+    ret = lms_db_bind_text(stmt, 1, info->album.str, info->album.len);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int64_or_null(stmt, 2, artist_id);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r == SQLITE_DONE) {
+        ret = 1;
+        goto done;
+    }
+
+    if (r != SQLITE_ROW) {
+        fprintf(stderr, "ERROR: could not get album from table: %s\n",
+                sqlite3_errmsg(lda->db));
+        ret = -2;
+        goto done;
+    }
+
+    *album_id = sqlite3_column_int64(stmt, 0);
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+
+}
+
+static int
+_db_insert_album(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *album_id)
+{
+    int r, ret, ret_artist;
+    int64_t artist_id;
+    sqlite3_stmt *stmt;
+
+    if (!info->album.str) /* fast path for unknown album */
+        return 1;
+
+    ret_artist = _db_insert_artist(lda, info, &artist_id);
+    if (ret_artist < 0)
+        return -1;
+
+    r =_db_get_album(lda, info,
+                     (ret_artist == 0) ? &artist_id : NULL,
+                     album_id);
+    if (r == 0)
+        return 0;
+    else if (r < 0)
+        return -1;
+
+    stmt = lda->insert_album;
+    ret = lms_db_bind_int64_or_null(stmt, 1,
+                                    (ret_artist == 0) ? &artist_id : NULL);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_text(stmt, 2, info->album.str, info->album.len);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert audio album: %s\n",
+                sqlite3_errmsg(lda->db));
+        ret = -3;
+        goto done;
+    }
+
+    *album_id = sqlite3_last_insert_rowid(lda->db);
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+static int
+_db_get_genre(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *genre_id)
+{
+    return _db_get_id_by_name(lda->get_genre, &info->genre, genre_id);
+}
+
+static int
+_db_insert_genre(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *genre_id)
+{
+    int r;
+
+    if (!info->genre.str) /* fast path for unknown genre */
+        return 1;
+
+    r =_db_get_genre(lda, info, genre_id);
+    if (r == 0)
+        return 0;
+    else if (r < 0)
+        return -1;
+
+    return _db_insert_name(lda->insert_genre, &info->genre, genre_id);
+}
+
+static int
+_db_insert_audio(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *album_id, int64_t *genre_id)
+{
+    sqlite3_stmt *stmt;
+    int r, ret;
+
+    stmt = lda->insert_audio;
+    ret = lms_db_bind_int64(stmt, 1, info->id);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_text(stmt, 2, info->title.str, info->title.len);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int64_or_null(stmt, 3, album_id);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int64_or_null(stmt, 4, genre_id);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 5, info->trackno);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 6, info->rating);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 7, info->playcnt);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert audio info: %s\n",
+                sqlite3_errmsg(lda->db));
+        ret = -8;
+        goto done;
+    }
+
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+int
+lms_db_audio_add(lms_db_audio_t *lda, struct lms_audio_info *info)
+{
+    int64_t album_id, genre_id;
+    int ret_album, ret_genre;
+
+    if (!lda)
+        return -1;
+    if (!info)
+        return -2;
+    if (info->id < 1)
+        return -3;
+
+    ret_album = _db_insert_album(lda, info, &album_id);
+    if (ret_album < 0)
+        return -4;
+
+    ret_genre = _db_insert_genre(lda, info, &genre_id);
+    if (ret_genre < 0)
+        return -5;
+
+    return _db_insert_audio(lda, info,
+                            (ret_album == 0) ? &album_id : NULL,
+                            (ret_genre == 0) ? &genre_id : NULL);
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner_db_common.c b/lightmediascanner/src/lib/lightmediascanner_db_common.c
new file mode 100644 (file)
index 0000000..94ff646
--- /dev/null
@@ -0,0 +1,769 @@
+#include "lightmediascanner_db_private.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#if SQLITE_VERSION_NUMBER < 3003009
+int
+sqlite3_prepare_v2(sqlite3 *db, const char *sql, int len, sqlite3_stmt **stmt, const char **tail)
+{
+    return sqlite3_prepare(db, sql, len, stmt, tail);
+}
+#endif /* SQLITE_VERSION_NUMBER < 3003009 */
+
+#if SQLITE_VERSION_NUMBER < 3003007
+int
+sqlite3_clear_bindings(sqlite3_stmt *stmt)
+{
+    int i, last;
+    int rc;
+
+    rc = SQLITE_OK;
+    last = sqlite3_bind_parameter_count(stmt);
+    for(i = 1; rc == SQLITE_OK && i <= last; i++) {
+        rc = sqlite3_bind_null(stmt, i);
+    }
+    return rc;
+}
+#endif /* SQLITE_VERSION_NUMBER < 3003007 */
+
+#if SQLITE_VERSION_NUMBER < 3003008
+/* Until 3.3.8 it doesn't support CREATE TRIGGER IF NOT EXISTS, so
+ * just ignore errors :-(
+ */
+int
+lms_db_create_trigger_if_not_exists(sqlite3 *db, const char *sql)
+{
+    char *errmsg, *query;
+    int r, sql_len, prefix_len;
+
+    prefix_len = sizeof("CREATE TRIGGER ") - 1;
+    sql_len = strlen(sql);
+    query = malloc((prefix_len + sql_len + 1) * sizeof(char));
+    if (!query)
+        return -1;
+
+    memcpy(query, "CREATE TRIGGER ", prefix_len);
+    memcpy(query + prefix_len, sql, sql_len + 1);
+    r = sqlite3_exec(db, query, NULL, NULL, &errmsg);
+    free(query);
+    if (r != SQLITE_OK)
+        sqlite3_free(errmsg);
+    return 0;
+}
+#else /* SQLITE_VERSION_NUMBER < 3003008 */
+int
+lms_db_create_trigger_if_not_exists(sqlite3 *db, const char *sql)
+{
+    char *errmsg, *query;
+    int r, sql_len, prefix_len;
+
+    prefix_len = sizeof("CREATE TRIGGER IF NOT EXISTS ") - 1;
+    sql_len = strlen(sql);
+    query = malloc((prefix_len + sql_len + 1) * sizeof(char));
+    if (!query)
+        return -1;
+
+    memcpy(query, "CREATE TRIGGER IF NOT EXISTS ", prefix_len);
+    memcpy(query + prefix_len, sql, sql_len + 1);
+    r = sqlite3_exec(db, query, NULL, NULL, &errmsg);
+    free(query);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create trigger: %s\n", errmsg);
+        sqlite3_free(errmsg);
+        return -2;
+    }
+    return 0;
+}
+#endif /* SQLITE_VERSION_NUMBER < 3003008 */
+
+sqlite3_stmt *
+lms_db_compile_stmt(sqlite3 *db, const char *sql)
+{
+    sqlite3_stmt *stmt;
+
+    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
+        fprintf(stderr, "ERROR: could not prepare \"%s\": %s\n", sql,
+                sqlite3_errmsg(db));
+
+    return stmt;
+}
+
+int
+lms_db_finalize_stmt(sqlite3_stmt *stmt, const char *name)
+{
+    int r;
+
+    r = sqlite3_finalize(stmt);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not finalize %s statement: #%d\n",
+                name, r);
+        return -1;
+    }
+
+    return 0;
+}
+
+int
+lms_db_reset_stmt(sqlite3_stmt *stmt)
+{
+    int r, ret;
+
+    ret = r = sqlite3_reset(stmt);
+    if (r != SQLITE_OK)
+        fprintf(stderr, "ERROR: could not reset SQL statement: #%d\n", r);
+
+    r = sqlite3_clear_bindings(stmt);
+    ret += r;
+    if (r != SQLITE_OK)
+        fprintf(stderr, "ERROR: could not clear SQL: #%d\n", r);
+
+    return ret;
+}
+
+int
+lms_db_bind_text(sqlite3_stmt *stmt, int col, const char *text, int len)
+{
+    int r;
+
+    if (text)
+        r = sqlite3_bind_text(stmt, col, text, len, SQLITE_STATIC);
+    else
+        r = sqlite3_bind_null(stmt, col);
+
+    if (r == SQLITE_OK)
+        return 0;
+    else {
+        sqlite3 *db;
+        const char *err;
+
+        db = sqlite3_db_handle(stmt);
+        err = sqlite3_errmsg(db);
+        fprintf(stderr, "ERROR: could not bind SQL value %d: %s\n", col, err);
+        return -col;
+    }
+}
+
+int
+lms_db_bind_blob(sqlite3_stmt *stmt, int col, const void *blob, int len)
+{
+    int r;
+
+    if (blob)
+        r = sqlite3_bind_blob(stmt, col, blob, len, SQLITE_STATIC);
+    else
+        r = sqlite3_bind_null(stmt, col);
+
+    if (r == SQLITE_OK)
+        return 0;
+    else {
+        sqlite3 *db;
+        const char *err;
+
+        db = sqlite3_db_handle(stmt);
+        err = sqlite3_errmsg(db);
+        fprintf(stderr, "ERROR: could not bind SQL value %d: %s\n", col, err);
+        return -col;
+    }
+}
+
+int
+lms_db_bind_int64(sqlite3_stmt *stmt, int col, int64_t value)
+{
+    int r;
+
+    r = sqlite3_bind_int64(stmt, col, value);
+    if (r == SQLITE_OK)
+        return 0;
+    else {
+        sqlite3 *db;
+        const char *err;
+
+        db = sqlite3_db_handle(stmt);
+        err = sqlite3_errmsg(db);
+        fprintf(stderr, "ERROR: could not bind SQL value %d: %s\n", col, err);
+        return -col;
+    }
+}
+
+int
+lms_db_bind_int64_or_null(sqlite3_stmt *stmt, int col, int64_t *p_value)
+{
+    int r;
+
+    if (p_value)
+        r = sqlite3_bind_int64(stmt, col, *p_value);
+    else
+        r = sqlite3_bind_null(stmt, col);
+    if (r == SQLITE_OK)
+        return 0;
+    else {
+        sqlite3 *db;
+        const char *err;
+
+        db = sqlite3_db_handle(stmt);
+        err = sqlite3_errmsg(db);
+        fprintf(stderr, "ERROR: could not bind SQL value %d: %s\n", col, err);
+        return -col;
+    }
+}
+
+int
+lms_db_bind_int(sqlite3_stmt *stmt, int col, int value)
+{
+    int r;
+
+    r = sqlite3_bind_int(stmt, col, value);
+    if (r == SQLITE_OK)
+        return 0;
+    else {
+        sqlite3 *db;
+        const char *err;
+
+        db = sqlite3_db_handle(stmt);
+        err = sqlite3_errmsg(db);
+        fprintf(stderr, "ERROR: could not bind SQL value %d: %s\n", col, err);
+        return -col;
+    }
+}
+
+int
+lms_db_bind_double(sqlite3_stmt *stmt, int col, double value)
+{
+    int r;
+
+    r = sqlite3_bind_double(stmt, col, value);
+    if (r == SQLITE_OK)
+        return 0;
+    else {
+        sqlite3 *db;
+        const char *err;
+
+        db = sqlite3_db_handle(stmt);
+        err = sqlite3_errmsg(db);
+        fprintf(stderr, "ERROR: could not bind SQL value %d: %s\n", col, err);
+        return -col;
+    }
+}
+
+int
+lms_db_table_version_get(sqlite3 *db, const char *table)
+{
+    int r, version;
+    sqlite3_stmt *stmt;
+
+    stmt = lms_db_compile_stmt(db,
+         "SELECT version FROM lms_internal WHERE tab = ?");
+    if (!stmt)
+        return -1;
+
+    if (lms_db_bind_text(stmt, 1, table, -1) != 0) {
+        version = -1;
+        goto done;
+    }
+
+    r = sqlite3_step(stmt);
+    if (r == SQLITE_DONE)
+        version = 0;
+    else if (r == SQLITE_ROW)
+        version = sqlite3_column_int(stmt, 1);
+    else {
+        version = -1;
+        fprintf(stderr, "ERROR: could not get table '%s' version: %s\n",
+                table, sqlite3_errmsg(db));
+    }
+
+  done:
+    lms_db_reset_stmt(stmt);
+    lms_db_finalize_stmt(stmt, "table_version_get");
+
+    return version;
+}
+
+int
+lms_db_table_version_set(sqlite3 *db, const char *table, unsigned int version)
+{
+    int r, ret;
+    sqlite3_stmt *stmt;
+
+    stmt = lms_db_compile_stmt(db,
+        "INSERT OR REPLACE INTO lms_internal (tab, version) VALUES (?, ?)");
+    if (!stmt)
+        return -1;
+
+    ret = lms_db_bind_text(stmt, 1, table, -1);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 2, version);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        ret = -1;
+        fprintf(stderr, "ERROR: could not set table '%s' version: %s\n",
+                table, sqlite3_errmsg(db));
+    }
+
+  done:
+    lms_db_reset_stmt(stmt);
+    lms_db_finalize_stmt(stmt, "table_version_set");
+
+    return ret;
+}
+
+int
+lms_db_table_update(sqlite3 *db, const char *table, unsigned int current_version, unsigned int last_version, const lms_db_table_updater_t *updaters)
+{
+    if (current_version == last_version)
+        return 0;
+    else if (current_version > last_version) {
+        fprintf(stderr,
+                "WARNING: current version (%d) of table '%s' is greater than "
+                "last known version (%d), no updates will be made.\n",
+                current_version, table, last_version);
+        return 0;
+    }
+
+    for (; current_version < last_version; current_version++) {
+        int r, is_last_run;
+
+        is_last_run = current_version == (last_version - 1);
+        r = updaters[current_version](db, table, current_version, is_last_run);
+        if (r != 0) {
+            fprintf(stderr,
+                    "ERROR: could not update table '%s' from version %d->%d\n",
+                    table, current_version, current_version + 1);
+            return r;
+        }
+        lms_db_table_version_set(db, table, current_version + 1);
+    }
+
+    return 0;
+}
+
+int
+lms_db_table_update_if_required(sqlite3 *db, const char *table, unsigned int last_version, lms_db_table_updater_t *updaters)
+{
+    int current_version;
+
+    current_version = lms_db_table_version_get(db, table);
+    if (current_version < 0)
+        return -1;
+    else
+        return lms_db_table_update(db, table, current_version, last_version,
+                                   updaters);
+}
+
+static int
+lms_db_cache_find_db(const struct lms_db_cache *cache, const sqlite3 *db)
+{
+    int i;
+
+    for (i = 0; i < cache->size; i++)
+        if (cache->entries[i].db == db)
+            return i;
+
+    return -1;
+}
+
+static int
+lms_db_cache_resize(struct lms_db_cache *cache, int new_size)
+{
+    cache->size = new_size;
+    cache->entries = realloc(cache->entries,
+                             cache->size * sizeof(*cache->entries));
+    if (cache->size && !cache->entries) {
+        perror("realloc");
+        cache->size = 0;
+        return -1;
+    }
+
+    return 0;
+}
+
+int
+lms_db_cache_add(struct lms_db_cache *cache, const sqlite3 *db, void *data)
+{
+    struct lms_db_cache_entry *e;
+    int idx;
+
+    idx = lms_db_cache_find_db(cache, db);
+    if (idx >= 0) {
+        e = cache->entries + idx;
+        if (e->data == data)
+            return 0;
+        else {
+            fprintf(stderr,
+                    "ERROR: cache %p for db %p has another data registered"
+                    ": %p (current is %p)\n", cache, db, e->data, data);
+            return -1;
+        }
+    }
+
+    idx = cache->size;
+    if (lms_db_cache_resize(cache, cache->size + 1) != 0) {
+        return -2;
+    }
+
+    e = cache->entries + idx;
+    e->db = db;
+    e->data = data;
+    return 0;
+}
+
+int
+lms_db_cache_del(struct lms_db_cache *cache, const sqlite3 *db, void *data)
+{
+    int idx;
+    struct lms_db_cache_entry *e;
+
+    idx = lms_db_cache_find_db(cache, db);
+    if (idx < 0) {
+        fprintf(stderr, "ERROR: no db %p found in cache %p\n", db, cache);
+        return -1;
+    }
+
+    e = cache->entries + idx;
+    if (e->data != data) {
+        fprintf(stderr, "ERROR: data mismatch in request to delete from cache: "
+                "want %p, has %p, cache %p, db %p\n", data, e->data, cache, db);
+        return -2;
+    }
+
+    for (; idx < cache->size - 1; idx++)
+        cache->entries[idx] = cache->entries[idx + 1];
+
+    return lms_db_cache_resize(cache, cache->size - 1);
+}
+
+int
+lms_db_cache_get(struct lms_db_cache *cache, const sqlite3 *db, void **pdata)
+{
+    int idx;
+
+    idx = lms_db_cache_find_db(cache, db);
+    if (idx < 0)
+        return -1;
+
+    *pdata = cache->entries[idx].data;
+    return 0;
+}
+
+int
+lms_db_create_core_tables_if_required(sqlite3 *db)
+{
+    char *errmsg;
+    int r;
+
+    errmsg = NULL;
+    r = sqlite3_exec(db,
+                     "CREATE TABLE IF NOT EXISTS lms_internal ("
+                     "tab TEXT NOT NULL UNIQUE, "
+                     "version INTEGER NOT NULL"
+                     ")",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create 'lms_internal' table: %s\n",
+                errmsg);
+        sqlite3_free(errmsg);
+        return -1;
+    }
+
+    r = sqlite3_exec(db,
+                     "CREATE TABLE IF NOT EXISTS files ("
+                     "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+                     "path BLOB NOT NULL UNIQUE, "
+                     "mtime INTEGER NOT NULL, "
+                     "dtime INTEGER NOT NULL, "
+                     "size INTEGER NOT NULL"
+                     ")",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create 'files' table: %s\n", errmsg);
+        sqlite3_free(errmsg);
+        return -2;
+    }
+
+    r = sqlite3_exec(db,
+                     "CREATE INDEX IF NOT EXISTS files_path_idx ON files ("
+                     "path"
+                     ")",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create 'files_path_idx' index: %s\n",
+                errmsg);
+        sqlite3_free(errmsg);
+        return -3;
+    }
+
+    return 0;
+}
+
+
+sqlite3_stmt *
+lms_db_compile_stmt_begin_transaction(sqlite3 *db)
+{
+    return lms_db_compile_stmt(db, "BEGIN TRANSACTION");
+}
+
+int
+lms_db_begin_transaction(sqlite3_stmt *stmt)
+{
+    int r, ret;
+
+    ret = 0;
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not begin transaction: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -1;
+    }
+
+    r = sqlite3_reset(stmt);
+    if (r != SQLITE_OK)
+        fprintf(stderr, "ERROR: could not reset SQL statement: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+
+    return ret;
+}
+
+sqlite3_stmt *
+lms_db_compile_stmt_end_transaction(sqlite3 *db)
+{
+    return lms_db_compile_stmt(db, "COMMIT");
+}
+
+int
+lms_db_end_transaction(sqlite3_stmt *stmt)
+{
+    int r, ret;
+
+    ret = 0;
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not end transaction: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -1;
+    }
+
+    r = sqlite3_reset(stmt);
+    if (r != SQLITE_OK)
+        fprintf(stderr, "ERROR: could not reset SQL statement: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+
+    return ret;
+}
+
+sqlite3_stmt *
+lms_db_compile_stmt_get_file_info(sqlite3 *db)
+{
+    return lms_db_compile_stmt(db,
+        "SELECT id, mtime, dtime, size FROM files WHERE path = ?");
+}
+
+int
+lms_db_get_file_info(sqlite3_stmt *stmt, struct lms_file_info *finfo)
+{
+    int r, ret;
+
+    ret = lms_db_bind_blob(stmt, 1, finfo->path, finfo->path_len);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r == SQLITE_DONE) {
+        ret = 1;
+        finfo->id = -1;
+        goto done;
+    }
+
+    if (r != SQLITE_ROW) {
+        fprintf(stderr, "ERROR: could not get file info from table: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -2;
+        goto done;
+    }
+
+    finfo->id = sqlite3_column_int64(stmt, 0);
+    finfo->mtime = sqlite3_column_int(stmt, 1);
+    finfo->dtime = sqlite3_column_int(stmt, 2);
+    finfo->size = sqlite3_column_int(stmt, 3);
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+sqlite3_stmt *
+lms_db_compile_stmt_update_file_info(sqlite3 *db)
+{
+    return lms_db_compile_stmt(db,
+        "UPDATE files SET mtime = ?, dtime = ?, size = ? WHERE id = ?");
+}
+
+int
+lms_db_update_file_info(sqlite3_stmt *stmt, const struct lms_file_info *finfo)
+{
+    int r, ret;
+
+    ret = lms_db_bind_int(stmt, 1, finfo->mtime);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 2, finfo->dtime);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 3, finfo->size);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 4, finfo->id);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not update file info: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -5;
+        goto done;
+    }
+
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+sqlite3_stmt *
+lms_db_compile_stmt_insert_file_info(sqlite3 *db)
+{
+    return lms_db_compile_stmt(db,
+        "INSERT INTO files (path, mtime, dtime, size) VALUES(?, ?, ?, ?)");
+}
+
+int
+lms_db_insert_file_info(sqlite3_stmt *stmt, struct lms_file_info *finfo)
+{
+    int r, ret;
+
+    ret = lms_db_bind_blob(stmt, 1, finfo->path, finfo->path_len);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 2, finfo->mtime);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 3, finfo->dtime);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 4, finfo->size);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert file info: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -5;
+        goto done;
+    }
+
+    finfo->id = sqlite3_last_insert_rowid(sqlite3_db_handle(stmt));
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+sqlite3_stmt *
+lms_db_compile_stmt_delete_file_info(sqlite3 *db)
+{
+    return lms_db_compile_stmt(db, "DELETE FROM files WHERE id = ?");
+}
+
+int
+lms_db_delete_file_info(sqlite3_stmt *stmt, const struct lms_file_info *finfo)
+{
+    int r, ret;
+
+    ret = lms_db_bind_int64(stmt, 1, finfo->id);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not delete file info: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -2;
+        goto done;
+    }
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+sqlite3_stmt *
+lms_db_compile_stmt_set_file_dtime(sqlite3 *db)
+{
+    return lms_db_compile_stmt(db, "UPDATE files SET dtime = ? WHERE id = ?");
+}
+
+int
+lms_db_set_file_dtime(sqlite3_stmt *stmt, const struct lms_file_info *finfo)
+{
+    int r, ret;
+
+    ret = lms_db_bind_int(stmt, 1, finfo->dtime);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int64(stmt, 1, finfo->id);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not set file dtime: %s\n",
+                sqlite3_errmsg(sqlite3_db_handle(stmt)));
+        ret = -3;
+        goto done;
+    }
+
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+sqlite3_stmt *
+lms_db_compile_stmt_get_files(sqlite3 *db)
+{
+    return lms_db_compile_stmt(db,
+        "SELECT id, path, mtime, dtime, size FROM files WHERE path LIKE ?");
+}
+
+int
+lms_db_get_files(sqlite3_stmt *stmt, const char *path, int len)
+{
+    int ret;
+
+    ret = lms_db_bind_blob(stmt, 1, path, len);
+    return ret;
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner_db_image.c b/lightmediascanner/src/lib/lightmediascanner_db_image.c
new file mode 100644 (file)
index 0000000..d75676b
--- /dev/null
@@ -0,0 +1,234 @@
+#include <lightmediascanner_db.h>
+#include "lightmediascanner_db_private.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+struct lms_db_image {
+    sqlite3 *db;
+    sqlite3_stmt *insert;
+    unsigned int _references;
+    unsigned int _is_started:1;
+};
+
+static struct lms_db_cache _cache = {0, NULL};
+
+static int
+_db_table_updater_images_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
+    char *errmsg;
+    int r, ret;
+
+    errmsg = NULL;
+    r = sqlite3_exec(db,
+                     "CREATE TABLE IF NOT EXISTS images ("
+                     "id INTEGER PRIMARY KEY, "
+                     "title TEXT, "
+                     "artist TEXT, "
+                     "date INTEGER NOT NULL, "
+                     "width INTEGER NOT NULL, "
+                     "height INTEGER NOT NULL, "
+                     "orientation INTEGER NOT NULL, "
+                     "gps_lat REAL DEFAULT 0.0, "
+                     "gps_long REAL DEFAULT 0.0, "
+                     "gps_alt REAL DEFAULT 0.0"
+                     ")",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create 'images' table: %s\n", errmsg);
+        sqlite3_free(errmsg);
+        return -1;
+    }
+
+    r = sqlite3_exec(db,
+                     "CREATE INDEX IF NOT EXISTS images_date_idx ON images ("
+                     "date"
+                     ")",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create 'images_date_idx' index: %s\n",
+                errmsg);
+        sqlite3_free(errmsg);
+        return -2;
+    }
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_images_on_files_deleted "
+        "DELETE ON files FOR EACH ROW BEGIN "
+        " DELETE FROM images WHERE id = OLD.id; END;");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_files_on_images_deleted "
+        "DELETE ON images FOR EACH ROW BEGIN "
+        " DELETE FROM files WHERE id = OLD.id; END;");
+
+  done:
+    return ret;
+}
+
+static lms_db_table_updater_t _db_table_updater_images[] = {
+    _db_table_updater_images_0
+};
+
+
+static int
+_db_create_table_if_required(sqlite3 *db)
+{
+    return lms_db_table_update_if_required(db, "images",
+         LMS_ARRAY_SIZE(_db_table_updater_images),
+         _db_table_updater_images);
+}
+
+lms_db_image_t *
+lms_db_image_new(sqlite3 *db)
+{
+    lms_db_image_t *ldi;
+    void *p;
+
+    if (lms_db_cache_get(&_cache, db, &p) == 0) {
+        ldi = p;
+        ldi->_references++;
+        return ldi;
+    }
+
+    if (!db)
+        return NULL;
+
+    if (_db_create_table_if_required(db) != 0) {
+        fprintf(stderr, "ERROR: could not create table.\n");
+        return NULL;
+    }
+
+    ldi = calloc(1, sizeof(lms_db_image_t));
+    ldi->_references = 1;
+    ldi->db = db;
+
+    if (lms_db_cache_add(&_cache, db, ldi) != 0) {
+        lms_db_image_free(ldi);
+        return NULL;
+    }
+
+    return ldi;
+}
+
+int
+lms_db_image_start(lms_db_image_t *ldi)
+{
+    if (!ldi)
+        return -1;
+    if (ldi->_is_started)
+        return 0;
+
+    ldi->insert = lms_db_compile_stmt(ldi->db,
+        "INSERT OR REPLACE INTO images ("
+        "id, title, artist, date, width, height, orientation, "
+        "gps_lat, gps_long, gps_alt) VALUES ("
+        "?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+    if (!ldi->insert)
+        return -2;
+
+    ldi->_is_started = 1;
+    return 0;
+}
+
+int
+lms_db_image_free(lms_db_image_t *ldi)
+{
+    int r;
+
+    if (!ldi)
+        return -1;
+    if (ldi->_references == 0) {
+        fprintf(stderr, "ERROR: over-called lms_db_image_free(%p)\n", ldi);
+        return -1;
+    }
+
+    ldi->_references--;
+    if (ldi->_references > 0)
+        return 0;
+
+    if (ldi->insert)
+        lms_db_finalize_stmt(ldi->insert, "insert");
+
+    r = lms_db_cache_del(&_cache, ldi->db, ldi);
+    free(ldi);
+
+    return r;
+}
+
+static int
+_db_insert(lms_db_image_t *ldi, const struct lms_image_info *info)
+{
+    sqlite3_stmt *stmt;
+    int r, ret;
+
+    stmt = ldi->insert;
+
+    ret = lms_db_bind_int64(stmt, 1, info->id);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_text(stmt, 2, info->title.str, info->title.len);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_text(stmt, 3, info->artist.str, info->artist.len);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 4, info->date);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 5, info->width);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 6, info->height);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 7, info->orientation);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_double(stmt, 8, info->gps.latitude);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_double(stmt, 9, info->gps.longitude);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_double(stmt, 10, info->gps.altitude);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert image info: %s\n",
+                sqlite3_errmsg(ldi->db));
+        ret = -11;
+        goto done;
+    }
+
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+int
+lms_db_image_add(lms_db_image_t *ldi, struct lms_image_info *info)
+{
+    if (!ldi)
+        return -1;
+    if (!info)
+        return -2;
+    if (info->id < 1)
+        return -3;
+
+    return _db_insert(ldi, info);
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner_db_playlist.c b/lightmediascanner/src/lib/lightmediascanner_db_playlist.c
new file mode 100644 (file)
index 0000000..a7dcf1e
--- /dev/null
@@ -0,0 +1,198 @@
+#include <lightmediascanner_db.h>
+#include "lightmediascanner_db_private.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+struct lms_db_playlist {
+    sqlite3 *db;
+    sqlite3_stmt *insert;
+    unsigned int _references;
+    unsigned int _is_started:1;
+};
+
+static struct lms_db_cache _cache = {0, NULL};
+
+static int
+_db_table_updater_playlists_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
+    char *errmsg;
+    int r, ret;
+
+    errmsg = NULL;
+    r = sqlite3_exec(db,
+                     "CREATE TABLE IF NOT EXISTS playlists ("
+                     "id INTEGER PRIMARY KEY, "
+                     "title TEXT, "
+                     "n_entries INTEGER NOT NULL"
+                     ")",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create 'playlists' table: %s\n",
+                errmsg);
+        sqlite3_free(errmsg);
+        return -1;
+    }
+
+    r = sqlite3_exec(db,
+                     "CREATE INDEX IF NOT EXISTS playlists_title_idx ON "
+                     "playlists (title)",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr,
+                "ERROR: could not create 'playlists_title_idx' index: %s\n",
+                errmsg);
+        sqlite3_free(errmsg);
+        return -2;
+    }
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_playlists_on_files_deleted "
+        "DELETE ON files FOR EACH ROW BEGIN "
+        " DELETE FROM playlists WHERE id = OLD.id; END;");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_files_on_playlists_deleted "
+        "DELETE ON playlists FOR EACH ROW BEGIN "
+        " DELETE FROM files WHERE id = OLD.id; END;");
+
+  done:
+    return ret;
+}
+
+static lms_db_table_updater_t _db_table_updater_playlists[] = {
+    _db_table_updater_playlists_0
+};
+
+
+static int
+_db_create_table_if_required(sqlite3 *db)
+{
+    return lms_db_table_update_if_required(db, "playlists",
+         LMS_ARRAY_SIZE(_db_table_updater_playlists),
+         _db_table_updater_playlists);
+}
+
+lms_db_playlist_t *
+lms_db_playlist_new(sqlite3 *db)
+{
+    lms_db_playlist_t *ldp;
+    void *p;
+
+    if (lms_db_cache_get(&_cache, db, &p) == 0) {
+        ldp = p;
+        ldp->_references++;
+        return ldp;
+    }
+
+    if (!db)
+        return NULL;
+
+    if (_db_create_table_if_required(db) != 0) {
+        fprintf(stderr, "ERROR: could not create table.\n");
+        return NULL;
+    }
+
+    ldp = calloc(1, sizeof(lms_db_playlist_t));
+    ldp->_references = 1;
+    ldp->db = db;
+
+    if (lms_db_cache_add(&_cache, db, ldp) != 0) {
+        lms_db_playlist_free(ldp);
+        return NULL;
+    }
+
+    return ldp;
+}
+
+int
+lms_db_playlist_start(lms_db_playlist_t *ldp)
+{
+    if (!ldp)
+        return -1;
+    if (ldp->_is_started)
+        return 0;
+
+    ldp->insert = lms_db_compile_stmt(ldp->db,
+        "INSERT OR REPLACE INTO playlists (id, title, n_entries) "
+        "VALUES (?, ?, ?)");
+    if (!ldp->insert)
+        return -2;
+
+    ldp->_is_started = 1;
+    return 0;
+}
+
+int
+lms_db_playlist_free(lms_db_playlist_t *ldp)
+{
+    int r;
+
+    if (!ldp)
+        return -1;
+    if (ldp->_references == 0) {
+        fprintf(stderr, "ERROR: over-called lms_db_playlist_free(%p)\n", ldp);
+        return -1;
+    }
+
+    ldp->_references--;
+    if (ldp->_references > 0)
+        return 0;
+
+    if (ldp->insert)
+        lms_db_finalize_stmt(ldp->insert, "insert");
+
+    r = lms_db_cache_del(&_cache, ldp->db, ldp);
+    free(ldp);
+
+    return r;
+}
+
+static int
+_db_insert(lms_db_playlist_t *ldp, const struct lms_playlist_info *info)
+{
+    sqlite3_stmt *stmt;
+    int r, ret;
+
+    stmt = ldp->insert;
+
+    ret = lms_db_bind_int64(stmt, 1, info->id);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_text(stmt, 2, info->title.str, info->title.len);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_int(stmt, 3, info->n_entries);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert playlist info: %s\n",
+                sqlite3_errmsg(ldp->db));
+        ret = -4;
+        goto done;
+    }
+
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+int
+lms_db_playlist_add(lms_db_playlist_t *ldp, struct lms_playlist_info *info)
+{
+    if (!ldp)
+        return -1;
+    if (!info)
+        return -2;
+    if (info->id < 1)
+        return -3;
+
+    return _db_insert(ldp, info);
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner_db_private.h b/lightmediascanner/src/lib/lightmediascanner_db_private.h
new file mode 100644 (file)
index 0000000..4ef6df3
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifndef _LIGHTMEDIASCANNER_DB_PRIVATE_H_
+#define _LIGHTMEDIASCANNER_DB_PRIVATE_H_ 1
+
+#ifdef __GNUC__
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#  define GNUC_NON_NULL(...) __attribute__((nonnull(__VA_ARGS__)))
+# else
+#  define GNUC_NON_NULL(...)
+# endif
+#else
+#  define GNUC_NON_NULL(...)
+#endif
+
+#include <sqlite3.h>
+#include <sys/types.h>
+#include "lightmediascanner_plugin.h"
+
+sqlite3_stmt *lms_db_compile_stmt(sqlite3 *db, const char *sql) GNUC_NON_NULL(1, 2);
+int lms_db_finalize_stmt(sqlite3_stmt *stmt, const char *name) GNUC_NON_NULL(1, 2);
+int lms_db_reset_stmt(sqlite3_stmt *stmt) GNUC_NON_NULL(1);
+int lms_db_bind_text(sqlite3_stmt *stmt, int col, const char *text, int len) GNUC_NON_NULL(1);
+int lms_db_bind_blob(sqlite3_stmt *stmt, int col, const void *blob, int len) GNUC_NON_NULL(1);
+int lms_db_bind_int64(sqlite3_stmt *stmt, int col, int64_t value) GNUC_NON_NULL(1);
+int lms_db_bind_int64_or_null(sqlite3_stmt *stmt, int col, int64_t *p_value) GNUC_NON_NULL(1);
+int lms_db_bind_int(sqlite3_stmt *stmt, int col, int value) GNUC_NON_NULL(1);
+int lms_db_bind_double(sqlite3_stmt *stmt, int col, double value) GNUC_NON_NULL(1);
+int lms_db_create_trigger_if_not_exists(sqlite3 *db, const char *sql) GNUC_NON_NULL(1, 2);
+
+int lms_db_table_version_get(sqlite3 *db, const char *table) GNUC_NON_NULL(1, 2);
+int lms_db_table_version_set(sqlite3 *db, const char *table, unsigned int version) GNUC_NON_NULL(1, 2);
+
+typedef int (*lms_db_table_updater_t)(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run);
+
+int lms_db_table_update(sqlite3 *db, const char *table, unsigned int current_version, unsigned int last_version, const lms_db_table_updater_t *updaters) GNUC_NON_NULL(1, 2, 5);
+int lms_db_table_update_if_required(sqlite3 *db, const char *table, unsigned int last_version, lms_db_table_updater_t *updaters) GNUC_NON_NULL(1, 2, 4);
+
+struct lms_db_cache_entry {
+    const sqlite3 *db;
+    void *data;
+};
+
+struct lms_db_cache {
+    int size;
+    struct lms_db_cache_entry *entries;
+};
+
+int lms_db_cache_add(struct lms_db_cache *cache, const sqlite3 *db, void *data) GNUC_NON_NULL(1, 2, 3);
+int lms_db_cache_del(struct lms_db_cache *cache, const sqlite3 *db, void *data) GNUC_NON_NULL(1, 2, 3);
+int lms_db_cache_get(struct lms_db_cache *cache, const sqlite3 *db, void **pdata) GNUC_NON_NULL(1, 2, 3);
+
+int lms_db_create_core_tables_if_required(sqlite3 *db) GNUC_NON_NULL(1);
+
+sqlite3_stmt *lms_db_compile_stmt_begin_transaction(sqlite3 *db) GNUC_NON_NULL(1);
+sqlite3_stmt *lms_db_compile_stmt_end_transaction(sqlite3 *db) GNUC_NON_NULL(1);
+sqlite3_stmt *lms_db_compile_stmt_get_file_info(sqlite3 *db) GNUC_NON_NULL(1);
+sqlite3_stmt *lms_db_compile_stmt_insert_file_info(sqlite3 *db) GNUC_NON_NULL(1);
+sqlite3_stmt *lms_db_compile_stmt_update_file_info(sqlite3 *db) GNUC_NON_NULL(1);
+sqlite3_stmt *lms_db_compile_stmt_delete_file_info(sqlite3 *db) GNUC_NON_NULL(1);
+sqlite3_stmt *lms_db_compile_stmt_set_file_dtime(sqlite3 *db) GNUC_NON_NULL(1);
+sqlite3_stmt *lms_db_compile_stmt_get_files(sqlite3 *db) GNUC_NON_NULL(1);
+
+int lms_db_begin_transaction(sqlite3_stmt *stmt) GNUC_NON_NULL(1);
+int lms_db_end_transaction(sqlite3_stmt *stmt) GNUC_NON_NULL(1);
+int lms_db_update_file_info(sqlite3_stmt *stmt, const struct lms_file_info *finfo) GNUC_NON_NULL(1, 2);
+int lms_db_get_file_info(sqlite3_stmt *stmt, struct lms_file_info *finfo) GNUC_NON_NULL(1, 2);
+int lms_db_insert_file_info(sqlite3_stmt *stmt, struct lms_file_info *finfo) GNUC_NON_NULL(1, 2);
+int lms_db_delete_file_info(sqlite3_stmt *stmt, const struct lms_file_info *finfo) GNUC_NON_NULL(1, 2);
+int lms_db_set_file_dtime(sqlite3_stmt *stmt, const struct lms_file_info *finfo) GNUC_NON_NULL(1, 2);
+int lms_db_get_files(sqlite3_stmt *stmt, const char *path, int len) GNUC_NON_NULL(1, 2);
+
+
+
+#endif /* _LIGHTMEDIASCANNER_DB_PRIVATE_H_ */
diff --git a/lightmediascanner/src/lib/lightmediascanner_db_video.c b/lightmediascanner/src/lib/lightmediascanner_db_video.c
new file mode 100644 (file)
index 0000000..f891bb9
--- /dev/null
@@ -0,0 +1,210 @@
+#include <lightmediascanner_db.h>
+#include "lightmediascanner_db_private.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+struct lms_db_video {
+    sqlite3 *db;
+    sqlite3_stmt *insert;
+    unsigned int _references;
+    unsigned int _is_started:1;
+};
+
+static struct lms_db_cache _cache = {0, NULL};
+
+static int
+_db_table_updater_videos_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
+    char *errmsg;
+    int r, ret;
+
+    errmsg = NULL;
+    r = sqlite3_exec(db,
+                     "CREATE TABLE IF NOT EXISTS videos ("
+                     "id INTEGER PRIMARY KEY, "
+                     "title TEXT, "
+                     "artist TEXT"
+                     ")",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not create 'videos' table: %s\n", errmsg);
+        sqlite3_free(errmsg);
+        return -1;
+    }
+
+    r = sqlite3_exec(db,
+                     "CREATE INDEX IF NOT EXISTS videos_title_idx ON videos ("
+                     "title"
+                     ")",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr,
+                "ERROR: could not create 'videos_title_idx' index: %s\n",
+                errmsg);
+        sqlite3_free(errmsg);
+        return -2;
+    }
+
+    r = sqlite3_exec(db,
+                     "CREATE INDEX IF NOT EXISTS videos_artist_idx ON videos ("
+                     "artist"
+                     ")",
+                     NULL, NULL, &errmsg);
+    if (r != SQLITE_OK) {
+        fprintf(stderr,
+                "ERROR: could not create 'videos_artist_idx' index: %s\n",
+                errmsg);
+        sqlite3_free(errmsg);
+        return -3;
+    }
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_videos_on_files_deleted "
+        "DELETE ON files FOR EACH ROW BEGIN "
+        " DELETE FROM videos WHERE id = OLD.id; END;");
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_create_trigger_if_not_exists(db,
+        "delete_files_on_videos_deleted "
+        "DELETE ON videos FOR EACH ROW BEGIN "
+        " DELETE FROM files WHERE id = OLD.id; END;");
+
+  done:
+    return ret;
+}
+
+static lms_db_table_updater_t _db_table_updater_videos[] = {
+    _db_table_updater_videos_0
+};
+
+
+static int
+_db_create_table_if_required(sqlite3 *db)
+{
+    return lms_db_table_update_if_required(db, "videos",
+         LMS_ARRAY_SIZE(_db_table_updater_videos),
+         _db_table_updater_videos);
+}
+
+lms_db_video_t *
+lms_db_video_new(sqlite3 *db)
+{
+    lms_db_video_t *ldv;
+    void *p;
+
+    if (lms_db_cache_get(&_cache, db, &p) == 0) {
+        ldv = p;
+        ldv->_references++;
+        return ldv;
+    }
+
+    if (!db)
+        return NULL;
+
+    if (_db_create_table_if_required(db) != 0) {
+        fprintf(stderr, "ERROR: could not create table.\n");
+        return NULL;
+    }
+
+    ldv = calloc(1, sizeof(lms_db_video_t));
+    ldv->_references = 1;
+    ldv->db = db;
+
+    if (lms_db_cache_add(&_cache, db, ldv) != 0) {
+        lms_db_video_free(ldv);
+        return NULL;
+    }
+
+    return ldv;
+}
+
+int
+lms_db_video_start(lms_db_video_t *ldv)
+{
+    if (!ldv)
+        return -1;
+    if (ldv->_is_started)
+        return 0;
+
+    ldv->insert = lms_db_compile_stmt(ldv->db,
+        "INSERT OR REPLACE INTO videos (id, title, artist) VALUES (?, ?, ?)");
+    if (!ldv->insert)
+        return -2;
+
+    ldv->_is_started = 1;
+    return 0;
+}
+
+int
+lms_db_video_free(lms_db_video_t *ldv)
+{
+    int r;
+
+    if (!ldv)
+        return -1;
+    if (ldv->_references == 0) {
+        fprintf(stderr, "ERROR: over-called lms_db_video_free(%p)\n", ldv);
+        return -1;
+    }
+
+    ldv->_references--;
+    if (ldv->_references > 0)
+        return 0;
+
+    if (ldv->insert)
+        lms_db_finalize_stmt(ldv->insert, "insert");
+
+    r = lms_db_cache_del(&_cache, ldv->db, ldv);
+    free(ldv);
+
+    return r;
+}
+
+static int
+_db_insert(lms_db_video_t *ldv, const struct lms_video_info *info)
+{
+    sqlite3_stmt *stmt;
+    int r, ret;
+
+    stmt = ldv->insert;
+
+    ret = lms_db_bind_int64(stmt, 1, info->id);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_text(stmt, 2, info->title.str, info->title.len);
+    if (ret != 0)
+        goto done;
+
+    ret = lms_db_bind_text(stmt, 3, info->artist.str, info->artist.len);
+    if (ret != 0)
+        goto done;
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_DONE) {
+        fprintf(stderr, "ERROR: could not insert video info: %s\n",
+                sqlite3_errmsg(ldv->db));
+        ret = -4;
+        goto done;
+    }
+
+    ret = 0;
+
+  done:
+    lms_db_reset_stmt(stmt);
+
+    return ret;
+}
+
+int
+lms_db_video_add(lms_db_video_t *ldv, struct lms_video_info *info)
+{
+    if (!ldv)
+        return -1;
+    if (!info)
+        return -2;
+    if (info->id < 1)
+        return -3;
+
+    return _db_insert(ldv, info);
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner_plugin.h b/lightmediascanner/src/lib/lightmediascanner_plugin.h
new file mode 100644 (file)
index 0000000..5c30667
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifndef _LIGHTMEDIASCANNER_PLUGIN_H_
+#define _LIGHTMEDIASCANNER_PLUGIN_H_ 1
+
+#include <lightmediascanner.h>
+#include <lightmediascanner_charset_conv.h>
+#include <sqlite3.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    struct lms_file_info {
+        const char *path; /**< file path */
+        int path_len; /**< path length */
+        int base; /**< index of basename inside path */
+        int64_t id; /**< database id */
+        time_t mtime; /**< in-disk modification time */
+        time_t dtime; /**< deletion time */
+        size_t size; /**< file size in bytes */
+    };
+
+    struct lms_context {
+        sqlite3 *db;
+        lms_charset_conv_t *cs_conv;
+    };
+
+    typedef void *(*lms_plugin_match_fn_t)(lms_plugin_t *p, const char *path, int len, int base);
+    typedef int (*lms_plugin_parse_fn_t)(lms_plugin_t *p, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match);
+    typedef int (*lms_plugin_close_fn_t)(lms_plugin_t *p);
+    typedef int (*lms_plugin_setup_fn_t)(lms_plugin_t *p, struct lms_context *ctxt);
+    typedef int (*lms_plugin_start_fn_t)(lms_plugin_t *p, struct lms_context *ctxt);
+    typedef int (*lms_plugin_finish_fn_t)(lms_plugin_t *p, struct lms_context *ctxt);
+
+    struct lms_plugin {
+        const char *name;
+        lms_plugin_match_fn_t match;
+        lms_plugin_parse_fn_t parse;
+        lms_plugin_close_fn_t close;
+        lms_plugin_setup_fn_t setup;
+        lms_plugin_start_fn_t start;
+        lms_plugin_finish_fn_t finish;
+    };
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _LIGHTMEDIASCANNER_PLUGIN_H_ */
diff --git a/lightmediascanner/src/lib/lightmediascanner_private.h b/lightmediascanner/src/lib/lightmediascanner_private.h
new file mode 100644 (file)
index 0000000..9a0afde
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifndef _LIGHTMEDIASCANNER_PRIVATE_H_
+#define _LIGHTMEDIASCANNER_PRIVATE_H_ 1
+
+#ifdef __GNUC__
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#  define GNUC_NON_NULL(...) __attribute__((nonnull(__VA_ARGS__)))
+# else
+#  define GNUC_NON_NULL(...)
+# endif
+#else
+#  define GNUC_NON_NULL(...)
+#endif
+
+#include "lightmediascanner.h"
+#include "lightmediascanner_plugin.h"
+#include "lightmediascanner_charset_conv.h"
+#include <sys/types.h>
+#include <poll.h>
+#include <limits.h>
+#include <sqlite3.h>
+
+#define PATH_SIZE PATH_MAX
+
+struct fds {
+    int r;
+    int w;
+};
+
+/* info to be carried along lms_process() and lms_check() */
+struct pinfo {
+    struct fds master;
+    struct fds slave;
+    struct pollfd poll;
+    lms_t *lms;
+    pid_t child;
+};
+
+struct parser {
+    lms_plugin_t *plugin;
+    void *dl_handle;
+    char *so_path;
+};
+
+struct lms {
+    struct parser *parsers;
+    int n_parsers;
+    lms_charset_conv_t *cs_conv;
+    char *db_path;
+    int slave_timeout;
+    unsigned int commit_interval;
+    unsigned int is_processing:1;
+};
+
+int lms_parser_del_int(lms_t *lms, int i) GNUC_NON_NULL(1);
+int lms_create_pipes(struct pinfo *pinfo) GNUC_NON_NULL(1);
+int lms_close_pipes(struct pinfo *pinfo) GNUC_NON_NULL(1);
+int lms_create_slave(struct pinfo *pinfo, int (*work)(lms_t *lms, struct fds *fds)) GNUC_NON_NULL(1, 2);
+int lms_restart_slave(struct pinfo *pinfo, int (*work)(lms_t *lms, struct fds *fds)) GNUC_NON_NULL(1, 2);
+int lms_finish_slave(struct pinfo *pinfo, int (*finish)(const struct fds *fds)) GNUC_NON_NULL(1, 2);
+
+int lms_parsers_setup(lms_t *lms, sqlite3 *db) GNUC_NON_NULL(1, 2);
+int lms_parsers_start(lms_t *lms, sqlite3 *db) GNUC_NON_NULL(1, 2);
+int lms_parsers_finish(lms_t *lms, sqlite3 *db) GNUC_NON_NULL(1, 2);
+int lms_parsers_check_using(lms_t *lms, void **parser_match, struct lms_file_info *finfo) GNUC_NON_NULL(1, 2, 3);
+int lms_parsers_run(lms_t *lms, sqlite3 *db, void **parser_match, struct lms_file_info *finfo) GNUC_NON_NULL(1, 2, 3, 4);
+
+
+#endif /* _LIGHTMEDIASCANNER_PRIVATE_H_ */
diff --git a/lightmediascanner/src/lib/lightmediascanner_process.c b/lightmediascanner/src/lib/lightmediascanner_process.c
new file mode 100644 (file)
index 0000000..80dc952
--- /dev/null
@@ -0,0 +1,879 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _GNU_SOURCE
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <signal.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lightmediascanner.h"
+#include "lightmediascanner_private.h"
+#include "lightmediascanner_db_private.h"
+
+struct db {
+    sqlite3 *handle;
+    sqlite3_stmt *transaction_begin;
+    sqlite3_stmt *transaction_commit;
+    sqlite3_stmt *get_file_info;
+    sqlite3_stmt *insert_file_info;
+    sqlite3_stmt *update_file_info;
+    sqlite3_stmt *delete_file_info;
+    sqlite3_stmt *set_file_dtime;
+};
+
+/***********************************************************************
+ * Master-Slave communication.
+ ***********************************************************************/
+
+static int
+_master_send_path(const struct fds *master, int plen, int dlen, const char *p)
+{
+    int lengths[2];
+
+    lengths[0] = plen;
+    lengths[1] = dlen;
+
+    if (write(master->w, lengths, sizeof(lengths)) < 0) {
+        perror("write");
+        return -1;
+    }
+
+    if (write(master->w, p, plen) < 0) {
+        perror("write");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+_master_send_finish(const struct fds *master)
+{
+    const int lengths[2] = {-1, -1};
+
+    if (write(master->w, lengths, sizeof(lengths)) < 0) {
+        perror("write");
+        return -1;
+    }
+    return 0;
+}
+
+static int
+_master_recv_reply(const struct fds *master, struct pollfd *pfd, int *reply, int timeout)
+{
+    int r;
+
+    r = poll(pfd, 1, timeout);
+    if (r < 0) {
+        perror("poll");
+        return -1;
+    }
+
+    if (r == 0)
+        return 1;
+
+    if (read(master->r, reply, sizeof(*reply)) != sizeof(*reply)) {
+        perror("read");
+        return -2;
+    }
+
+    return 0;
+}
+
+static int
+_slave_send_reply(const struct fds *slave, int reply)
+{
+    if (write(slave->w, &reply, sizeof(reply)) == 0) {
+        perror("write");
+        return -1;
+    }
+    return 0;
+}
+
+static int
+_slave_recv_path(const struct fds *slave, int *plen, int *dlen, char *path)
+{
+    int lengths[2], r;
+
+    r = read(slave->r, lengths, sizeof(lengths));
+    if (r != sizeof(lengths)) {
+        perror("read");
+        return -1;
+    }
+    *plen = lengths[0];
+    *dlen = lengths[1];
+
+    if (*plen == -1)
+        return 0;
+
+    if (*plen > PATH_SIZE) {
+        fprintf(stderr, "ERROR: path too long (%d/%d)\n", *plen, PATH_SIZE);
+        return -2;
+    }
+
+    r = read(slave->r, path, *plen);
+    if (r != *plen) {
+        fprintf(stderr, "ERROR: could not read whole path %d/%d\n", r, *plen);
+        return -3;
+    }
+
+    path[*plen] = 0;
+    return 0;
+}
+
+
+/***********************************************************************
+ * Slave-side.
+ ***********************************************************************/
+
+static int
+_db_compile_all_stmts(struct db *db)
+{
+    sqlite3 *handle;
+
+    handle = db->handle;
+    db->transaction_begin = lms_db_compile_stmt_begin_transaction(handle);
+    if (!db->transaction_begin)
+        return -1;
+
+    db->transaction_commit = lms_db_compile_stmt_end_transaction(handle);
+    if (!db->transaction_commit)
+        return -2;
+
+    db->get_file_info = lms_db_compile_stmt_get_file_info(handle);
+    if (!db->get_file_info)
+        return -4;
+
+    db->insert_file_info = lms_db_compile_stmt_insert_file_info(handle);
+    if (!db->insert_file_info)
+        return -5;
+
+    db->update_file_info = lms_db_compile_stmt_update_file_info(handle);
+    if (!db->update_file_info)
+        return -6;
+
+    db->delete_file_info = lms_db_compile_stmt_delete_file_info(handle);
+    if (!db->delete_file_info)
+        return -6;
+
+    db->set_file_dtime = lms_db_compile_stmt_set_file_dtime(handle);
+    if (!db->set_file_dtime)
+        return -7;
+
+    return 0;
+}
+
+static struct db *
+_db_open(const char *db_path)
+{
+    struct db *db;
+
+    db = calloc(1, sizeof(*db));
+    if (!db) {
+        perror("calloc");
+        return NULL;
+    }
+
+    if (sqlite3_open(db_path, &db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not open DB \"%s\": %s\n",
+                db_path, sqlite3_errmsg(db->handle));
+        goto error;
+    }
+
+    if (lms_db_create_core_tables_if_required(db->handle) != 0) {
+        fprintf(stderr, "ERROR: could not setup tables and indexes.\n");
+        goto error;
+    }
+
+    return db;
+
+  error:
+    sqlite3_close(db->handle);
+    free(db);
+    return NULL;
+}
+
+static int
+_db_close(struct db *db)
+{
+    if (db->transaction_begin)
+        lms_db_finalize_stmt(db->transaction_begin, "transaction_begin");
+
+    if (db->transaction_commit)
+        lms_db_finalize_stmt(db->transaction_commit, "transaction_commit");
+
+    if (db->get_file_info)
+        lms_db_finalize_stmt(db->get_file_info, "get_file_info");
+
+    if (db->insert_file_info)
+        lms_db_finalize_stmt(db->insert_file_info, "insert_file_info");
+
+    if (db->update_file_info)
+        lms_db_finalize_stmt(db->update_file_info, "update_file_info");
+
+    if (db->delete_file_info)
+        lms_db_finalize_stmt(db->delete_file_info, "delete_file_info");
+
+    if (db->set_file_dtime)
+        lms_db_finalize_stmt(db->set_file_dtime, "set_file_dtime");
+
+    if (sqlite3_close(db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: clould not close DB: %s\n",
+                sqlite3_errmsg(db->handle));
+        return -1;
+    }
+    free(db);
+
+    return 0;
+}
+
+static int
+_retrieve_file_status(struct db *db, struct lms_file_info *finfo)
+{
+    struct stat st;
+    int r;
+
+    if (stat(finfo->path, &st) != 0) {
+        perror("stat");
+        return -1;
+    }
+
+    r = lms_db_get_file_info(db->get_file_info, finfo);
+    if (r == 0) {
+        if (st.st_mtime <= finfo->mtime && finfo->size == st.st_size)
+            return 0;
+        else {
+            finfo->mtime = st.st_mtime;
+            finfo->size = st.st_size;
+            return 1;
+        }
+    } else if (r == 1) {
+        finfo->mtime = st.st_mtime;
+        finfo->size = st.st_size;
+        return 1;
+    } else
+        return -2;
+}
+
+static void
+_ctxt_init(struct lms_context *ctxt, const lms_t *lms, sqlite3 *db)
+{
+    ctxt->cs_conv = lms->cs_conv;
+    ctxt->db = db;
+}
+
+int
+lms_parsers_setup(lms_t *lms, sqlite3 *db)
+{
+    struct lms_context ctxt;
+    int i;
+
+    _ctxt_init(&ctxt, lms, db);
+
+    for (i = 0; i < lms->n_parsers; i++) {
+        lms_plugin_t *plugin;
+        int r;
+
+        plugin = lms->parsers[i].plugin;
+        r = plugin->setup(plugin, &ctxt);
+        if (r != 0) {
+            fprintf(stderr, "ERROR: parser \"%s\" failed to setup: %d.\n",
+                    plugin->name, r);
+            plugin->finish(plugin, &ctxt);
+            lms_parser_del_int(lms, i);
+            i--; /* cancel i++ */
+        }
+    }
+
+    return 0;
+}
+
+int
+lms_parsers_start(lms_t *lms, sqlite3 *db)
+{
+    struct lms_context ctxt;
+    int i;
+
+    _ctxt_init(&ctxt, lms, db);
+
+    for (i = 0; i < lms->n_parsers; i++) {
+        lms_plugin_t *plugin;
+        int r;
+
+        plugin = lms->parsers[i].plugin;
+        r = plugin->start(plugin, &ctxt);
+        if (r != 0) {
+            fprintf(stderr, "ERROR: parser \"%s\" failed to start: %d.\n",
+                    plugin->name, r);
+            plugin->finish(plugin, &ctxt);
+            lms_parser_del_int(lms, i);
+            i--; /* cancel i++ */
+        }
+    }
+
+    return 0;
+}
+
+int
+lms_parsers_finish(lms_t *lms, sqlite3 *db)
+{
+    struct lms_context ctxt;
+    int i;
+
+    _ctxt_init(&ctxt, lms, db);
+
+    for (i = 0; i < lms->n_parsers; i++) {
+        lms_plugin_t *plugin;
+        int r;
+
+        plugin = lms->parsers[i].plugin;
+        r = plugin->finish(plugin, &ctxt);
+        if (r != 0)
+            fprintf(stderr, "ERROR: parser \"%s\" failed to finish: %d.\n",
+                    plugin->name, r);
+    }
+
+    return 0;
+}
+
+int
+lms_parsers_check_using(lms_t *lms, void **parser_match, struct lms_file_info *finfo)
+{
+    int used, i;
+
+    used = 0;
+    for (i = 0; i < lms->n_parsers; i++) {
+        lms_plugin_t *plugin;
+        void *r;
+
+        plugin = lms->parsers[i].plugin;
+        r = plugin->match(plugin, finfo->path, finfo->path_len, finfo->base);
+        parser_match[i] = r;
+        if (r)
+            used = 1;
+    }
+
+    return used;
+}
+
+int
+lms_parsers_run(lms_t *lms, sqlite3 *db, void **parser_match, struct lms_file_info *finfo)
+{
+    struct lms_context ctxt;
+    int i, failed, available;
+
+    _ctxt_init(&ctxt, lms, db);
+
+    failed = 0;
+    available = 0;
+    for (i = 0; i < lms->n_parsers; i++) {
+        lms_plugin_t *plugin;
+
+        plugin = lms->parsers[i].plugin;
+        if (parser_match[i]) {
+            int r;
+
+            available++;
+            r = plugin->parse(plugin, &ctxt, finfo, parser_match[i]);
+            if (r != 0)
+                failed++;
+        }
+    }
+
+    if (!failed)
+        return 0;
+    else if (failed == available)
+        return -1;
+    else
+        return 1; /* non critical */
+}
+
+static int
+_slave_work(lms_t *lms, struct fds *fds)
+{
+    int r, len, base, counter;
+    char path[PATH_SIZE];
+    void **parser_match;
+    struct db *db;
+
+    db = _db_open(lms->db_path);
+    if (!db)
+        return -1;
+
+    if (lms_parsers_setup(lms, db->handle) != 0) {
+        fprintf(stderr, "ERROR: could not setup parsers.\n");
+        r = -2;
+        goto end;
+    }
+
+    if (_db_compile_all_stmts(db) != 0) {
+        fprintf(stderr, "ERROR: could not compile statements.\n");
+        r = -3;
+        goto end;
+    }
+
+    if (lms_parsers_start(lms, db->handle) != 0) {
+        fprintf(stderr, "ERROR: could not start parsers.\n");
+        r = -4;
+        goto end;
+    }
+    if (lms->n_parsers < 1) {
+        fprintf(stderr, "ERROR: no parser could be started, exit.\n");
+        r = -5;
+        goto end;
+    }
+
+    parser_match = malloc(lms->n_parsers * sizeof(*parser_match));
+    if (!parser_match) {
+        perror("malloc");
+        r = -6;
+        goto end;
+    }
+
+    counter = 0;
+    lms_db_begin_transaction(db->transaction_begin);
+
+    while (((r = _slave_recv_path(fds, &len, &base, path)) == 0) && len > 0) {
+        struct lms_file_info finfo;
+        int used, r;
+
+        finfo.path = path;
+        finfo.path_len = len;
+        finfo.base = base;
+
+        r = _retrieve_file_status(db, &finfo);
+        if (r == 0) {
+            if (finfo.dtime) {
+                finfo.dtime = 0;
+                lms_db_set_file_dtime(db->set_file_dtime, &finfo);
+            }
+            goto inform_end;
+        } else if (r < 0) {
+            fprintf(stderr, "ERROR: could not detect file status.\n");
+            goto inform_end;
+        }
+
+        used = lms_parsers_check_using(lms, parser_match, &finfo);
+        if (!used)
+            goto inform_end;
+
+        finfo.dtime = 0;
+        if (finfo.id > 0)
+            r = lms_db_update_file_info(db->update_file_info, &finfo);
+        else
+            r = lms_db_insert_file_info(db->insert_file_info, &finfo);
+        if (r < 0) {
+            fprintf(stderr, "ERROR: could not register path in DB\n");
+            goto inform_end;
+        }
+
+        r = lms_parsers_run(lms, db->handle, parser_match, &finfo);
+        if (r < 0) {
+            fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
+                    getpid(), finfo.path);
+            lms_db_delete_file_info(db->delete_file_info, &finfo);
+        }
+
+      inform_end:
+        _slave_send_reply(fds, r);
+        counter++;
+        if (counter > lms->commit_interval) {
+            lms_db_end_transaction(db->transaction_commit);
+            lms_db_begin_transaction(db->transaction_begin);
+            counter = 0;
+        }
+    }
+
+    free(parser_match);
+    lms_db_end_transaction(db->transaction_commit);
+  end:
+    lms_parsers_finish(lms, db->handle);
+    _db_close(db);
+
+    return r;
+}
+
+
+/***********************************************************************
+ * Master-side.
+ ***********************************************************************/
+
+static int
+_consume_garbage(struct pollfd *pfd)
+{
+    int r;
+
+    while ((r = poll(pfd, 1, 0)) > 0) {
+        if (pfd->revents & (POLLERR | POLLHUP | POLLNVAL))
+            return 0;
+        else if (pfd->revents & POLLIN) {
+            char c;
+
+            read(pfd->fd, &c, sizeof(c));
+        }
+    }
+
+    return r;
+}
+
+static int
+_close_fds(struct fds *fds)
+{
+    int r;
+
+    r = 0;
+    if (close(fds->r) != 0) {
+        r--;
+        perror("close");
+    }
+
+    if (close(fds->w) != 0) {
+        r--;
+        perror("close");
+    }
+
+    return r;
+}
+
+int
+lms_close_pipes(struct pinfo *pinfo)
+{
+    int r;
+
+    r = _close_fds(&pinfo->master);
+    r += _close_fds(&pinfo->slave);
+
+    return r;
+}
+
+int
+lms_create_pipes(struct pinfo *pinfo)
+{
+    int fds[2];
+
+    if (pipe(fds) != 0) {
+        perror("pipe");
+        return -1;
+    }
+    pinfo->master.r = fds[0];
+    pinfo->slave.w = fds[1];
+
+    if (pipe(fds) != 0) {
+        perror("pipe");
+        close(pinfo->master.r);
+        close(pinfo->slave.w);
+        return -1;
+    }
+    pinfo->slave.r = fds[0];
+    pinfo->master.w = fds[1];
+
+    pinfo->poll.fd = pinfo->master.r;
+    pinfo->poll.events = POLLIN;
+
+    return 0;
+}
+
+int
+lms_create_slave(struct pinfo *pinfo, int (*work)(lms_t *lms, struct fds *fds))
+{
+    int r;
+
+    pinfo->child = fork();
+    if (pinfo->child == -1) {
+        perror("fork");
+        return -1;
+    }
+
+    if (pinfo->child > 0)
+        return 0;
+
+    _close_fds(&pinfo->master);
+    nice(19);
+    r = work(pinfo->lms, &pinfo->slave);
+    lms_free(pinfo->lms);
+    _exit(r);
+    return r; /* shouldn't reach anyway... */
+}
+
+static int
+_waitpid(pid_t pid)
+{
+    int status;
+    pid_t r;
+
+    r = waitpid(pid, &status, 0);
+    if (r > -1)
+        return 0;
+    else
+        perror("waitpid");
+
+    return r;
+}
+
+int
+lms_finish_slave(struct pinfo *pinfo, int (*finish)(const struct fds *fds))
+{
+    int r;
+
+    if (pinfo->child <= 0)
+        return 0;
+
+    r = finish(&pinfo->master);
+    if (r == 0)
+        r = _waitpid(pinfo->child);
+    else {
+        r = kill(pinfo->child, SIGKILL);
+        if (r < 0)
+            perror("kill");
+        else
+            r =_waitpid(pinfo->child);
+    }
+    pinfo->child = 0;
+
+    return r;
+}
+
+int
+lms_restart_slave(struct pinfo *pinfo, int (*work)(lms_t *lms, struct fds *fds))
+{
+    int status;
+
+    if (waitpid(pinfo->child, &status, WNOHANG) > 0) {
+        if (WIFEXITED(status)) {
+            int code;
+
+            code = WEXITSTATUS(status);
+            if (code != 0) {
+                fprintf(stderr, "ERROR: slave returned %d, exit.\n", code);
+                pinfo->child = 0;
+                return -1;
+            }
+        } else {
+            if (WIFSIGNALED(status)) {
+                int code;
+
+                code = WTERMSIG(status);
+                fprintf(stderr, "ERROR: slave was terminated by signal %d.\n",
+                        code);
+            }
+            pinfo->child = 0;
+            return -1;
+        }
+    }
+
+    if (kill(pinfo->child, SIGKILL))
+        perror("kill");
+
+    if (waitpid(pinfo->child, &status, 0) < 0)
+        perror("waitpid");
+
+    _consume_garbage(&pinfo->poll);
+    return lms_create_slave(pinfo, work);
+}
+
+static int
+_strcat(int base, char *path, const char *name)
+{
+    int new_len, name_len;
+
+    name_len = strlen(name);
+    new_len = base + name_len;
+
+    if (new_len >= PATH_SIZE) {
+        path[base] = '\0';
+        fprintf(stderr,
+                "ERROR: path concatenation too long %d of %d "
+                "available: \"%s\" + \"%s\"\n", new_len, PATH_SIZE,
+                path, name);
+        return -1;
+    }
+
+    memcpy(path + base, name, name_len + 1);
+
+    return new_len;
+}
+
+static int
+_process_file(struct pinfo *pinfo, int base, char *path, const char *name)
+{
+    int new_len, reply, r;
+
+    new_len = _strcat(base, path, name);
+    if (new_len < 0)
+        return -1;
+
+    if (_master_send_path(&pinfo->master, new_len, base, path) != 0)
+        return -2;
+
+    r = _master_recv_reply(&pinfo->master, &pinfo->poll, &reply,
+                           pinfo->lms->slave_timeout);
+    if (r < 0)
+        return -3;
+    else if (r == 1) {
+        fprintf(stderr, "ERROR: slave took too long, restart %d\n",
+                pinfo->child);
+        if (lms_restart_slave(pinfo, _slave_work) != 0)
+            return -4;
+        return 1;
+    } else {
+        if (reply < 0) {
+            /* XXX callback library users to inform error. */
+            fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
+                    getpid(), path);
+            return (-reply) << 8;
+        } else
+            return reply;
+    }
+}
+
+static int
+_process_dir(struct pinfo *pinfo, int base, char *path, const char *name)
+{
+    DIR *dir;
+    struct dirent *de;
+    int new_len, r;
+
+    new_len = _strcat(base, path, name);
+    if (new_len < 0)
+        return -1;
+    else if (new_len + 1 >= PATH_SIZE) {
+        fprintf(stderr, "ERROR: path too long\n");
+        return 2;
+    }
+
+    dir = opendir(path);
+    if (dir == NULL) {
+        perror("opendir");
+        return 3;
+    }
+
+    path[new_len] = '/';
+    new_len++;
+
+    r = 0;
+    while ((de = readdir(dir)) != NULL) {
+        if (de->d_name[0] == '.')
+            continue;
+        if (de->d_type == DT_REG) {
+            if (_process_file(pinfo, new_len, path, de->d_name) < 0) {
+                path[new_len - 1] = '\0';
+                fprintf(stderr,
+                        "ERROR: unrecoverable error parsing file, "
+                        "exit \"%s\".\n", path);
+                r = -4;
+                goto end;
+            }
+        } else if (de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) {
+            if (_process_dir(pinfo, new_len, path, de->d_name) < 0) {
+                path[new_len - 1] = '\0';
+                fprintf(stderr,
+                        "ERROR: unrecoverable error parsing dir, "
+                        "exit \"%s\".\n", path);
+                r = -5;
+                goto end;
+            }
+        }
+    }
+
+  end:
+    closedir(dir);
+    return r;
+}
+
+int
+lms_process(lms_t *lms, const char *top_path)
+{
+    struct pinfo pinfo;
+    int r, len;
+    char path[PATH_SIZE], *bname;
+
+    if (!lms) {
+        r = -1;
+        goto end;
+    }
+
+    if (!top_path) {
+        r = -2;
+        goto end;
+    }
+
+    if (lms->is_processing) {
+        fprintf(stderr, "ERROR: is already processing.\n");
+        r = -3;
+        goto end;
+    }
+
+    if (!lms->parsers) {
+        fprintf(stderr, "ERROR: no plugins registered.\n");
+        r = -4;
+        goto end;
+    }
+
+    pinfo.lms = lms;
+
+    if (lms_create_pipes(&pinfo) != 0) {
+        r = -5;
+        goto end;
+    }
+
+    if (lms_create_slave(&pinfo, _slave_work) != 0) {
+        r = -6;
+        goto close_pipes;
+    }
+
+    if (realpath(top_path, path) == NULL) {
+        perror("realpath");
+        r = -7;
+        goto finish_slave;
+    }
+
+    /* search '/' backwards, split dirname and basename, note realpath usage */
+    len = strlen(path);
+    for (; len >= 0 && path[len] != '/'; len--);
+    len++;
+    bname = strdup(path + len);
+    if (bname == NULL) {
+        perror("strdup");
+        r = -8;
+        goto finish_slave;
+    }
+
+    lms->is_processing = 1;
+    r = _process_dir(&pinfo, len, path, bname);
+    lms->is_processing = 0;
+    free(bname);
+
+  finish_slave:
+    lms_finish_slave(&pinfo, _master_send_finish);
+  close_pipes:
+    lms_close_pipes(&pinfo);
+  end:
+    return r;
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner_utils.c b/lightmediascanner/src/lib/lightmediascanner_utils.c
new file mode 100644 (file)
index 0000000..4024ca1
--- /dev/null
@@ -0,0 +1,91 @@
+#include <lightmediascanner_utils.h>
+#include <ctype.h>
+#include <alloca.h>
+
+void
+lms_strstrip(char *str, unsigned int *p_len)
+{
+    int i, len;
+    char *p;
+
+    len = *p_len;
+
+    if (len < 2) /* just '\0'? */
+        return;
+
+    p = str + len - 1;
+    for (i = len - 1; i >= 0; i--) {
+        if (isspace(*p)) {
+            *p = '\0';
+            len--;
+            p--;
+        } else
+            break;
+    }
+    if (len == 0) {
+        *p_len = 0;
+        return;
+    }
+
+    p = str;
+    for (i = 0; i < len; i++) {
+        if (isspace(*p))
+            p++;
+        else
+            break;
+    }
+    len -= i;
+    if (len == 0) {
+        *str = '\0';
+        *p_len = 0;
+        return;
+    }
+
+    *p_len = len;
+
+    if (str < p)
+        for (; len > 0; len--, str++, p++)
+            *str = *p;
+}
+
+int
+lms_which_extension(const char *name, unsigned int name_len, const struct lms_string_size *exts, unsigned int exts_len) {
+    int i;
+    unsigned int *exts_pos;
+    const char *s;
+
+    exts_pos = alloca(exts_len * sizeof(*exts_pos));
+    for (i = 0; i < exts_len; i++)
+        exts_pos[i] = exts[i].len;
+
+    for (s = name + name_len - 1; s >= name; s--) {
+        int i, match;
+        char c1, c2;
+
+        c1 = *s;
+        if (c1 >= 'a')
+            c2 = c1;
+        else
+            c2 = 'a' + c1 - 'A';
+
+        match = 0;
+        for (i = 0; i < exts_len; i++) {
+            if (exts_pos[i] > 0) {
+                char ce;
+
+                ce = exts[i].str[exts_pos[i] - 1];
+                if (ce == c1 || ce == c2) {
+                    if (exts_pos[i] == 1)
+                        return i;
+                    exts_pos[i]--;
+                    match = 1;
+                } else
+                    exts_pos[i] = 0;
+            }
+        }
+        if (!match)
+            return -1;
+    }
+
+    return -1;
+}
diff --git a/lightmediascanner/src/lib/lightmediascanner_utils.h b/lightmediascanner/src/lib/lightmediascanner_utils.h
new file mode 100644 (file)
index 0000000..dd52cf5
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifndef _LIGHTMEDIASCANNER_UTILS_H_
+#define _LIGHTMEDIASCANNER_UTILS_H_ 1
+
+#ifdef API
+#undef API
+#endif
+
+#ifdef __GNUC__
+# if __GNUC__ >= 4
+#  define API __attribute__ ((visibility("default")))
+# else
+#  define API
+# endif
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#  define GNUC_NON_NULL(...) __attribute__((nonnull(__VA_ARGS__)))
+# else
+#  define GNUC_NON_NULL(...)
+# endif
+#else
+#  define API
+#  define GNUC_NON_NULL(...)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+    struct lms_string_size {
+        char *str;
+        unsigned int len;
+    };
+
+#define LMS_STATIC_STRING_SIZE(s)  {s, sizeof(s) - 1}
+#define LMS_ARRAY_SIZE(a)  (sizeof(a) / sizeof(*a))
+
+
+    API void lms_strstrip(char *str, unsigned int *p_len) GNUC_NON_NULL(1, 2);
+    API int lms_which_extension(const char *name, unsigned int name_len, const struct lms_string_size *exts, unsigned int exts_len) GNUC_NON_NULL(1, 3);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _LIGHTMEDIASCANNER_UTILS_H_ */
diff --git a/lightmediascanner/src/plugins/Makefile.am b/lightmediascanner/src/plugins/Makefile.am
new file mode 100644 (file)
index 0000000..282cb93
--- /dev/null
@@ -0,0 +1,40 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+SUBDIRS =
+
+if USE_MODULE_DUMMY
+SUBDIRS += dummy
+endif
+
+if USE_MODULE_JPEG
+SUBDIRS += jpeg
+endif
+
+if USE_MODULE_ID3LIB
+SUBDIRS += id3lib
+endif
+
+if USE_MODULE_VIDEO_DUMMY
+SUBDIRS += video-dummy
+endif
+
+if USE_MODULE_M3U
+SUBDIRS += m3u
+endif
+
+if USE_MODULE_PLS
+SUBDIRS += pls
+endif
+
+if USE_MODULE_OGG
+SUBDIRS += ogg
+endif
+
+DIST_SUBDIRS = \
+       dummy \
+       jpeg \
+       id3lib \
+       video-dummy \
+       m3u \
+       ogg \
+       pls
diff --git a/lightmediascanner/src/plugins/dummy/Makefile.am b/lightmediascanner/src/plugins/dummy/Makefile.am
new file mode 100644 (file)
index 0000000..7af4e97
--- /dev/null
@@ -0,0 +1,10 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_srcdir)/src/plugins/dummy
+
+pkgdir = $(pluginsdir)
+pkg_LTLIBRARIES = dummy.la
+dummy_la_SOURCES = dummy.c
+dummy_la_DEPENDENCIES = $(top_builddir)/config.h
+dummy_la_LIBADD = $(top_builddir)/src/lib/liblightmediascanner.la
+dummy_la_LDFLAGS = -module -avoid-version
diff --git a/lightmediascanner/src/plugins/dummy/dummy.c b/lightmediascanner/src/plugins/dummy/dummy.c
new file mode 100644 (file)
index 0000000..8d3f7be
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+/**
+ * @brief
+ *
+ * Dummy plugin demonstrating the basic lightmediascanner_plugin API,
+ * it just write paths to /tmp/dummy.log.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _XOPEN_SOURCE 600
+#include <lightmediascanner_plugin.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static const char _name[] = "dummy";
+
+struct plugin {
+    struct lms_plugin plugin;
+    int fd;
+};
+
+static void *
+_match(struct plugin *p, const char *path, int len, int base)
+{
+    return (void*)1;
+}
+
+static int
+_parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
+{
+    write(plugin->fd, finfo->path, finfo->path_len);
+    write(plugin->fd, "\n", 1);
+    return 0;
+}
+
+static int
+_close(struct plugin *plugin)
+{
+    free(plugin);
+    return 0;
+}
+
+static int
+_setup(struct plugin *plugin,  struct lms_context *ctxt)
+{
+    return 0;
+}
+
+static int
+_start(struct plugin *plugin, struct lms_context *ctxt)
+{
+    char logfile[PATH_MAX];
+
+    snprintf(logfile, sizeof(logfile), "/tmp/dummy-%d.log", getuid());
+    plugin->fd = open(logfile, O_WRONLY | O_CREAT, 0600);
+    if (plugin->fd < 0) {
+        perror("open");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+_finish(struct plugin *plugin, struct lms_context *ctxt)
+{
+    if (close(plugin->fd) != 0)
+        perror("close");
+
+    plugin->fd = 0;
+
+    return 0;
+}
+
+API struct lms_plugin *
+lms_plugin_open(void)
+{
+    struct plugin *plugin;
+
+    plugin = malloc(sizeof(*plugin));
+    plugin->plugin.name = _name;
+    plugin->plugin.match = (lms_plugin_match_fn_t)_match;
+    plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
+    plugin->plugin.close = (lms_plugin_close_fn_t)_close;
+    plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
+    plugin->plugin.start = (lms_plugin_start_fn_t)_start;
+    plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
+
+    return (struct lms_plugin *)plugin;
+}
diff --git a/lightmediascanner/src/plugins/id3lib/Makefile.am b/lightmediascanner/src/plugins/id3lib/Makefile.am
new file mode 100644 (file)
index 0000000..4f9dd4f
--- /dev/null
@@ -0,0 +1,10 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_srcdir)/src/plugins/id3lib @ID3LIB_CFLAGS@
+
+pkgdir = $(pluginsdir)
+pkg_LTLIBRARIES = id3lib.la
+id3lib_la_SOURCES = id3lib.cpp
+id3lib_la_DEPENDENCIES = $(top_builddir)/config.h
+id3lib_la_LIBADD = $(top_builddir)/src/lib/liblightmediascanner.la @ID3LIB_LIBS@
+id3lib_la_LDFLAGS = -module -avoid-version
diff --git a/lightmediascanner/src/plugins/id3lib/id3lib.cpp b/lightmediascanner/src/plugins/id3lib/id3lib.cpp
new file mode 100644 (file)
index 0000000..5916ff3
--- /dev/null
@@ -0,0 +1,297 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+/**
+ * @brief
+ *
+ * Reads ID3 tags from MP3 using id3lib.
+ *
+ * @todo: write a faster module to replace this one, using no external libs.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _XOPEN_SOURCE 600
+#include <lightmediascanner_plugin.h>
+#include <lightmediascanner_utils.h>
+#include <lightmediascanner_db.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <id3/tag.h>
+
+extern "C" {
+
+static void
+_id3lib_get_string(const ID3_Frame *frame, ID3_FieldID field_id, struct lms_string_size *s)
+{
+  ID3_Field* field;
+  ID3_TextEnc enc;
+  size_t size;
+
+  field = frame->GetField(field_id);
+  if (!field)
+    return;
+
+  enc = field->GetEncoding();
+  field->SetEncoding(ID3TE_ISO8859_1);
+  size = field->Size();
+  if (size < 1)
+    goto done;
+
+  size++;
+  s->str = (char *)malloc(size * sizeof(char));
+  s->len = field->Get(s->str, size);
+  if (s->len > 0)
+    lms_strstrip(s->str, &s->len);
+
+  if (s->len < 1) {
+    free(s->str);
+    s->str = NULL;
+    s->len = 0;
+  }
+
+ done:
+  field->SetEncoding(enc);
+}
+
+static unsigned int
+_id3lib_get_string_as_int(const ID3_Frame *frame, ID3_FieldID field_id)
+{
+  char buf[32];
+  ID3_Field* field;
+  size_t size;
+
+  field = frame->GetField(field_id);
+  if (!field)
+    return 0;
+
+  size = field->Get(buf, sizeof(buf));
+  if (size > 0)
+    return atoi(buf);
+
+  return 0;
+}
+
+static int
+_id3lib_get_data(const ID3_Tag &tag, struct lms_audio_info *info)
+{
+  ID3_Tag::ConstIterator *itr;
+  const ID3_Frame *frame;
+  int todo;
+
+  todo = 7; /* info fields left to parse: title, artist, album, genre,
+               trackno, rating, playcnt */
+
+  itr = tag.CreateIterator();
+
+  while ((frame = itr->GetNext()) != NULL && todo > 0) {
+    ID3_FrameID fid;
+
+    fid = frame->GetID();
+
+    switch (fid) {
+    case ID3FID_TITLE:
+      if (!info->title.str) {
+        _id3lib_get_string(frame, ID3FN_TEXT, &info->title);
+        if (info->title.str)
+          todo--;
+      }
+      break;
+
+    case ID3FID_COMPOSER:
+    case ID3FID_LEADARTIST:
+    case ID3FID_BAND:
+    case ID3FID_CONDUCTOR:
+    case ID3FID_MIXARTIST:
+      if (!info->artist.str) {
+        _id3lib_get_string(frame, ID3FN_TEXT, &info->artist);
+        if (info->artist.str)
+          todo--;
+      }
+      break;
+
+    case ID3FID_ALBUM:
+      if (!info->album.str) {
+        _id3lib_get_string(frame, ID3FN_TEXT, &info->album);
+        if (info->album.str)
+          todo--;
+      }
+      break;
+
+    case ID3FID_CONTENTTYPE:
+      if (!info->genre.str) {
+        _id3lib_get_string(frame, ID3FN_TEXT, &info->genre);
+        if (info->genre.str)
+          todo--;
+      }
+      break;
+
+    case ID3FID_TRACKNUM:
+      if (!info->trackno) {
+        info->trackno = _id3lib_get_string_as_int(frame, ID3FN_TEXT);
+        if (info->trackno)
+          todo--;
+      }
+      break;
+
+    case ID3FID_POPULARIMETER:
+      if (!info->rating) {
+        info->rating = frame->GetField(ID3FN_RATING)->Get();
+        if (info->rating)
+          todo--;
+      }
+      if (!info->playcnt) {
+        info->playcnt = frame->GetField(ID3FN_COUNTER)->Get();
+        if (info->playcnt)
+          todo--;
+      }
+      break;
+
+    default:
+      break;
+    }
+  }
+
+  delete itr;
+  return 0;
+}
+
+static const char _name[] = "id3lib";
+static const struct lms_string_size _exts[] = {
+    LMS_STATIC_STRING_SIZE(".mp3")
+};
+
+struct plugin {
+    struct lms_plugin plugin;
+    lms_db_audio_t *audio_db;
+};
+
+static void *
+_match(struct plugin *p, const char *path, int len, int base)
+{
+    int i;
+
+    i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
+    if (i < 0)
+      return NULL;
+    else
+      return (void*)(i + 1);
+}
+
+static int
+_parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
+{
+    struct lms_audio_info info = {0};
+    ID3_Tag tag;
+    int r;
+
+    tag.Link(finfo->path);
+    r = _id3lib_get_data(tag, &info);
+    if (r != 0)
+      goto done;
+
+    if (!info.title.str) {
+      int ext_idx;
+
+      ext_idx = ((int)match) - 1;
+      info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
+      info.title.str = (char *)malloc((info.title.len + 1) * sizeof(char));
+      memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
+      info.title.str[info.title.len] = '\0';
+    }
+
+    if (info.title.str)
+      lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
+    if (info.artist.str)
+      lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
+    if (info.album.str)
+      lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
+    if (info.genre.str)
+      lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
+
+    info.id = finfo->id;
+    r = lms_db_audio_add(plugin->audio_db, &info);
+
+ done:
+    if (info.title.str)
+        free(info.title.str);
+    if (info.artist.str)
+        free(info.artist.str);
+    if (info.album.str)
+        free(info.album.str);
+    if (info.genre.str)
+        free(info.genre.str);
+
+    return r;
+}
+
+static int
+_setup(struct plugin *plugin, struct lms_context *ctxt)
+{
+    plugin->audio_db = lms_db_audio_new(ctxt->db);
+    if (!plugin->audio_db)
+        return -1;
+
+    return 0;
+}
+
+static int
+_start(struct plugin *plugin, struct lms_context *ctxt)
+{
+    return lms_db_audio_start(plugin->audio_db);
+}
+
+static int
+_finish(struct plugin *plugin,  struct lms_context *ctxt)
+{
+    if (plugin->audio_db)
+        return lms_db_audio_free(plugin->audio_db);
+
+    return 0;
+}
+
+static int
+_close(struct plugin *plugin)
+{
+    free(plugin);
+    return 0;
+}
+
+API struct lms_plugin *
+lms_plugin_open(void)
+{
+    struct plugin *plugin;
+
+    plugin = (struct plugin *)malloc(sizeof(*plugin));
+    plugin->plugin.name = _name;
+    plugin->plugin.match = (lms_plugin_match_fn_t)_match;
+    plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
+    plugin->plugin.close = (lms_plugin_close_fn_t)_close;
+    plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
+    plugin->plugin.start = (lms_plugin_start_fn_t)_start;
+    plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
+
+    return (struct lms_plugin *)plugin;
+}
+
+}
diff --git a/lightmediascanner/src/plugins/jpeg/Makefile.am b/lightmediascanner/src/plugins/jpeg/Makefile.am
new file mode 100644 (file)
index 0000000..6ec341a
--- /dev/null
@@ -0,0 +1,10 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_srcdir)/src/plugins/jpeg
+
+pkgdir = $(pluginsdir)
+pkg_LTLIBRARIES = jpeg.la
+jpeg_la_SOURCES = jpeg.c
+jpeg_la_DEPENDENCIES = $(top_builddir)/config.h
+jpeg_la_LIBADD = $(top_builddir)/src/lib/liblightmediascanner.la
+jpeg_la_LDFLAGS = -module -avoid-version
diff --git a/lightmediascanner/src/plugins/jpeg/jpeg.c b/lightmediascanner/src/plugins/jpeg/jpeg.c
new file mode 100644 (file)
index 0000000..f32e397
--- /dev/null
@@ -0,0 +1,715 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+/**
+ * @brief
+ *
+ * Reads EXIF tags from images.
+ *
+ * @todo: get GPS data.
+ * @todo: check if worth using mmap().
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _XOPEN_SOURCE 600
+#include <lightmediascanner_plugin.h>
+#include <lightmediascanner_utils.h>
+#include <lightmediascanner_db.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+enum {
+    JPEG_MARKER_SOI = 0xd8,
+    JPEG_MARKER_JFIF = 0xe0,
+    JPEG_MARKER_EXIF = 0xe1,
+    JPEG_MARKER_COMM = 0xfe,
+    JPEG_MARKER_SOF0 = 0xc0,
+    JPEG_MARKER_SOS = 0xda
+};
+
+/**
+ * Process SOF JPEG, this contains width and height.
+ */
+static int
+_jpeg_sof_process(int fd, unsigned short *width, unsigned short *height)
+{
+    unsigned char buf[6];
+
+    if (read(fd, buf, 6) != 6) {
+        perror("could not read() SOF data");
+        return -1;
+    }
+
+    *height = (buf[1] << 8) | buf[2];
+    *width = (buf[3] << 8) | buf[4];
+
+    return 0;
+}
+
+/**
+ * Process COM JPEG, this contains user comment.
+ */
+static int
+_jpeg_com_process(int fd, int len, struct lms_string_size *comment)
+{
+    if (len < 1) {
+        comment->str = NULL;
+        comment->len = 0;
+        return 0;
+    }
+
+    comment->str = malloc(len + 1);
+    if (!comment->str) {
+        perror("malloc");
+        return -1;
+    }
+    if (read(fd, comment->str, len) != len) {
+        perror("read");
+        free(comment->str);
+        comment->str = NULL;
+        comment->len = 0;
+        return -2;
+    }
+    if (comment->str[len - 1] == '\0')
+        len--;
+    else
+        comment->str[len] = '\0';
+    comment->len = len;
+
+    lms_strstrip(comment->str, &comment->len);
+    if (comment->len == 0) {
+        free(comment->str);
+        comment->str = NULL;
+    }
+
+    return 0;
+}
+
+/**
+ * Walk JPEG markers in order to get useful information.
+ */
+static int
+_jpeg_info_get(int fd, int len, struct lms_image_info *info)
+{
+    unsigned char buf[4];
+    int found;
+    off_t offset;
+
+    found = info->title.str ? 1 : 0;
+    offset = lseek(fd, len - 2, SEEK_CUR);
+    len = 0;
+    while (found < 2) {
+        offset = lseek(fd, offset + len, SEEK_SET);
+        if (offset == -1) {
+            perror("lseek");
+            return -1;
+        }
+
+        if (read(fd, buf, 4) != 4) {
+            perror("read");
+            return -2;
+        }
+
+        len = ((buf[2] << 8) | buf[3]) - 2;
+
+        if (buf[0] != 0xff) {
+            fprintf(stderr, "ERROR: expected 0xff marker, got %#x\n", buf[0]);
+            return -3;
+        }
+
+        if (buf[1] == JPEG_MARKER_SOF0) {
+            if (_jpeg_sof_process(fd, &info->width, &info->height) != 0)
+                return -4;
+            found++;
+        } else if (buf[1] == JPEG_MARKER_COMM && !info->title.str) {
+            if (_jpeg_com_process(fd, len, &info->title) != 0)
+                return -5;
+            found++;
+        } else if (buf[1] == JPEG_MARKER_SOS)
+            break;
+
+        len += 4; /* add read size */
+    }
+
+    return 0;
+}
+
+/**
+ * Read JPEG file start (0xffd8 marker) and return the next
+ * marker type and its length.
+ */
+static int
+_jpeg_data_get(int fd, int *type, int *len)
+{
+    unsigned char buf[6];
+
+    if (lseek(fd, 0, SEEK_SET) != 0) {
+        perror("lseek");
+        return -1;
+    }
+
+    if (read(fd, buf, 6) != 6) {
+        perror("read");
+        return -2;
+    }
+
+    if (buf[0] != 0xff || buf[1] != JPEG_MARKER_SOI || buf[2] != 0xff) {
+        fprintf(stderr, "ERROR: not JPEG file (magic=%#x %#x %#x)\n",
+                buf[0], buf[1], buf[2]);
+        return -3;
+    }
+
+    *type = buf[3];
+    *len = (buf[4] << 8) | buf[5];
+
+    return 0;
+}
+
+#define LE_4BYTE(a) ((a)[0] | ((a)[1] << 8) | ((a)[2] << 16) | ((a)[3] << 24))
+#define BE_4BYTE(a) (((a)[0] << 24) | ((a)[1] << 16) | ((a)[2] << 8) | (a)[3])
+
+#define LE_2BYTE(a) ((a)[0] | ((a)[1] << 8))
+#define BE_2BYTE(a) (((a)[0] << 8) | (a)[1])
+
+#define E_2BTYE(little_endian, a) ((little_endian) ? LE_2BYTE(a) : BE_2BYTE(a))
+#define E_4BTYE(little_endian, a) ((little_endian) ? LE_4BYTE(a) : BE_4BYTE(a))
+
+enum {
+    EXIF_TYPE_BYTE = 1, /* 8 bit unsigned */
+    EXIF_TYPE_ASCII = 2, /* 8 bit byte with 7-bit ASCII code, NULL terminated */
+    EXIF_TYPE_SHORT = 3, /* 2-byte unsigned integer */
+    EXIF_TYPE_LONG = 4, /* 4-byte unsigned integer */
+    EXIF_TYPE_RATIONAL = 5, /* 2 4-byte unsigned integer, 1st = numerator */
+    EXIF_TYPE_UNDEFINED = 7, /* 8-bit byte */
+    EXIF_TYPE_SLONG = 9, /* 4-byte signed integer (2'complement) */
+    EXIF_TYPE_SRATIONAL = 10 /* 2 4-byte signed integer, 1st = numerator */
+};
+
+enum {
+    EXIF_TAG_ORIENTATION = 0x0112,
+    EXIF_TAG_ARTIST = 0x013b,
+    EXIF_TAG_USER_COMMENT = 0x9286,
+    EXIF_TAG_IMAGE_DESCRIPTION = 0x010e,
+    EXIF_TAG_DATE_TIME = 0x0132,
+    EXIF_TAG_DATE_TIME_ORIGINAL = 0x9003,
+    EXIF_TAG_DATE_TIME_DIGITIZED = 0x9004,
+    EXIF_TAG_EXIF_IFD_POINTER = 0x8769
+};
+
+
+struct exif_ifd {
+    unsigned short tag;
+    unsigned short type;
+    unsigned int count;
+    unsigned int offset;
+};
+
+/**
+ * Read IFD from stream.
+ */
+static int
+_exif_ifd_get(int fd, int little_endian, struct exif_ifd *ifd)
+{
+    unsigned char buf[12];
+
+    if (read(fd, buf, 12) != 12) {
+        perror("read");
+        return -1;
+    }
+
+    if (little_endian) {
+        ifd->tag = LE_2BYTE(buf);
+        ifd->type = LE_2BYTE(buf + 2);
+        ifd->count = LE_4BYTE(buf + 4);
+        ifd->offset = LE_4BYTE(buf + 8);
+    } else {
+        ifd->tag = BE_2BYTE(buf);
+        ifd->type = BE_2BYTE(buf + 2);
+        ifd->count = BE_4BYTE(buf + 4);
+        ifd->offset = BE_4BYTE(buf + 8);
+    }
+    return 0;
+}
+
+/**
+ * Get non-exif data based on Exif tag offset.
+ *
+ * This will setup the file description position and call _jpeg_info_get().
+ */
+static int
+_exif_extra_get(int fd, int abs_offset, int len, struct lms_image_info *info)
+{
+    if (lseek(fd, abs_offset, SEEK_SET) == -1) {
+        perror("lseek");
+        return -1;
+    }
+
+    if (_jpeg_info_get(fd, len, info) != 0) {
+        fprintf(stderr, "ERROR: could not get image size.\n");
+        return -2;
+    }
+    return 0;
+}
+
+static int
+_exif_text_encoding_get(int fd, unsigned int count, int offset, struct lms_string_size *s)
+{
+    if (count <= 8)
+        return -1;
+
+    count -= 8; /* XXX don't just ignore character code, handle it. */
+    offset += 8;
+
+    if (lseek(fd, offset, SEEK_SET) == -1) {
+        perror("lseek");
+        return -2;
+    }
+
+    s->str = malloc(count + 1);
+
+    if (read(fd, s->str, count) != count) {
+        perror("read");
+        free(s->str);
+        s->str = NULL;
+        s->len = 0;
+        return -3;
+    }
+    s->str[count] = '\0';
+    s->len = count;
+
+    lms_strstrip(s->str, &s->len);
+    if (s->len == 0) {
+        free(s->str);
+        s->str = NULL;
+    }
+
+    return 0;
+}
+
+static int
+_exif_text_ascii_get(int fd, unsigned int count, int offset, struct lms_string_size *s)
+{
+    if (count < 1) {
+        s->str = NULL;
+        s->len = 0;
+        return 0;
+    }
+
+    if (lseek(fd, offset, SEEK_SET) == -1) {
+        perror("lseek");
+        return -1;
+    }
+
+    s->str = malloc(count);
+
+    if (read(fd, s->str, count) != count) {
+        perror("read");
+        free(s->str);
+        s->str = NULL;
+        s->len = 0;
+        return -1;
+    }
+    s->str[count - 1] = '\0';
+    s->len = count - 1;
+
+    lms_strstrip(s->str, &s->len);
+    if (s->len == 0) {
+        free(s->str);
+        s->str = NULL;
+    }
+
+    return 0;
+}
+
+static unsigned int
+_exif_datetime_get(int fd, int offset)
+{
+    char buf[20];
+    struct tm tm = {0};
+
+    if (lseek(fd, offset, SEEK_SET) == -1) {
+        perror("lseek");
+        return 0;
+    }
+
+    if (read(fd, buf, 20) != 20) {
+        perror("read");
+        return 0;
+    }
+
+    buf[19] = '\0';
+    if (strptime(buf, "%Y:%m:%d %H:%M:%S", &tm)) {
+        return mktime(&tm);
+    }
+    return 0;
+}
+
+static int _exif_private_ifd_get(int fd, int base_offset, int offset, int little_endian, struct lms_image_info *info);
+
+/**
+ * Process IFD contents.
+ */
+static int
+_exif_ifd_process(int fd, int count, int ifd_offset, int tiff_base, int little_endian, struct lms_image_info *info)
+{
+    int i, torig, tdig, tlast;
+
+    torig = tdig = tlast = 0;
+
+    for (i = 0; i < count; i++) {
+        struct exif_ifd ifd;
+
+        lseek(fd, ifd_offset + i * 12, SEEK_SET);
+        if (_exif_ifd_get(fd, little_endian, &ifd) != 0) {
+            fprintf(stderr, "ERROR: could not read Exif IFD.\n");
+            return -8;
+        }
+
+        switch (ifd.tag) {
+        case EXIF_TAG_ORIENTATION:
+            info->orientation = ifd.offset >> 16;
+            break;
+        case EXIF_TAG_ARTIST:
+            if (!info->artist.str)
+                _exif_text_ascii_get(fd, ifd.count, tiff_base + ifd.offset,
+                                     &info->artist);
+            break;
+        case EXIF_TAG_USER_COMMENT:
+            if (!info->title.str)
+                _exif_text_encoding_get(fd, ifd.count, tiff_base + ifd.offset,
+                                        &info->title);
+            break;
+        case EXIF_TAG_IMAGE_DESCRIPTION:
+            if (!info->title.str)
+                _exif_text_ascii_get(fd, ifd.count, tiff_base + ifd.offset,
+                                     &info->title);
+            break;
+        case EXIF_TAG_DATE_TIME:
+            if (torig == 0 && info->date == 0)
+                tlast = _exif_datetime_get(fd, tiff_base + ifd.offset);
+            break;
+        case EXIF_TAG_DATE_TIME_ORIGINAL:
+            if (torig == 0 && info->date == 0)
+                torig = _exif_datetime_get(fd, tiff_base + ifd.offset);
+            break;
+        case EXIF_TAG_DATE_TIME_DIGITIZED:
+            if (torig == 0 && info->date == 0)
+                tdig = _exif_datetime_get(fd, tiff_base + ifd.offset);
+            break;
+        case EXIF_TAG_EXIF_IFD_POINTER:
+            if (ifd.count == 1 && ifd.type == EXIF_TYPE_LONG)
+                _exif_private_ifd_get(fd, ifd.offset, tiff_base,
+                                      little_endian, info);
+            break;
+        default:
+            /* ignore */
+            break;
+        }
+    }
+
+    if (info->date == 0) {
+        if (torig)
+            info->date = torig;
+        else if (tdig)
+            info->date = tdig;
+        else
+            info->date = tlast;
+    }
+
+    return 0;
+}
+
+/**
+ * Process Exif IFD (Exif Private Tag), with more specific info.
+ */
+static int
+_exif_private_ifd_get(int fd, int ifd_offset, int tiff_base, int little_endian, struct lms_image_info *info)
+{
+    char buf[2];
+    unsigned int count;
+
+    if (lseek(fd, tiff_base + ifd_offset, SEEK_SET) == -1) {
+        perror("lseek");
+        return -1;
+    }
+
+    if (read(fd, buf, 2) != 2) {
+        perror("read");
+        return -1;
+    }
+
+    count = E_2BTYE(little_endian, buf);
+    return _exif_ifd_process(fd, count, ifd_offset + 2, tiff_base,
+                             little_endian, info);
+}
+
+/**
+ * Process file as it being Exif, will extract Exif as well as other
+ * JPEG markers (comment, size).
+ */
+static int
+_exif_data_get(int fd, int len, struct lms_image_info *info)
+{
+    const unsigned char exif_hdr[6] = "Exif\0";
+    unsigned char buf[8];
+    unsigned int little_endian, offset, count;
+    off_t abs_offset, tiff_base;
+
+    abs_offset = lseek(fd, 0, SEEK_CUR);
+    if (abs_offset == -1) {
+        perror("lseek");
+        return -1;
+    }
+
+    if (read(fd, buf, 6) != 6) {
+        perror("read");
+        return -2;
+    }
+
+    memset(info, 0, sizeof(*info));
+    info->orientation = 1;
+
+    if (memcmp(buf, exif_hdr, 6) != 0)
+        return _exif_extra_get(fd, abs_offset, len, info);
+
+    if (read(fd, buf, 8) != 8) {
+        perror("read");
+        return -4;
+    }
+
+    if (buf[0] == 'I' && buf[1] == 'I') {
+        little_endian = 1;
+        offset = LE_4BYTE(buf + 4);
+    } else if (buf[0] == 'M' && buf[1] == 'M') {
+        little_endian = 0;
+        offset = BE_4BYTE(buf + 4);
+    } else {
+        fprintf(stderr, "ERROR: undefined byte sex \"%2.2s\".\n", buf);
+        return -5;
+    }
+
+    offset -= 8;
+    if (offset > 0 && lseek(fd, offset, SEEK_CUR) == -1) {
+        perror("lseek");
+        return -6;
+    }
+
+    tiff_base = abs_offset + 6; /* offsets are relative to TIFF base */
+
+    if (read(fd, buf, 2) != 2) {
+        perror("read");
+        return -7;
+    }
+    count = E_2BTYE(little_endian, buf);
+
+    _exif_ifd_process(fd, count, tiff_base + 8 + 2, tiff_base,
+                      little_endian, info);
+
+    return _exif_extra_get(fd, abs_offset, len, info);
+}
+
+/**
+ * Process file as it being JFIF
+ */
+static int
+_jfif_data_get(int fd, int len, struct lms_image_info *info)
+{
+    unsigned char buf[4];
+
+    memset(info, 0, sizeof(*info));
+    info->orientation = 1;
+
+    /* JFIF provides no useful information, try to find out Exif */
+    if (lseek(fd, len - 2, SEEK_CUR) == -1) {
+        perror("lseek");
+        return -1;
+    }
+
+    if (read(fd, buf, 4) != 4) {
+        perror("read");
+        return -2;
+    }
+
+    len = ((buf[2] << 8) | buf[3]);
+    if (buf[0] != 0xff) {
+        fprintf(stderr, "ERROR: expected 0xff marker, got %#x\n", buf[0]);
+        return -3;
+    }
+
+    if (buf[1] == JPEG_MARKER_EXIF)
+        return _exif_data_get(fd, len, info);
+    else
+        return _jpeg_info_get(fd, len, info);
+}
+
+static const char _name[] = "jpeg";
+static const struct lms_string_size _exts[] = {
+    LMS_STATIC_STRING_SIZE(".jpg"),
+    LMS_STATIC_STRING_SIZE(".jpeg"),
+    LMS_STATIC_STRING_SIZE(".jpe")
+};
+
+struct plugin {
+    struct lms_plugin plugin;
+    lms_db_image_t *img_db;
+};
+
+static void *
+_match(struct plugin *p, const char *path, int len, int base)
+{
+    int i;
+
+    i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
+    if (i < 0)
+        return NULL;
+    else
+        return (void*)(i + 1);
+}
+
+static int
+_parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
+{
+    struct lms_image_info info = {0};
+    int fd, type, len, r;
+
+    fd = open(finfo->path, O_RDONLY);
+    if (fd < 0) {
+        perror("open");
+        return -1;
+    }
+
+    if (_jpeg_data_get(fd, &type, &len) != 0) {
+        r = -2;
+        goto done;
+    }
+
+    if (type == JPEG_MARKER_EXIF) {
+        if (_exif_data_get(fd, len, &info) != 0) {
+            fprintf(stderr, "ERROR: could not get EXIF info (%s).\n",
+                    finfo->path);
+            r = -3;
+            goto done;
+        }
+    } else if (type == JPEG_MARKER_JFIF) {
+        if (_jfif_data_get(fd, len, &info) != 0) {
+            fprintf(stderr, "ERROR: could not get JPEG size (%s).\n",
+                    finfo->path);
+            r = -4;
+            goto done;
+        }
+    } else {
+        fprintf(stderr, "ERROR: unsupported JPEG marker %#x (%s)\n", type,
+                finfo->path);
+        r = -6;
+        goto done;
+    }
+
+    if (info.date == 0)
+        info.date = finfo->mtime;
+
+    if (!info.title.str) {
+      int ext_idx;
+
+      ext_idx = ((int)match) - 1;
+      info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
+      info.title.str = malloc((info.title.len + 1) * sizeof(char));
+      memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
+      info.title.str[info.title.len] = '\0';
+    }
+
+    if (info.title.str)
+      lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
+    if (info.artist.str)
+      lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
+
+    info.id = finfo->id;
+    r = lms_db_image_add(plugin->img_db, &info);
+
+  done:
+    if (info.title.str)
+        free(info.title.str);
+    if (info.artist.str)
+        free(info.artist.str);
+
+    posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+    close(fd);
+
+    return r;
+}
+
+static int
+_setup(struct plugin *plugin, struct lms_context *ctxt)
+{
+    plugin->img_db = lms_db_image_new(ctxt->db);
+    if (!plugin->img_db)
+        return -1;
+
+    return 0;
+}
+
+static int
+_start(struct plugin *plugin, struct lms_context *ctxt)
+{
+    return lms_db_image_start(plugin->img_db);
+}
+
+static int
+_finish(struct plugin *plugin, struct lms_context *ctxt)
+{
+    if (plugin->img_db)
+        return lms_db_image_free(plugin->img_db);
+
+    return 0;
+}
+
+
+static int
+_close(struct plugin *plugin)
+{
+    free(plugin);
+    return 0;
+}
+
+API struct lms_plugin *
+lms_plugin_open(void)
+{
+    struct plugin *plugin;
+
+    plugin = malloc(sizeof(*plugin));
+    plugin->plugin.name = _name;
+    plugin->plugin.match = (lms_plugin_match_fn_t)_match;
+    plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
+    plugin->plugin.close = (lms_plugin_close_fn_t)_close;
+    plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
+    plugin->plugin.start = (lms_plugin_start_fn_t)_start;
+    plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
+
+    return (struct lms_plugin *)plugin;
+}
diff --git a/lightmediascanner/src/plugins/m3u/Makefile.am b/lightmediascanner/src/plugins/m3u/Makefile.am
new file mode 100644 (file)
index 0000000..b2332cf
--- /dev/null
@@ -0,0 +1,10 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_srcdir)/src/plugins/m3u
+
+pkgdir = $(pluginsdir)
+pkg_LTLIBRARIES = m3u.la
+m3u_la_SOURCES = m3u.c
+m3u_la_DEPENDENCIES = $(top_builddir)/config.h
+m3u_la_LIBADD = $(top_builddir)/src/lib/liblightmediascanner.la
+m3u_la_LDFLAGS = -module -avoid-version
diff --git a/lightmediascanner/src/plugins/m3u/m3u.c b/lightmediascanner/src/plugins/m3u/m3u.c
new file mode 100644 (file)
index 0000000..d5c907e
--- /dev/null
@@ -0,0 +1,193 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+/**
+ * @brief
+ *
+ * m3u playlist parser.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _XOPEN_SOURCE 600
+#include <lightmediascanner_plugin.h>
+#include <lightmediascanner_db.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+static int
+_m3u_get_n_entries(int fd, struct lms_playlist_info *info)
+{
+    char buf[1024];
+    enum {
+        IS_EMPTY,
+        IS_ENTRY,
+        IS_COMMENT
+    } state;
+
+    state = IS_EMPTY;
+    do {
+        ssize_t r;
+        int i;
+
+        r = read(fd, buf, sizeof(buf));
+        if (r < 0) {
+            perror("read");
+            return -1;
+        } else if (r == 0)
+            goto done;
+
+        for (i = 0; i < r; i++) {
+            char c;
+
+            c = buf[i];
+            if (c == '\n') {
+                if (state == IS_ENTRY)
+                    info->n_entries++;
+                state = IS_EMPTY;
+            } else if (state == IS_EMPTY) {
+                if (c == '#')
+                    state = IS_COMMENT;
+                else if (!isspace(c))
+                    state = IS_ENTRY;
+            }
+        }
+    } while (1);
+
+  done:
+    if (state == IS_ENTRY)
+        info->n_entries++;
+    return 0;
+}
+
+static const char _name[] = "m3u";
+static const struct lms_string_size _exts[] = {
+    LMS_STATIC_STRING_SIZE(".m3u")
+};
+
+struct plugin {
+    struct lms_plugin plugin;
+    lms_db_playlist_t *playlist_db;
+};
+
+static void *
+_match(struct plugin *p, const char *path, int len, int base)
+{
+    int i;
+
+    i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
+    if (i < 0)
+      return NULL;
+    else
+      return (void*)(i + 1);
+}
+
+static int
+_parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
+{
+    struct lms_playlist_info info = {0};
+    int fd, r, ext_idx;
+
+    fd = open(finfo->path, O_RDONLY);
+    if (fd < 0) {
+        perror("open");
+        return -1;
+    }
+
+    if (_m3u_get_n_entries(fd, &info) != 0)
+        fprintf(stderr,
+                "WARNING: could not get number of entries in playlist '%s'.\n",
+                finfo->path);
+
+    ext_idx = ((int)match) - 1;
+    info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
+    info.title.str = malloc((info.title.len + 1) * sizeof(char));
+    memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
+    info.title.str[info.title.len] = '\0';
+    lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
+
+    info.id = finfo->id;
+    r = lms_db_playlist_add(plugin->playlist_db, &info);
+
+    if (info.title.str)
+        free(info.title.str);
+    posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+    close(fd);
+
+    return r;
+}
+
+static int
+_setup(struct plugin *plugin, struct lms_context *ctxt)
+{
+    plugin->playlist_db = lms_db_playlist_new(ctxt->db);
+    if (!plugin->playlist_db)
+        return -1;
+
+    return 0;
+}
+
+static int
+_start(struct plugin *plugin, struct lms_context *ctxt)
+{
+    return lms_db_playlist_start(plugin->playlist_db);
+}
+
+static int
+_finish(struct plugin *plugin, struct lms_context *ctxt)
+{
+    if (plugin->playlist_db)
+        return lms_db_playlist_free(plugin->playlist_db);
+
+    return 0;
+}
+
+
+static int
+_close(struct plugin *plugin)
+{
+    free(plugin);
+    return 0;
+}
+
+API struct lms_plugin *
+lms_plugin_open(void)
+{
+    struct plugin *plugin;
+
+    plugin = malloc(sizeof(*plugin));
+    plugin->plugin.name = _name;
+    plugin->plugin.match = (lms_plugin_match_fn_t)_match;
+    plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
+    plugin->plugin.close = (lms_plugin_close_fn_t)_close;
+    plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
+    plugin->plugin.start = (lms_plugin_start_fn_t)_start;
+    plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
+
+    return (struct lms_plugin *)plugin;
+}
diff --git a/lightmediascanner/src/plugins/ogg/Makefile.am b/lightmediascanner/src/plugins/ogg/Makefile.am
new file mode 100644 (file)
index 0000000..26d414d
--- /dev/null
@@ -0,0 +1,10 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_srcdir)/src/plugins/ogg @VORBIS_CFLAGS@
+
+pkgdir = $(pluginsdir)
+pkg_LTLIBRARIES = ogg.la
+ogg_la_SOURCES = ogg.c
+ogg_la_DEPENDENCIES = $(top_builddir)/config.h
+ogg_la_LIBADD = $(top_builddir)/src/lib/liblightmediascanner.la @VORBIS_LIBS@
+ogg_la_LDFLAGS = -module -avoid-version
diff --git a/lightmediascanner/src/plugins/ogg/ogg.c b/lightmediascanner/src/plugins/ogg/ogg.c
new file mode 100644 (file)
index 0000000..3425370
--- /dev/null
@@ -0,0 +1,318 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Renato Chencarek <renato.chencarek@openbossa.org>
+ */
+
+/**
+ * @brief
+ *
+ * ogg file parser.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <lightmediascanner_plugin.h>
+#include <lightmediascanner_db.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
+
+static long int
+_id3_tag_size (FILE *file)
+{
+    unsigned char tmp[4];
+    long int size;
+
+    if (fread(tmp, 1, 4, file) == 4) {
+        if (tmp[0] == 'I' && tmp[1] == 'D' &&
+            tmp[2] == '3' && tmp[3] < 0xFF) {
+            fseek(file, 2, SEEK_CUR);
+            if (fread(tmp, 1, 4, file) == 4) {
+                size = 10 +   ( (long)(tmp[3])
+                              | ((long)(tmp[2]) << 7)
+                              | ((long)(tmp[1]) << 14)
+                              | ((long)(tmp[0]) << 21) );
+
+                return size;
+            }
+        }
+    }
+    return 0L;
+}
+
+
+static int
+_get_vorbis_comment (FILE *file, vorbis_comment *vc)
+{
+    char *buffer;
+    int bytes, i, chunks = 0;
+    ogg_packet header;
+
+    ogg_page og;
+    ogg_sync_state osync;
+    ogg_stream_state os;
+    vorbis_info vi;
+
+    int serial;
+    int CHUNKSIZE = 4096;
+    int nheaders = 0;
+
+    ogg_sync_init(&osync);
+
+    while (1) {
+        buffer = ogg_sync_buffer(&osync, CHUNKSIZE);
+        bytes = fread(buffer, 1, CHUNKSIZE, file);
+
+        ogg_sync_wrote(&osync, bytes);
+
+        if (ogg_sync_pageout(&osync, &og) == 1)
+            break;
+
+        if (chunks++ >= 10)
+            return -1;
+    }
+
+    serial = ogg_page_serialno(&og);
+
+    ogg_stream_init(&os, serial);
+    vorbis_info_init(&vi);
+    vorbis_comment_init(vc);
+
+    if (ogg_stream_pagein(&os, &og) < 0)
+        return -1;
+    if (ogg_stream_packetout(&os, &header) != 1)
+        return -1;
+    if (vorbis_synthesis_headerin(&vi, vc, &header) != 0)
+        return -1;
+
+    i = 1;
+    nheaders = 3;
+    while (i < nheaders) {
+        while (i < nheaders) {
+            int result = ogg_sync_pageout(&osync, &og);
+            if (result == 0)
+                break;
+            else if (result == 1) {
+                ogg_stream_pagein(&os, &og);
+                while (i < nheaders) {
+                    result = ogg_stream_packetout(&os, &header);
+                    if (result == 0)
+                        break;
+                    if (result == -1)
+                        return -1;
+
+                    vorbis_synthesis_headerin(&vi, vc, &header);
+                    i++;
+                }
+            }
+        }
+
+        buffer = ogg_sync_buffer(&osync, CHUNKSIZE);
+        bytes = fread(buffer, 1, CHUNKSIZE, file);
+
+        if (bytes == 0 && i < 2)
+            return -1;
+
+        ogg_sync_wrote(&osync, bytes);
+    }
+
+    ogg_stream_clear(&os);
+    ogg_sync_clear(&osync);
+    vorbis_info_clear(&vi);
+
+    return 0;
+}
+
+static int
+_parse_ogg (const char *filename, struct lms_audio_info *info)
+{
+    vorbis_comment vc;
+    FILE *file;
+    char *tag = NULL;
+    int size;
+
+    if( !filename )
+        return -1;
+
+    file = fopen(filename, "rb");
+    if (file == NULL)
+        return -1;
+
+    fseek(file, _id3_tag_size(file), SEEK_SET);
+
+    if (_get_vorbis_comment(file, &vc) != 0)
+        return -1;
+
+    tag = vorbis_comment_query(&vc, "TITLE", 0);
+    if (tag && (size = strlen(tag)) > 0) {
+        info->title.len = size;
+        info->title.str = malloc(size * sizeof(char));
+        memcpy(info->title.str, tag, size);
+    }
+
+    tag = vorbis_comment_query(&vc, "ARTIST", 0);
+    if (tag && (size = strlen(tag)) > 0) {
+        info->artist.len = size;
+        info->artist.str = malloc(size * sizeof(char));
+        memcpy(info->artist.str, tag, size);
+    }
+
+    tag = vorbis_comment_query(&vc, "ALBUM", 0);
+    if (tag && (size = strlen(tag)) > 0) {
+        info->album.len = size;
+        info->album.str = malloc(size * sizeof(char));
+        memcpy(info->album.str, tag, size);
+    }
+
+    tag = vorbis_comment_query(&vc, "TRACKNUMBER", 0);
+    if (tag && (size = strlen(tag)) > 0) {
+        info->trackno = atoi(tag);
+    }
+
+    tag = vorbis_comment_query(&vc, "GENRE", 0);
+    if (tag && (size = strlen(tag)) > 0) {
+        info->genre.len = size;
+        info->genre.str = malloc(size * sizeof(char));
+        memcpy(info->genre.str, tag, size);
+    }
+
+    fclose(file);
+    vorbis_comment_clear(&vc);
+
+    return 0;
+}
+
+
+static const char _name[] = "ogg";
+static const struct lms_string_size _exts[] = {
+    LMS_STATIC_STRING_SIZE(".ogg")
+};
+
+struct plugin {
+    struct lms_plugin plugin;
+    lms_db_audio_t *audio_db;
+};
+
+static void *
+_match(struct plugin *p, const char *path, int len, int base)
+{
+    int i;
+
+    i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
+    if (i < 0)
+      return NULL;
+    else
+      return (void*)(i + 1);
+}
+
+static int
+_parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
+{
+    struct lms_audio_info info = {0};
+    int r;
+
+    r = _parse_ogg(finfo->path, &info);
+    if (r != 0)
+      goto done;
+
+    if (!info.title.str) {
+      int ext_idx;
+
+      ext_idx = ((int)match) - 1;
+      info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
+      info.title.str = (char *)malloc((info.title.len + 1) * sizeof(char));
+      memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
+      info.title.str[info.title.len] = '\0';
+    }
+
+    if (info.title.str)
+      lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
+    if (info.artist.str)
+      lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
+    if (info.album.str)
+      lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
+    if (info.genre.str)
+      lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
+
+    info.id = finfo->id;
+    r = lms_db_audio_add(plugin->audio_db, &info);
+
+ done:
+    if (info.title.str)
+        free(info.title.str);
+    if (info.artist.str)
+        free(info.artist.str);
+    if (info.album.str)
+        free(info.album.str);
+    if (info.genre.str)
+        free(info.genre.str);
+
+    return r;
+}
+
+static int
+_setup(struct plugin *plugin, struct lms_context *ctxt)
+{
+    plugin->audio_db = lms_db_audio_new(ctxt->db);
+    if (!plugin->audio_db)
+        return -1;
+
+    return 0;
+}
+
+static int
+_start(struct plugin *plugin, struct lms_context *ctxt)
+{
+    return lms_db_audio_start(plugin->audio_db);
+}
+
+static int
+_finish(struct plugin *plugin, struct lms_context *ctxt)
+{
+    if (plugin->audio_db)
+        return lms_db_audio_free(plugin->audio_db);
+
+    return 0;
+}
+
+static int
+_close(struct plugin *plugin)
+{
+    free(plugin);
+    return 0;
+}
+
+API struct lms_plugin *
+lms_plugin_open(void)
+{
+    struct plugin *plugin;
+
+    plugin = (struct plugin *)malloc(sizeof(*plugin));
+    plugin->plugin.name = _name;
+    plugin->plugin.match = (lms_plugin_match_fn_t)_match;
+    plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
+    plugin->plugin.close = (lms_plugin_close_fn_t)_close;
+    plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
+    plugin->plugin.start = (lms_plugin_start_fn_t)_start;
+    plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
+
+    return (struct lms_plugin *)plugin;
+}
diff --git a/lightmediascanner/src/plugins/pls/Makefile.am b/lightmediascanner/src/plugins/pls/Makefile.am
new file mode 100644 (file)
index 0000000..f85b557
--- /dev/null
@@ -0,0 +1,10 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_srcdir)/src/plugins/pls
+
+pkgdir = $(pluginsdir)
+pkg_LTLIBRARIES = pls.la
+pls_la_SOURCES = pls.c
+pls_la_DEPENDENCIES = $(top_builddir)/config.h
+pls_la_LIBADD = $(top_builddir)/src/lib/liblightmediascanner.la
+pls_la_LDFLAGS = -module -avoid-version
diff --git a/lightmediascanner/src/plugins/pls/pls.c b/lightmediascanner/src/plugins/pls/pls.c
new file mode 100644 (file)
index 0000000..08533ab
--- /dev/null
@@ -0,0 +1,375 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+/**
+ * @brief
+ *
+ * pls playlist parser.
+ *
+ * This parser doesn't actually parse the whole file, instead it just checks
+ * for the header [playlist], then search the beginning and ending of the file
+ * (at most PLS_MAX_N_ENTRIES_BYTES_LOOKUP bytes) in order to find out
+ * NumberOfEntries=XXX line. If there are too many bogus (ie: empty) lines or
+ * this line is inside the data declaration, then it will fail the parse.
+ * In theory this should not happen, so let's wait for bug reports.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _XOPEN_SOURCE 600
+#include <lightmediascanner_plugin.h>
+#include <lightmediascanner_db.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#define PLS_MAX_N_ENTRIES_BYTES_LOOKUP 64
+
+static int
+_pls_find_header(int fd)
+{
+    const char header[] = "[playlist]";
+    char buf[sizeof(header) - 1];
+    ssize_t r;
+
+    /* skip out white spaces */
+    do {
+        r = read(fd, buf, 1);
+        if (r < 0) {
+            perror("read");
+            return -1;
+        } else if (r == 0) {
+            fprintf(stderr, "ERROR: premature end of file.\n");
+            return -2;
+        }
+
+        if (!isspace(buf[0]))
+            break;
+    } while (1);
+
+    if (buf[0] != header[0])
+        return -3;
+
+    /* try to read rest (from the second on) of the header */
+    r = read(fd, buf + 1, sizeof(buf) - 1);
+    if (r < 0) {
+        perror("read");
+        return -4;
+    } else if (r != sizeof(buf) - 1) {
+        fprintf(stderr, "ERROR: premature end of file: read %d of %d bytes.\n",
+                r, sizeof(buf) - 1);
+        return -5;
+    }
+
+    if (memcmp(buf + 1, header + 1, sizeof(buf) - 1) != 0) {
+        fprintf(stderr, "ERROR: invalid pls header '%.*s'\n",
+                sizeof(buf) - 1, buf);
+        return -6;
+    }
+
+    /* find '\n' */
+    do {
+        r = read(fd, buf, 1);
+        if (r < 0) {
+            perror("read");
+            return -7;
+        } else if (r == 0)
+            return -8;
+
+        if (buf[0] == '\n')
+            return 0;
+    } while (1);
+
+    return -1;
+}
+
+static int
+_pls_find_n_entries_start(int fd, struct lms_playlist_info *info)
+{
+    char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
+    const char n_entries[] = "NumberOfEntries=";
+    ssize_t r;
+    int i;
+    off_t off;
+
+    off = lseek(fd, 0, SEEK_CUR);
+    if (off < 0) {
+        perror("lseek");
+        return -1;
+    }
+
+    r = read(fd, buf, sizeof(buf));
+    if (r < 0) {
+        perror("read");
+        return -2;
+    } else if (r == 0)
+        return -3;
+
+    for (i = 0; i < r; i++) {
+        char c;
+
+        c = buf[i];
+        if (c == n_entries[0]) {
+            const char *p;
+            i++;
+            if (memcmp(buf + i, n_entries + 1, sizeof(n_entries) - 2) != 0) {
+                off += i + sizeof(n_entries) - 2;
+                goto done;
+            }
+
+            i += sizeof(n_entries) - 2;
+            p = buf + i;
+            for (; i < r; i++)
+                if (buf[i] == '\n')
+                    break;
+
+            if (i == r) {
+                fprintf(stderr, "WARNING: missing end of line\n");
+                i = r - 1;
+            }
+            buf[i] = '\0';
+            info->n_entries = atoi(p);
+            return 0;
+        } else if (c == 'V') {
+            /* skip possible 'Version=XX' */
+            for (i++; i < r; i++)
+                if (buf[i] == '\n')
+                    break;
+        } else if (isspace(c))
+            continue;
+        else {
+            off += i;
+            goto done;
+        }
+    }
+
+  done:
+    /* not at the file beginning, reset offset */
+    if (lseek(fd, off, SEEK_SET) < 0) {
+        perror("lseek");
+        return -1;
+    }
+
+    return 1;
+}
+
+static int
+_pls_parse_entries_line(int fd, struct lms_playlist_info *info, char *buf, int len)
+{
+    const char n_entries[] = "NumberOfEntries=";
+    int i;
+
+    for (i = 0; i < len; i++, buf++)
+        if (!isspace(*buf))
+            break;
+
+    if (i == len)
+        return 1;
+    len -= i;
+
+    if (memcmp(buf, n_entries, sizeof(n_entries) - 1) != 0)
+        return 1;
+
+    buf += sizeof(n_entries) - 1;
+    len -= sizeof(n_entries) - 1;
+    buf[len] = '\0';
+
+    info->n_entries = atoi(buf);
+    return 0;
+}
+
+static int
+_pls_find_n_entries_end(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
+{
+    char buf[PLS_MAX_N_ENTRIES_BYTES_LOOKUP];
+    ssize_t r;
+    int i, last_nl;
+
+    if (finfo->size > sizeof(buf))
+        if (lseek(fd, finfo->size - sizeof(buf), SEEK_SET) < 0) {
+            perror("lseek");
+            return -1;
+        }
+
+    r = read(fd, buf, sizeof(buf));
+    if (r < 0) {
+        perror("read");
+        return -1;
+    } else if (r == 0)
+        return -2;
+
+    last_nl = -1;
+    for (i = r - 1; i >= 0; i--) {
+        if (buf[i] == '\n') {
+            if (last_nl >= 0) {
+                int len;
+
+                len = last_nl - i - 1;
+                if (len > 0) {
+                    int ret;
+
+                    ret = _pls_parse_entries_line(fd, info, buf + i + 1, len);
+                    if (ret <= 0)
+                        return ret;
+                }
+            }
+            last_nl = i;
+        }
+    }
+
+    return 1;
+}
+
+static int
+_pls_parse(int fd, const struct lms_file_info *finfo, struct lms_playlist_info *info)
+{
+    int r;
+
+    r = _pls_find_header(fd);
+    if (r != 0) {
+        fprintf(stderr, "ERROR: could not find pls header. code=%d\n", r);
+        return -1;
+    }
+
+    r = _pls_find_n_entries_start(fd, info);
+    if (r <= 0)
+        return r;
+
+    r = _pls_find_n_entries_end(fd, finfo, info);
+    if (r != 0)
+        fprintf(stderr, "ERROR: could not find pls NumberOfEntries=\n");
+
+    return r;
+}
+
+static const char _name[] = "pls";
+static const struct lms_string_size _exts[] = {
+    LMS_STATIC_STRING_SIZE(".pls")
+};
+
+struct plugin {
+    struct lms_plugin plugin;
+    lms_db_playlist_t *playlist_db;
+};
+
+static void *
+_match(struct plugin *p, const char *path, int len, int base)
+{
+    int i;
+
+    i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
+    if (i < 0)
+      return NULL;
+    else
+      return (void*)(i + 1);
+}
+
+static int
+_parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
+{
+    struct lms_playlist_info info = {0};
+    int fd, r, ext_idx;
+
+    fd = open(finfo->path, O_RDONLY);
+    if (fd < 0) {
+        perror("open");
+        return -1;
+    }
+
+    if (_pls_parse(fd, finfo, &info) != 0) {
+        fprintf(stderr,
+                "WARNING: could not parse playlist '%s'.\n", finfo->path);
+        return -1;
+    }
+
+    ext_idx = ((int)match) - 1;
+    info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
+    info.title.str = malloc((info.title.len + 1) * sizeof(char));
+    memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
+    info.title.str[info.title.len] = '\0';
+    lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
+
+    info.id = finfo->id;
+    r = lms_db_playlist_add(plugin->playlist_db, &info);
+
+    if (info.title.str)
+        free(info.title.str);
+    posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+    close(fd);
+
+    return r;
+}
+
+static int
+_setup(struct plugin *plugin, struct lms_context *ctxt)
+{
+    plugin->playlist_db = lms_db_playlist_new(ctxt->db);
+    if (!plugin->playlist_db)
+        return -1;
+
+    return 0;
+}
+
+static int
+_start(struct plugin *plugin, struct lms_context *ctxt)
+{
+    return lms_db_playlist_start(plugin->playlist_db);
+}
+
+static int
+_finish(struct plugin *plugin, struct lms_context *ctxt)
+{
+    if (plugin->playlist_db)
+        return lms_db_playlist_free(plugin->playlist_db);
+
+    return 0;
+}
+
+
+static int
+_close(struct plugin *plugin)
+{
+    free(plugin);
+    return 0;
+}
+
+API struct lms_plugin *
+lms_plugin_open(void)
+{
+    struct plugin *plugin;
+
+    plugin = malloc(sizeof(*plugin));
+    plugin->plugin.name = _name;
+    plugin->plugin.match = (lms_plugin_match_fn_t)_match;
+    plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
+    plugin->plugin.close = (lms_plugin_close_fn_t)_close;
+    plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
+    plugin->plugin.start = (lms_plugin_start_fn_t)_start;
+    plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
+
+    return (struct lms_plugin *)plugin;
+}
diff --git a/lightmediascanner/src/plugins/video-dummy/Makefile.am b/lightmediascanner/src/plugins/video-dummy/Makefile.am
new file mode 100644 (file)
index 0000000..f40813c
--- /dev/null
@@ -0,0 +1,10 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_srcdir)/src/plugins/video-dummy
+
+pkgdir = $(pluginsdir)
+pkg_LTLIBRARIES = video-dummy.la
+video_dummy_la_SOURCES = video-dummy.c
+video_dummy_la_DEPENDENCIES = $(top_builddir)/config.h
+video_dummy_la_LIBADD = $(top_builddir)/src/lib/liblightmediascanner.la
+video_dummy_la_LDFLAGS = -module -avoid-version
diff --git a/lightmediascanner/src/plugins/video-dummy/video-dummy.c b/lightmediascanner/src/plugins/video-dummy/video-dummy.c
new file mode 100644 (file)
index 0000000..af292c4
--- /dev/null
@@ -0,0 +1,137 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program 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
+ * 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 Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+/**
+ * @brief
+ *
+ * Video Dummy plugin, just register matched extensions in videos DB.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _XOPEN_SOURCE 600
+#include <lightmediascanner_plugin.h>
+#include <lightmediascanner_db.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+static const char _name[] = "video-dummy";
+static const struct lms_string_size _exts[] = {
+    LMS_STATIC_STRING_SIZE(".avi"),
+    LMS_STATIC_STRING_SIZE(".mpg"),
+    LMS_STATIC_STRING_SIZE(".mpeg"),
+    LMS_STATIC_STRING_SIZE(".rm"),
+    LMS_STATIC_STRING_SIZE(".3gp"),
+    LMS_STATIC_STRING_SIZE(".rm"),
+    LMS_STATIC_STRING_SIZE(".ram"),
+    LMS_STATIC_STRING_SIZE(".mp4"),
+    LMS_STATIC_STRING_SIZE(".ogm")
+};
+
+struct plugin {
+    struct lms_plugin plugin;
+    lms_db_video_t *video_db;
+};
+
+static void *
+_match(struct plugin *p, const char *path, int len, int base)
+{
+    int i;
+
+    i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
+    if (i < 0)
+        return NULL;
+    else
+        return (void*)(i + 1);
+}
+
+static int
+_parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
+{
+    struct lms_video_info info = {0};
+    int r, ext_idx;
+
+    ext_idx = ((int)match) - 1;
+    info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
+    info.title.str = malloc((info.title.len + 1) * sizeof(char));
+    memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
+    info.title.str[info.title.len] = '\0';
+    lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
+
+    info.id = finfo->id;
+    r = lms_db_video_add(plugin->video_db, &info);
+
+    free(info.title.str);
+
+    return r;
+}
+
+static int
+_setup(struct plugin *plugin, struct lms_context *ctxt)
+{
+    plugin->video_db = lms_db_video_new(ctxt->db);
+    if (!plugin->video_db)
+        return -1;
+
+    return 0;
+}
+
+static int
+_start(struct plugin *plugin, struct lms_context *ctxt)
+{
+    return lms_db_video_start(plugin->video_db);
+}
+
+static int
+_finish(struct plugin *plugin, struct lms_context *ctxt)
+{
+    if (plugin->video_db)
+        return lms_db_video_free(plugin->video_db);
+
+    return 0;
+}
+
+
+static int
+_close(struct plugin *plugin)
+{
+    free(plugin);
+    return 0;
+}
+
+API struct lms_plugin *
+lms_plugin_open(void)
+{
+    struct plugin *plugin;
+
+    plugin = malloc(sizeof(*plugin));
+    plugin->plugin.name = _name;
+    plugin->plugin.match = (lms_plugin_match_fn_t)_match;
+    plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
+    plugin->plugin.close = (lms_plugin_close_fn_t)_close;
+    plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
+    plugin->plugin.start = (lms_plugin_start_fn_t)_start;
+    plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
+
+    return (struct lms_plugin *)plugin;
+}