--- /dev/null
+*~
+*.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
--- /dev/null
+Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
--- /dev/null
+ 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!
+
+
--- /dev/null
+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.
+
--- /dev/null
+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
--- /dev/null
+ 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.
--- /dev/null
+#!/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
--- /dev/null
+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"
--- /dev/null
+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}
--- /dev/null
+MAINTAINERCLEANFILES = Makefile.in
+EXTRA_DIST = \
+ as-expand.m4 \
+ ac-plugins.m4
--- /dev/null
+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)
+# ----------------------------------------------------------------------
+])
--- /dev/null
+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
+])
--- /dev/null
+MAINTAINERCLEANFILES = Makefile.in
+
+SUBDIRS = lib bin plugins
--- /dev/null
+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
--- /dev/null
+#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;
+}
--- /dev/null
+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@
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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_ */
--- /dev/null
+#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;
+}
--- /dev/null
+/**
+ * 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_ */
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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_ */
--- /dev/null
+#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);
+}
--- /dev/null
+#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;
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#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);
+}
--- /dev/null
+/**
+ * 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_ */
--- /dev/null
+#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);
+}
--- /dev/null
+/**
+ * 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_ */
--- /dev/null
+/**
+ * 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_ */
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+#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;
+}
--- /dev/null
+/**
+ * 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_ */
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+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
--- /dev/null
+/**
+ * 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;
+}
+
+}
--- /dev/null
+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
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+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
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+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
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+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
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+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
--- /dev/null
+/**
+ * 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;
+}