File liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d.obscpio of Package liblastfm-qt6

07070100000000000081A400000000000000000000000167B98EA800000075000000000000000000000000000000000000003E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/.gitattributes/admin/dist.sh	export-ignore
/admin/cleansrc.sh	export-ignore
/.gitignore	export-ignore
/.gitattributes	export-ignore07070100000001000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000003700000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/.github07070100000002000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000004100000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/.github/workflows07070100000003000081A400000000000000000000000167B98EA800000725000000000000000000000000000000000000004E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/.github/workflows/ci_build.ymlname: CI

on:
  push:
    branches: [ dev ]
  pull_request:
    branches: [ dev ]

env:
  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
  BUILD_TYPE: Release

jobs:    
  build:
    # The CMake configure and build commands are platform agnostic and should work equally
    # well on Windows or Mac.  You can convert this to a matrix build if you need
    # cross-platform coverage.
    # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
    runs-on: ubuntu-latest

    strategy:
      matrix:
        qt-version: [5.15.*, 6.6.*]

    steps:
    - name: Install Qt
      uses: jurplel/install-qt-action@v4
      with:
        version: ${{ matrix.qt-version }}

    - name: install_deps
      run: sudo apt-get install cmake libsamplerate0-dev libfftw3-dev

    - uses: actions/checkout@v4

    - name: Configure CMake
      # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
      # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
      run: |
        PARAM=OFF
        if [[ "${{ matrix.qt-version }}" == "5.15.*" ]]; then
          PARAM=ON
        fi
        cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_WITH_QT5=$PARAM

    - name: Build
      # Build your program with the given configuration
      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}

    - name: Test
      working-directory: ${{github.workspace}}/build
      # Execute tests defined by the CMake configuration.  
      # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
      run: ctest -C ${{env.BUILD_TYPE}}
07070100000004000081A400000000000000000000000167B98EA8000009B7000000000000000000000000000000000000005500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/.github/workflows/codeql-analysis.yml# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
  push:
    branches: [ dev ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ dev ]

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'cpp' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Learn more about CodeQL language support at https://git.io/codeql-language-support

    steps:
    - name: Install Qt
      uses: jurplel/install-qt-action@v4
      with:
        version: 6.6.*

    - name: install_deps
      run: sudo apt-get install cmake libsamplerate0-dev libfftw3-dev

    - name: Checkout repository
      uses: actions/checkout@v4

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v2
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.
        # queries: ./path/to/local/query, your-org/your-repo/queries@main

    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v2

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 https://git.io/JvXDl

    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
    #    and modify them (or add more) to build your code if your project
    #    uses a compiled language

    #- run: |
    #   make bootstrap
    #   make release

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v2
07070100000005000081A400000000000000000000000167B98EA800000054000000000000000000000000000000000000003A00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/.gitignoreMakefile*
_*
*~
src/lastfm_resource.rc
src/object_script.lastfm*
/.qmake.env
build/*07070100000006000081A400000000000000000000000167B98EA800000A8A000000000000000000000000000000000000003E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/CMakeLists.txtcmake_minimum_required(VERSION 3.6)
project(liblastfm)

# general settings
set(LASTFM_SOVERSION 1)
set(LASTFM_VERSION "0x00010200")

set(LASTFM_MAJOR_VERSION "1")
set(LASTFM_MINOR_VERSION "2")
set(LASTFM_PATCH_VERSION "0")
set(LASTFM_VERSION_STRING "${LASTFM_MAJOR_VERSION}.${LASTFM_MINOR_VERSION}.${LASTFM_PATCH_VERSION}")

# options
option(BUILD_FINGERPRINT "Build the lastfm-fingerprint library" ON)
option(BUILD_DEMOS "Build the lastfm example programs" OFF)
option(BUILD_TESTS "Build liblastfm tests" ON)


# installation dirs
include(GNUInstallDirs)
include(FeatureSummary)
include(FindPkgConfig)

# setup qt stuff
set(CMAKE_AUTOMOC ON)

option(BUILD_WITH_QT5 "Build liblastfm with Qt5" OFF)

if(NOT BUILD_WITH_QT5)
    find_package(Qt6 CONFIG REQUIRED COMPONENTS Core Network Xml)

    if(BUILD_FINGERPRINT)
        find_package(Qt6Sql CONFIG REQUIRED)
    endif()

    if(BUILD_TESTS)
        find_package(Qt6Test CONFIG REQUIRED)
    endif()

    set(LASTFM_LIB_VERSION_SUFFIX 6)

    if(UNIX AND NOT APPLE)
        find_package(Qt6DBus CONFIG REQUIRED)
    endif()
else()
    find_package(Qt5 CONFIG REQUIRED COMPONENTS Core Network Xml)

    if(BUILD_FINGERPRINT)
        find_package(Qt5Sql CONFIG REQUIRED)
    endif()

    if(BUILD_TESTS)
        find_package(Qt5Test CONFIG REQUIRED)
    endif()

    set(LASTFM_LIB_VERSION_SUFFIX 5)

    if(UNIX AND NOT APPLE)
        find_package(Qt5DBus CONFIG REQUIRED)
    endif()
endif()

if(BUILD_FINGERPRINT)
    pkg_check_modules(LibSamplerate REQUIRED IMPORTED_TARGET samplerate)
    add_feature_info(LibSamplerate LibSamplerate_FOUND "Required for lastfm-fingerprint library")

    pkg_check_modules(FFTW3F REQUIRED IMPORTED_TARGET fftw3f>=3.0)
    add_feature_info(FFTW3F FFTW3F_FOUND "Required for lastfm-fingerprint library")
endif()

if(CMAKE_COMPILER_IS_GNUCXX)
    add_definitions("-fno-operator-names -fvisibility-inlines-hidden -fvisibility=hidden")
endif()
if(UNIX AND NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-undefined")
endif()

if(MSVC)
	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:wchar_t-")
endif(MSVC)

set(LASTFM_LIB_TARGET_NAME lastfm${LASTFM_LIB_VERSION_SUFFIX} CACHE
    INTERNAL "Target name of liblastfm" FORCE)

set(FINGERPRINT_LIB_TARGET_NAME lastfm_fingerprint${LASTFM_LIB_VERSION_SUFFIX} CACHE
    INTERNAL "Target name of liblastfm_fingerprint" FORCE)

# main library
add_subdirectory(src)

# lastfm_fingerprint library
if(BUILD_FINGERPRINT)
    add_subdirectory(src/fingerprint)
endif()

# demos
if(BUILD_DEMOS)
    add_subdirectory(demos)
endif()

# tests
if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
07070100000007000081A400000000000000000000000167B98EA800007ED8000000000000000000000000000000000000003700000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/COPYING                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU 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
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS
07070100000008000081A400000000000000000000000167B98EA800000B77000000000000000000000000000000000000003900000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/README.md# liblastfm

liblastfm is a collection of libraries to help you integrate Last.fm services
into your rich desktop software. It is officially supported software developed
by Last.fm staff.

Michael Coffey http://twitter.com/eartle

Fork it: http://github.com/lastfm/liblastfm

Join us for chats on IRC!

Server: irc.last.fm
Channel: #last.desktop

# Dependencies

liblastfm requires:

* Qt 5.0 https://www.qt.io/
* FFTW http://www.fftw.org/
* libsamplerate http://www.mega-nerd.com/SRC/

Additionally, to build it you will need:

* cmake

## Mac OS X

We recommend that you use Homebrew to install dependancies https://brew.sh/

In order to install Qt 5 run:
```
brew install qt
```

For other dependencies:
```
brew install cmake
brew install fftw
brew install libsamplerate
```

## Linux/*NIX

On Debian-based systems, do something like this:

    sudo apt-get install qt5-qmake pkg-config g++ qtbase5-dev cmake libfftw-dev libsamplerate0-dev

Please note, we have only tested on Linux, but we think it'll work on all
varieties of UNIX. If it doesn't, report the bug to eartle on GitHub.

## Windows

Install Visual Studio 2008 or higher. Install Qt. Install the
Windows Server 2003 Platform SDK r2:

http://www.microsoft.com/Downloads/details.aspx?FamilyID=484269e2-3b89-47e3-8eb7-1f2be6d7123a

Set up your environment variables so all include paths and tools are
available.

Open a plain Windows shell, and see the next section.

# Installing liblastfm

    mkdir _build && cd _build
    cmake ..
    make -j4
    sudo make install

# Using liblastfm

We have copied the API at http://last.fm/api onto C++, so like you find
artist.getInfo there you will find an lastfm::Artist::getInfo function in our
C++ API. lastfm is a namespace, Artist a class and getInfo a function.

Thus the API is quite easy to learn. We suggest installing and checking the
include/lastfm/* directory to find out all capabilities.

The demos directory shows some further basic usage including Audioscrobbling
and getting metadata for music via our fingerprinting technology.

You need an API key from http://last.fm/api to use the webservice API.

Your link line needs to include the following:

    -llastfm -lQtCore -lQtNetwork -lQtXml

## Radio

Please set an identifiable UserAgent on your HTTP requests for the actual MP3s,
in extreme cases we'll contact you directly and demand you do so :P

## HTTP & Networking

You can specify your own QNetworkAccessManager derived class for liblastfm to
use with lastfm::setNetworkAccessManager(). Our default is pretty good
though, auto-determining proxy settings on Windows and OS X for instance.

# Development

## Public Headers

1. Header guards should be prefixed with LASTFM, eg. LASTFM_WS_REPLY_H
2. #includes should be to the system path eg. `#include <lastfm/Scrobbler.h>`
3. Don't make a header public unless it is absolutely required!
4. All headers under src/ (not subfolders) are public by default
07070100000009000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000003500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/demos0707010000000A000081A400000000000000000000000167B98EA800000AD2000000000000000000000000000000000000003F00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/demos/demo1.cpp/*
   This software is in the public domain, furnished "as is", without technical 
   support, and with no warranty, express or implied, as to its usefulness for 
   any purpose.
*/


#include "Artist.h"
#include "ws.h"

#include <QtCore>
#include <QtGui>
#include <QPointer>
#include <QNetworkReply>

#include <stdexcept>

class ArtistList : public QListWidget
{
    Q_OBJECT
    
    QPointer<QNetworkReply> reply;
    QString artist;
    
public:
    ArtistList()
    {
        connect( this,
                 SIGNAL(itemActivated( QListWidgetItem* )),
                 SLOT(onItemActivated( QListWidgetItem* )) );
    }
    
    void getSimilar( const QString& artist )
    {
        this->artist = artist;
        setWindowTitle( "Loading " + artist + "..." );
        
        // deleting a reply cancels the request and disconnects all signals
        delete reply;
        reply = lastfm::Artist( artist ).getSimilar();
        connect( reply, SIGNAL(finished()), SLOT(onGotSimilar()) );
    }
    
private Q_SLOTS:
    void onGotSimilar()
    {
        QNetworkReply* r = static_cast<QNetworkReply*>(sender());
        // always enclose retrieval functions in a try block, as they will
        // throw if they can't parse the data
        try
        {
            // you decode the response using the equivalent static function
            QMap<int, QString> artists = lastfm::Artist::getSimilar( r );

            clear();

            // we iterate backwards because best match is last because the map
            // sorts itself by key
            QStringListIterator i( artists.values() );
            i.toBack();
            while (i.hasPrevious())
                addItem( i.previous() );
                
            setWindowTitle( artist );
        }
        catch (std::runtime_error& e)
        {
            // if getSimilar() failed to parse the QNetworkReply, then e will 
            // be of type lastfm::ws::ParseError, which derives 
            // std::runtime_error
            qWarning() << e.what();
        }
    }
    
    void onItemActivated( QListWidgetItem* item )
    {
        getSimilar( item->text() );
    }
};


int main( int argc, char** argv )
{
    QApplication app( argc, argv );
    app.setApplicationName( "liblastfm" ); // used to generate UserAgent
    
    // all you need for non-authenticated webservices is your API key
    // this one is a public one, it can only do artist.getSimilar calls, so
    // I suggest you don't use it :P
    lastfm::ws::ApiKey = "b25b959554ed76058ac220b7b2e0a026";

    ArtistList artists;
    artists.getSimilar( "nirvana" );
    artists.resize( 300, 400 ); // Qt picks truly asanine default sizes for its widgets
    artists.show();

    return app.exec();
}


#include "demo1.moc"
0707010000000B000081A400000000000000000000000167B98EA800001141000000000000000000000000000000000000003F00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/demos/demo2.cpp/*
   This software is in the public domain, furnished "as is", without technical 
   support, and with no warranty, express or implied, as to its usefulness for 
   any purpose.
*/

#include "ws.h"
#include "misc.h"
#include "XmlQuery.h"
#include "Artist.h"

#include <QtCore>


struct MyCoreApp : QCoreApplication
{
    Q_OBJECT
    
public:
    MyCoreApp( int& argc, char**& argv ) : QCoreApplication( argc, argv )
    {}
    
private Q_SLOTS:
    void onWsError( lastfm::ws::Error e )
    {
        // QNetworkReply will invoke this slot on application level errors
        // mostly this is only stuff like Ws::InvalidSessionKey and
        // Ws::InvalidApiKey    
        qWarning() << e;
    }
};


int main( int argc, char** argv )
{
    MyCoreApp app( argc, argv );
    // this is used to generate the UserAgent for webservice requests
    // please set it to something sensible in your application
    app.setApplicationName( "liblastfm" );
    
////// you'll need to fill these in for this demo to work
#warning credentials need to be filled in
    lastfm::ws::Username = "";
    lastfm::ws::ApiKey = "";
    lastfm::ws::SharedSecret = "";
    QString password = "";

////// Usually you never have to construct an Last.fm WS API call manually
    // eg. Track.getTopTags() just returns a QNetworkReply* but authentication is
    // different.
    // We're using getMobileSession here as we're a console app, but you 
    // should use getToken if you can as the user will be presented with a
    // route that feels my trustworthy to them than entering their password
    // into some random app they just downloaded... ;)
    QMap<QString, QString> params;
    params["method"] = "auth.getMobileSession";
    params["username"] = lastfm::ws::Username;
    params["authToken"] = lastfm::md5( (lastfm::ws::Username + lastfm::md5( password.toUtf8() )).toUtf8() );
    QNetworkReply* reply = lastfm::ws::post( params );
    
    // never do this when an event loop is running it's a real HACK
    QEventLoop loop;
    loop.connect( reply, SIGNAL(finished()), SLOT(quit()) );
    loop.exec();
    
    try
    {
    ////// Usually there is a convenience function to decode the output from
        // ws calls too, but again, authentication is different. We think you
        // need to handle it yourselves :P Also conveniently it means you
        // can learn more about what our webservices return, eg. this service
        // will return an XML document like this:
        //
        // <lfm status="ok">
        //   <session>
        //     <name>mxcl</name>
        //      <key>d580d57f32848f5dcf574d1ce18d78b2</key>
        //      <subscriber>1</subscriber>
        //   </session>
        // </lfm>
        //
        // If status is not "ok" then this function throws
        lastfm::XmlQuery const lfm;
#warning this code needs to be ported to new api, executable is most likely broken
        //= lastfm::ws::parse( reply );

        // replace username; because eg. perhaps the user typed their
        // username with the wrong case
        lastfm::ws::Username = lfm["session"]["name"].text();
        
        // we now have a session key, you should save this, forever! Really.
        // DO NOT AUTHENTICATE EVERY TIME THE APP STARTS! You only have to do
        // this once. Or again if the user deletes your key on the site. If 
        // that happens you'll get notification to your onWsError() function,
        // see above.
        lastfm::ws::SessionKey = lfm["session"]["key"].text();
        
        qDebug() << "sk:" << lastfm::ws::SessionKey;
        
    ////// because the SessionKey is now set, the AuthenticatedUser class will
        // work. And we can call authenticated calls
        QNetworkReply* reply;
#warning this code needs to be ported to new api, executable is most likely broken
        //= lastfm::AuthenticatedUser().getRecommendedArtists();
        // again, you shouldn't do this.. ;)
        QEventLoop loop;
        loop.connect( reply, SIGNAL(finished()), SLOT(quit()) );
        loop.exec();
        
        // yay, a list rec'd artists to stderr :)
        qDebug() << lastfm::Artist::list( reply );
    }
    catch (std::runtime_error& e)
    {
        // lastfm::ws::parse() can throw lastfm::ws::ParseError, this 
        // exception derives std::runtime_error
        qWarning() << e.what();
        return 1;
    }
}


#include "demo2.moc"
0707010000000C000081A400000000000000000000000167B98EA8000006E4000000000000000000000000000000000000003F00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/demos/demo3.cpp/*
   This software is in the public domain, furnished "as is", without technical 
   support, and with no warranty, express or implied, as to its usefulness for 
   any purpose.
*/

#include "Audioscrobbler.h"
#include "ws.h"
#include "Track.h"


#include <QtCore>
#include <QtNetwork>


struct MyCoreApp : QCoreApplication
{
    Q_OBJECT

public:
    MyCoreApp( int& argc, char** argv ) : QCoreApplication( argc, argv )
    {}

public Q_SLOTS:
    void onStatus( int status )
    {
        qDebug() << lastfm::Audioscrobbler::Status(status);
    }
};


int main( int argc, char** argv )
{
    // all 6 of these lines are REQUIRED in order to scrobble
    // this demo requires you to fill in the blanks as well...
    lastfm::ws::Username = "";
    lastfm::ws::ApiKey = "";
    lastfm::ws::SharedSecret = "";
    lastfm::ws::SessionKey = ""; // you need to auth to get this... try demo2
    QCoreApplication::setApplicationName( "liblastfm" );

    MyCoreApp app( argc, argv );
    
    lastfm::MutableTrack t;
    t.setArtist( "Max Howell" );
    t.setTitle( "I Told You Not To Trust Me With Your Daughter" );
    t.setDuration( 30 );
    t.stamp(); //sets track start time
    
    lastfm::Audioscrobbler as( "ass" );
    as.nowPlaying( t );
    // Audioscrobbler will submit whatever is in the cache when you call submit.
    // And the cache is persistent between sessions. So you should cache at the
    // scrobble point usually, not before
    as.cache( t );
    
    QTimer timer;
    timer.setSingleShot( true );
    timer.setInterval( 31*1000 );
    connect( &timer, SIGNAL(timeout()), &as, SLOT(submit()) );
    timer.start();
    
    app.connect( &as, SIGNAL(status(int)), SLOT(onStatus(int)) );
    
    return app.exec();
}


#include "demo3.moc"
0707010000000D000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000003300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src0707010000000E000081A400000000000000000000000167B98EA8000005B8000000000000000000000000000000000000004200000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/AbstractType.h/*
   Copyright 2010 Last.fm Ltd.
      - Primarily authored by Micahel Coffey and Jono Cole

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef LASTFM_ABSTRACTTYPE_H
#define LASTFM_ABSTRACTTYPE_H

#include <QDomElement>
#include <QUrl>

#include "global.h"

namespace lastfm
{
    class LASTFM_DLLEXPORT AbstractType
    {
    public:
        enum ImageSize
        {
            SmallImage,
            MediumImage,
            LargeImage, /** seemingly 174x174 */
            ExtraLargeImage,
            MegaImage
        };

        virtual QString toString() const = 0;
        virtual QDomElement toDomElement( QDomDocument& ) const = 0;
        virtual QUrl www() const = 0;
        virtual QUrl imageUrl( ImageSize size, bool square ) const = 0;
        virtual ~AbstractType() {;}
    };
};

#endif // LASTFM_ABSTRACTTYPE_H
0707010000000F000081A400000000000000000000000167B98EA80000114C000000000000000000000000000000000000003D00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Album.cpp/*
   Copyright 2009-2010 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "Album.h"
#include "Artist.h"
#include "User.h"
#include "UrlBuilder.h"
#include "XmlQuery.h"
#include "ws.h"
#include <QFile>
#include <QStringList>
#include <QTimer>
#include <QRegularExpression>

using lastfm::Album;
using lastfm::Artist;
using lastfm::Mbid;

namespace lastfm
{
    class AlbumPrivate : public QSharedData
    {
        public:
            AlbumPrivate() {}

            Mbid mbid;
            Artist artist;
            QString title;
            QMap<AbstractType::ImageSize, QUrl> images;
    };
}

Album::Album()
    :AbstractType(), d( new lastfm::AlbumPrivate )
{
}

Album::Album( Mbid mbid )
    :AbstractType(), d( new lastfm::AlbumPrivate )
{
    d->mbid = mbid;
}

Album::Album( Artist artist, QString title )
    :AbstractType(), d( new lastfm::AlbumPrivate )
{
    d->artist = artist;
    d->title = title;
}

Album::Album( const Album& other )
    : d( other.d )
{
}

Album::~Album()
{
}

QDomElement
Album::toDomElement( QDomDocument& ) const
{
    return QDomElement();
}

QUrl
Album::imageUrl( ImageSize size, bool square ) const
{
    if( !square ) return d->images.value( size );

    QUrl url = d->images.value( size );
    QRegularExpression re( "/serve/(\\d*)s?/" );
    return QUrl( url.toString().replace( re, "/serve/\\1s/" ));
}

void
Album::setImageUrl( ImageSize size, const QString& url )
{
    if ( !url.isEmpty() )
        d->images[size] = url;
}

bool
Album::operator==( const Album& that ) const
{
    return d->title == that.d->title && d->artist == that.d->artist;
}

bool
Album::operator!=( const Album& that ) const
{
    return d->title != that.d->title || d->artist != that.d->artist;
}

Album&
Album::operator=( const Album& that )
{
    d = that.d;
    return *this;
}

QString
Album::toString() const
{
    return title();
}

Album::operator QString() const
{
    return toString();
}

QString
Album::title() const
{
    return d->title;
}

void
Album::setArtist( const QString& artist )
{
    return d->artist.setName( artist );
}

Artist
Album::artist() const
{
    return d->artist;
}

Mbid
Album::mbid() const
{
    return d->mbid;
}

bool
Album::isNull() const
{
    return d->title.isEmpty() && d->mbid.isNull();
}


QNetworkReply*
lastfm::Album::getInfo( const QString& username) const
{
    QMap<QString, QString> map;
    map["method"] = "album.getInfo";
    map["artist"] = d->artist;
    map["album"] = d->title;
    if (!username.isEmpty()) map["username"] = username;
    if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey;
    return lastfm::ws::get(map);
}


QNetworkReply*
lastfm::Album::getTags() const
{
    QMap<QString, QString> map;
    map["method"] = "album.getTags";
    map["artist"] = d->artist;
    map["album"] = d->title;
    return lastfm::ws::get(map);
}


QNetworkReply*
lastfm::Album::share( const QStringList& recipients, const QString& message, bool isPublic ) const
{
    QMap<QString, QString> map;
    map["method"] = "album.share";
    map["artist"] = d->artist;
    map["album"] = d->title;
    map["recipient"] = recipients.join(",");
    map["public"] = isPublic ? "1" : "0";
    if (message.size()) map["message"] = message;
    return lastfm::ws::post(map);
}


QUrl
lastfm::Album::www() const
{
    return lastfm::UrlBuilder( "music" ).slash( d->artist ).slash( d->title ).url();
}


QNetworkReply*
lastfm::Album::addTags( const QStringList& tags ) const
{
    if (tags.isEmpty())
        return 0;

    QMap<QString, QString> map;
    map["method"] = "album.addTags";
    map["artist"] = d->artist;
    map["album"] = d->title;
    map["tags"] = tags.join( QChar(',') );
    return lastfm::ws::post(map);
}
07070100000010000081A400000000000000000000000167B98EA800000A2B000000000000000000000000000000000000003B00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Album.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_ALBUM_H
#define LASTFM_ALBUM_H

#include "AbstractType.h"

#include "Artist.h"
#include "Mbid.h"

namespace lastfm
{
    class LASTFM_DLLEXPORT Album : public AbstractType
    {
    public:
        Album();
        explicit Album( Mbid mbid );
        Album( Artist artist, QString title );
        Album( const Album& album );
        ~Album();

        QDomElement toDomElement( QDomDocument& ) const;

        virtual QUrl imageUrl( ImageSize size, bool square = false ) const;
        void setImageUrl( ImageSize size, const QString& url );

        void setArtist( const QString& artist );

        bool operator==( const Album& that ) const;
        bool operator!=( const Album& that ) const;
        Album& operator=( const Album& that );
    
        QString toString() const;
        operator QString() const;
        QString title() const;
        Artist artist() const;
        Mbid mbid() const;

        /** artist may have been set, since we allow that in the ctor, but should we handle untitled albums? */
        bool isNull() const;
    
        /** Album.getInfo WebService */
        QNetworkReply* getInfo( const QString& username = QStringLiteral("") ) const;
        QNetworkReply* share( const QStringList& recipients, const QString& message = QStringLiteral(""), bool isPublic = true ) const;

        /** use Tag::list to get the tag list out of the finished reply */
        QNetworkReply* getTags() const;
        QNetworkReply* getTopTags() const;
        
        /** Last.fm dictates that you may submit at most 10 of these */
        QNetworkReply* addTags( const QStringList& ) const;
    
        /** the Last.fm website url for this album */
        QUrl www() const;

    private:
        QExplicitlySharedDataPointer<class AlbumPrivate> d;
    };
}

#endif //LASTFM_ALBUM_H
07070100000011000081A400000000000000000000000167B98EA800001BBB000000000000000000000000000000000000003E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Artist.cpp/*
   Copyright 2009 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Artist.h"
#include "User.h"
#include "UrlBuilder.h"
#include "XmlQuery.h"
#include "ws.h"

#include <QDebug>
#include <QRegularExpression>
#include <QStringList>

using lastfm::Artist;
using lastfm::ArtistData;
using lastfm::User;
using lastfm::XmlQuery;


class lastfm::ArtistData : public QSharedData
{
public:
    ArtistData() {}
    ~ArtistData() {}
    QString name;
    QMap<AbstractType::ImageSize, QUrl> images;
    QString biography;
    QString biographySummary;
};


Artist::Artist()
    :AbstractType()
{
    d = new ArtistData;
}

Artist::Artist( const QString& name )
    : AbstractType()
{
    d = new ArtistData;
    d->name = name;
}

Artist::Artist( const XmlQuery& xml )
    :AbstractType()
{
    d = new ArtistData;

    d->name = xml["name"].text();
    setImageUrl( SmallImage, xml["image size=small"].text() );
    setImageUrl( MediumImage, xml["image size=medium"].text() );
    setImageUrl( LargeImage, xml["image size=large"].text() );
    setImageUrl( ExtraLargeImage, xml["image size=extralarge"].text() );
    setImageUrl( MegaImage, xml["image size=mega"].text() );

    d->biography = xml["bio"]["content"].text().trimmed();
    d->biographySummary = xml["bio"]["summary"].text().trimmed();
}


Artist::Artist( const Artist& artist )
    :AbstractType(), d( artist.d )
{
}


Artist::~Artist()
{
}


QUrl
Artist::imageUrl( ImageSize size, bool square ) const
{
    if( !square ) return d->images.value( size );

    QUrl url = d->images.value( size );
    QRegularExpression re( "/serve/(\\d*)s?/" );
    return QUrl( url.toString().replace( re, "/serve/\\1s/" ));
}


void
Artist::setImageUrl( ImageSize size, const QString& url )
{
    if ( !url.isEmpty() )
        d->images[size] = url;
}


QString
Artist::biography() const
{
    return d->biography;
}


QString
Artist::biographySummary() const
{
    return d->biographySummary;
}


QMap<QString, QString> //private
Artist::params( const QString& method ) const
{
    QMap<QString, QString> map;
    map["method"] = "artist."+method;
    map["artist"] = d->name;
    return map;
}


QNetworkReply*
Artist::share( const QStringList& recipients, const QString& message, bool isPublic ) const
{
    QMap<QString, QString> map = params("share");
    map["recipient"] = recipients.join(",");
    map["public"] = isPublic ? "1" : "0";
    if (message.size()) map["message"] = message;
    return lastfm::ws::post(map);
}


QUrl
Artist::www() const
{
    return UrlBuilder( "music" ).slash( Artist::name() ).url();
}


QNetworkReply*
Artist::getEvents(int limit) const
{
    QMap<QString, QString> map = params("getEvents");
    if (limit) map["limit"] = QString::number(limit);
    return ws::get( map );
}


QNetworkReply*
Artist::getInfo( const QString& username ) const
{
    QMap<QString, QString> map = params("getInfo");
    if (!username.isEmpty()) map["username"] = username;
    if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey;
    return ws::get( map );
}


QNetworkReply*
Artist::getTags() const
{
    return ws::get( params("getTags") );
}


QNetworkReply*
Artist::getTopTags() const
{
    return ws::get( params("getTopTags") );
}


QNetworkReply*
Artist::getTopTracks() const
{
    return ws::get( params("getTopTracks") );
}


QNetworkReply*
Artist::getSimilar( int limit ) const
{
    QMap<QString, QString> map = params("getSimilar");
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::get( map );
}


QNetworkReply*
Artist::search( int limit ) const
{
    QMap<QString, QString> map = params("search");
    if (limit > 0) map["limit"] = QString::number(limit);
    return ws::get(map);
}


QMap<int, QString> /* static */
Artist::getSimilar( QNetworkReply* r )
{
    QMap<int, QString> artists;

    XmlQuery lfm;

    if ( lfm.parse( r ) )
    {
        foreach (XmlQuery e, lfm.children( "artist" ))
        {
            // convert floating percentage to int in range 0 to 10,000
            int const match = e["match"].text().toFloat() * 100;
            artists.insert( match, e["name"].text() ); // FIXME values with the same key will be overwritten, switch to QMultiMap
        }
    }
    else
    {
        qWarning() << lfm.parseError().message();
    }
    return artists;
}


QStringList /* static */
Artist::getTopTracks( QNetworkReply* r )
{
    QStringList tracks;
    try
    {
        XmlQuery lfm;
        lfm.parse( r );
        foreach (XmlQuery e, lfm.children( "track" ))
        {
            tracks << e["name"].text();
        }
    }
    catch (ws::ParseError& e)
    {
        qWarning() << e.message();
    }
    return tracks;
}


QList<Artist> /* static */
Artist::list( QNetworkReply* r )
{
    QList<Artist> artists;
    XmlQuery lfm;

    if ( lfm.parse( r ) )
    {
        foreach (XmlQuery xq, lfm.children( "artist" ))
        {
            Artist artist( xq );
            artists += artist;
        }
    }
    else
    {
        qWarning() << lfm.parseError().message();
    }
    return artists;
}


Artist
Artist::getInfo( QNetworkReply* r )
{
    XmlQuery lfm;

    if ( lfm.parse( r ) )
    {
        Artist artist = Artist( lfm["artist"] );
        return artist;
    }
    else
    {
        qWarning() << lfm.parseError().message();
        return Artist();
    }
}


QNetworkReply*
Artist::addTags( const QStringList& tags ) const
{
    if (tags.isEmpty())
        return 0;
    QMap<QString, QString> map = params("addTags");
    map["tags"] = tags.join( QChar(',') );
    return ws::post(map);
}


bool
Artist::isNull() const
{
    return d->name.isEmpty();
}


Artist&
Artist::operator=( const Artist& that )
{
    d->name = that.name(); d->images = that.d->images; return *this;
}


bool
lastfm::Artist::operator==( const Artist& that ) const
{
    return d->name == that.d->name;
}


bool
Artist::operator!=( const Artist& that ) const
{
    return d->name != that.d->name;
}


bool
Artist::operator<( const Artist& that ) const
{
    return d->name < that.d->name;
}


Artist::operator QString() const
{
    return d->name;
}


QString Artist::toString() const
{
    return name();
}


QString Artist::name() const
{
    return QString(*this);
}


void Artist::setName( const QString& name )
{
    d->name = name;
}


QDomElement Artist::toDomElement( QDomDocument& ) const
{
    return QDomElement();
}

07070100000012000081A400000000000000000000000167B98EA800000DB1000000000000000000000000000000000000003C00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Artist.h/*
   Copyright 2009-2010 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_ARTIST_H
#define LASTFM_ARTIST_H

#include "AbstractType.h"

#include <QMap>
#include <QExplicitlySharedDataPointer>

class QNetworkReply;

namespace lastfm
{
    class LASTFM_DLLEXPORT Artist : public AbstractType
    {
    private:
        QExplicitlySharedDataPointer<class ArtistData> d;

    public:
        Artist();
        ~Artist();
        Artist( const QString& name );
        Artist( const class XmlQuery& xml );
        Artist( const Artist& artist );

        /** will be QUrl() unless you got this back from a getInfo or something call */
        QUrl imageUrl( ImageSize size = LargeImage, bool square = false ) const;
        void setImageUrl( ImageSize size, const QString& url );

        bool isNull() const;

        /** the url for this artist's page at www.last.fm */
        QUrl www() const;

        Artist& operator=( const Artist& that );
        bool operator==( const Artist& that ) const;
        bool operator!=( const Artist& that ) const;
        bool operator<( const Artist& that ) const;

        operator QString() const;

        QString toString() const;

        QString name() const;
        void setName( const QString& name );

        QString biographySummary() const;
        QString biography() const;

        QDomElement toDomElement( QDomDocument& ) const;

        QNetworkReply* share( const QStringList& recipients, const QString& message = QStringLiteral(""), bool isPublic = true ) const;

        QNetworkReply* getEvents(int limit = 0) const;
        QNetworkReply* getInfo( const QString& username = QStringLiteral("") ) const;
        static Artist getInfo( QNetworkReply* );

        QNetworkReply* getSimilar( int limit = -1 ) const;
        /** The match percentage is returned from last.fm as a 4 significant
          * figure floating point value. So we multply it by 100 to make an
          * integer in the range of 0 to 10,000. This is possible confusing
          * for you, but I felt it best not to lose any precision, and floats
          * aren't much fun. */
        static QMap<int, QString> getSimilar( QNetworkReply* );

        /** use Tag::list to get the tag list out of the finished reply */
        QNetworkReply* getTags() const;
        QNetworkReply* getTopTags() const;

        QNetworkReply* getTopTracks() const;
        static QStringList getTopTracks( QNetworkReply* );

        /** Last.fm dictates that you may submit at most 10 of these */
        QNetworkReply* addTags( const QStringList& ) const;

        QNetworkReply* search( int limit = -1 ) const;
        static QList<Artist> list( QNetworkReply* );

        QMap<QString, QString> params( const QString& method ) const;
    };
}

#endif
07070100000013000081A400000000000000000000000167B98EA800001B1B000000000000000000000000000000000000004600000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Audioscrobbler.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Audioscrobbler.h"
#include "ScrobbleCache.h"
#include "User.h"
#include "ws.h"
#include "XmlQuery.h"

#include <QDebug>
#include <QPointer>


namespace lastfm
{
    class AudioscrobblerPrivate
    {
    public:
        AudioscrobblerPrivate(const QString& id)
                : m_id( id )
                , m_cache( ws::Username )
        {}
        
        ~AudioscrobblerPrivate()
        {
        }

        void parseTrack( const XmlQuery& trackXml, const Track& track );

        const QString m_id;
        ScrobbleCache m_cache;
        QList<Track> m_batch;
        QPointer<QNetworkReply> m_nowPlayingReply;
        QPointer<QNetworkReply> m_scrobbleReply;
        Track m_nowPlayingTrack;
    };
}


lastfm::Audioscrobbler::Audioscrobbler( const QString& id )
        : d( new AudioscrobblerPrivate(id) )
{
    submit();
}


lastfm::Audioscrobbler::~Audioscrobbler()
{
    if ( d->m_nowPlayingReply ) d->m_nowPlayingReply->abort();
    if ( d->m_scrobbleReply ) d->m_scrobbleReply->abort();
    delete d;
}


void
lastfm::Audioscrobbler::nowPlaying( const Track& track )
{
    if ( d->m_nowPlayingReply.isNull())
    {
        d->m_nowPlayingTrack = track;
        d->m_nowPlayingReply = track.updateNowPlaying();
        connect( d->m_nowPlayingReply, SIGNAL(finished()), SLOT(onNowPlayingReturn()));
    }
}


void
lastfm::Audioscrobbler::cache( const Track& track )
{
    QList<Track> tracks;
    tracks.append( track );
    cacheBatch( tracks );
}


void
lastfm::Audioscrobbler::cacheBatch( const QList<lastfm::Track>& tracks, const QString& )
{
    d->m_cache.add( tracks );
    emit scrobblesCached( tracks );
}

void
lastfm::Audioscrobbler::cacheBatch( const QList<lastfm::Track>& tracks )
{
    cacheBatch( tracks, "" ); 
}


void
lastfm::Audioscrobbler::submit()
{
    if (d->m_cache.tracks().isEmpty() // there are no tracks to submit
            || !d->m_scrobbleReply.isNull() ) // we are already submitting scrobbles
        return;

    // copy tracks to be submitted to a temporary list
    d->m_batch = d->m_cache.tracks().mid( 0, 50 );

    // if there is only one track use track.scrobble, otherwise use track.scrobbleBatch
    if (d->m_batch.count() == 1)
        d->m_scrobbleReply = d->m_batch[0].scrobble();
    else
        d->m_scrobbleReply = lastfm::Track::scrobble( d->m_batch );

    connect( d->m_scrobbleReply, SIGNAL(finished()), SLOT(onTrackScrobbleReturn()));
}

void
lastfm::AudioscrobblerPrivate::parseTrack( const XmlQuery& trackXml, const Track& track )
{
    MutableTrack mTrack = MutableTrack( track );
    bool isScrobble = QDomElement(trackXml).tagName() == "scrobble";

    if ( trackXml["ignoredMessage"].attribute("code") == "0" )
    {
        if ( isScrobble ) mTrack.setScrobbleStatus( Track::Submitted );

        // corrections!
        if ( trackXml["track"].attribute("corrected") == "1"
             || trackXml["artist"].attribute("corrected") == "1"
             || trackXml["album"].attribute("corrected") == "1"
             || trackXml["albumArtist"].attribute("corrected") == "1")
        {
            mTrack.setCorrections(trackXml["track"].text(),
                                           trackXml["album"].text(),
                                           trackXml["artist"].text(),
                                           trackXml["albumArtist"].text());
        }
    }
    else if ( isScrobble )
    {
        mTrack.setScrobbleError( static_cast<Track::ScrobbleError>(trackXml["ignoredMessage"].attribute("code").toInt()) );
        mTrack.setScrobbleErrorText( trackXml["ignoredMessage"].text() );
        mTrack.setScrobbleStatus( Track::Error );
    }
}

void
lastfm::Audioscrobbler::onNowPlayingReturn()
{
    lastfm::XmlQuery lfm;

    if ( lfm.parse( d->m_nowPlayingReply ) )
    {
        qDebug() << lfm;

        if ( lfm.attribute("status") == "ok" )
            d->parseTrack( lfm["nowplaying"], d->m_nowPlayingTrack );
        else
            emit nowPlayingError( lfm["error"].attribute("code").toInt(), lfm["error"].text() );
    }
    else
    {
        qDebug() << lfm.parseError().message() << lfm.parseError().enumValue();
    }

    d->m_nowPlayingTrack = Track();
    d->m_nowPlayingReply = 0;
}


void
lastfm::Audioscrobbler::onTrackScrobbleReturn()
{
    lastfm::XmlQuery lfm;

    if ( lfm.parse( d->m_scrobbleReply ) )
    {
        qDebug() << lfm;

        if (lfm.attribute("status") == "ok")
        {
            int index = 0;

            foreach ( const XmlQuery& scrobble, lfm["scrobbles"].children("scrobble") )
                d->parseTrack( scrobble, d->m_batch.at( index++ ) );

            emit scrobblesSubmitted( d->m_batch );

            d->m_cache.remove( d->m_batch );
            d->m_batch.clear();
        }
        else if ( d->m_scrobbleReply->error() == QNetworkReply::NoError )
        {
            // The scrobble submission failed, but the http request was sucessful

            if ( !(lfm["error"].attribute("code") == "9" // Bad session
                || lfm["error"].attribute("code") == "11" // Service offline
                || lfm["error"].attribute("code") == "16") ) // Service temporarily unavailable
            {
                foreach ( const Track& track, d->m_batch )
                {
                    MutableTrack mTrack = MutableTrack( track );
                    mTrack.setScrobbleError( static_cast<Track::ScrobbleError>(lfm["error"].attribute("code").toInt()) );
                    mTrack.setScrobbleErrorText( lfm["error"].text() );
                    mTrack.setScrobbleStatus( Track::Error );
                }

                emit scrobblesSubmitted( d->m_batch );

                // clear the cache if it was not one of these error codes
                d->m_cache.remove( d->m_batch );
                d->m_batch.clear();
            }
            else
            {
                Q_ASSERT(false);
            }
        }

        d->m_scrobbleReply = 0;

        // check is there are anymore scrobbles to submit
        submit();
    }
    else
    {
        qDebug() << lfm.parseError().message() << lfm.parseError().enumValue();
        d->m_scrobbleReply = 0;
    }
}
07070100000014000081A400000000000000000000000167B98EA800000A0B000000000000000000000000000000000000004400000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Audioscrobbler.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_AUDIOSCROBBLER_H
#define LASTFM_AUDIOSCROBBLER_H

#include "global.h"
#include "Track.h"
#include <QList>
#include <QObject>

namespace lastfm
{
    /** @author Max Howell <max@last.fm>
      * An implementation of the Audioscrobbler Realtime Submissions Protocol 
      * version 1.2.1 for a single Last.fm user
      * http://www.audioscrobbler.net/development/protocol/
      */
    class LASTFM_DLLEXPORT Audioscrobbler : public QObject
    {
        Q_OBJECT

    public:
        /** You will need to do QCoreApplication::setVersion and 
          * QCoreApplication::setApplicationName for this to work, also you will 
          * need to have set all the keys in the Ws namespace in WsKeys.h */
        Audioscrobbler( const QString& clientId );
        ~Audioscrobbler();

    Q_SIGNALS:
        void scrobblesCached( const QList<lastfm::Track>& tracks );
        /* Note that this is emitted after we tried to submit the scrobbles
        It could just be that they have an error code */
        void scrobblesSubmitted( const QList<lastfm::Track>& tracks );
        void nowPlayingError( int code, QString message );

    public Q_SLOTS:
        /** will ask Last.fm to update the now playing information for the 
          * authenticated user */
        void nowPlaying( const Track& );
        /** will cache the track and call submit() */
        void cache( const Track& );
        void cacheBatch( const QList<lastfm::Track>&, const QString& id );
        void cacheBatch( const QList<lastfm::Track>& );

        /** will submit the submission cache for this user */
        void submit();

    private Q_SLOTS:
        void onNowPlayingReturn();
        void onTrackScrobbleReturn();

    private:
        class AudioscrobblerPrivate * const d;
    };
}

#endif
07070100000015000081A400000000000000000000000167B98EA800000598000000000000000000000000000000000000003C00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Auth.cpp/*
   Copyright 2012 Last.fm Ltd.
      - Primarily authored by Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Auth.h"
#include "ws.h"


lastfm::Auth::Auth()
{
}


QNetworkReply*
lastfm::Auth::getSessionInfo()
{
    QMap<QString, QString> map;
    map["method"] = "Auth.getSessionInfo";
    if (!lastfm::ws::Username.isEmpty()) map["username"] = lastfm::ws::Username;
    return nam()->get( QNetworkRequest( lastfm::ws::url( map, true ) ) );
}

QNetworkReply*
lastfm::Auth::getMobileSession( const QString& username, const QString& password )
{
    QMap<QString, QString> map;
    map["method"] = "Auth.getMobileSession";
    map["username"] = username;
    map["password"] = password;

    return 0;
    //return nam()->post( QNetworkRequest( lastfm::ws::url( map, true ) ) );
}
07070100000016000081A400000000000000000000000167B98EA80000042B000000000000000000000000000000000000003A00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Auth.h/*
   Copyright 2012 Last.fm Ltd.
      - Primarily authored by Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "global.h"

class QNetworkReply;

namespace lastfm
{
    class LASTFM_DLLEXPORT Auth
    {
    private:
        Auth();

    public:
        static QNetworkReply* getSessionInfo();
        static QNetworkReply* getMobileSession( const QString& username, const QString& password );
    };
}

07070100000017000081A400000000000000000000000167B98EA800000C35000000000000000000000000000000000000004200000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/CMakeLists.txtconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/global.h.in ${CMAKE_CURRENT_BINARY_DIR}/global.h)

include_directories(${CMAKE_CURRENT_BINARY_DIR})

set(liblastfm_SOURCES
        ws.cpp
        NetworkConnectionMonitor.cpp
        NetworkAccessManager.cpp
        InternetConnectionMonitor.cpp
        Xspf.cpp
        User.cpp
        Track.cpp
        Tasteometer.cpp
        Tag.cpp
        Playlist.cpp
        Mbid.cpp
        FingerprintId.cpp
        Artist.cpp
        Album.cpp
        ScrobbleCache.cpp
        ScrobblePoint.cpp
        Audioscrobbler.cpp
        RadioTuner.cpp
        RadioStation.cpp
        XmlQuery.cpp
        Url.cpp
        UrlBuilder.cpp
        misc.cpp
        Chart.cpp
        Auth.cpp
        Library.cpp
)

if(WIN32)
    add_definitions("-DWIN32_LEAN_AND_MEAN")

    if(NOT MINGW)
        add_definitions("-D_ATL_DLL -D_CRT_SECURE_NO_WARNINGS")

        set(liblastfm_SOURCES ${liblastfm_SOURCES}
            win/WNetworkConnectionMonitor_win.cpp
            win/WmiSink.cpp
            win/NdisEvents.cpp
        )
    endif()
endif()

if(APPLE)
    #FIXME: enable this when deploying
    #set(CMAKE_OSX_ARCHITECTURES "i386;ppc")
    #set(CMAKE_OSX_DEPLOYMENT_TARGET 10.5)
    #set(CMAKE_OSX_SYSROOT "/Developer/SDKs/MacOSX${CMAKE_OSX_DEPLOYMENT_TARGET}.sdk")

    set(liblastfm_SOURCES ${liblastfm_SOURCES}
        mac/MNetworkConnectionMonitor_mac.cpp
    )
endif()

if(UNIX AND NOT APPLE)
    set(liblastfm_SOURCES ${liblastfm_SOURCES}
        linux/LNetworkConnectionMonitor_linux.cpp
    )
endif()

add_library(${LASTFM_LIB_TARGET_NAME} SHARED ${liblastfm_SOURCES})

target_link_libraries(${LASTFM_LIB_TARGET_NAME} Qt::Network Qt::Xml)
if(UNIX AND NOT APPLE)
    target_link_libraries(${LASTFM_LIB_TARGET_NAME} Qt::DBus)
endif()

if(WIN32 AND NOT MINGW)
    target_link_libraries(${LASTFM_LIB_TARGET_NAME}
        winhttp
        wbemuuid
    )
endif()

if(APPLE)
    find_library(SYSTEMCONFIGURATION_LIBRARY SystemConfiguration)
    find_library(COREFOUNDATION_LIBRARY CoreFoundation)
    target_link_libraries(${LASTFM_LIB_TARGET_NAME}
        ${COREFOUNDATION_LIBRARY}
        ${SYSTEMCONFIGURATION_LIBRARY}
    )
endif()

set_target_properties(${LASTFM_LIB_TARGET_NAME} PROPERTIES
    VERSION ${LASTFM_VERSION_STRING}
    SOVERSION ${LASTFM_SOVERSION}
    COMPILE_DEFINITIONS LASTFM_LIB
)

if(WIN32)
    set_target_properties(${LASTFM_LIB_TARGET_NAME} PROPERTIES
        RUNTIME_OUTPUT_NAME "${LASTFM_LIB_TARGET_NAME}-${LASTFM_SOVERSION}")
endif()

install(TARGETS ${LASTFM_LIB_TARGET_NAME}
    EXPORT "${LASTFM_LIB_TARGET_NAME}Config"
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(EXPORT "${LASTFM_LIB_TARGET_NAME}Config"
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/lastfm${LASTFM_LIB_VERSION_SUFFIX}
    NAMESPACE LASTFM::
)

file(GLOB liblastfm_HEADERS ${CMAKE_CURRENT_LIST_DIR}/*.h)
list(APPEND liblastfm_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/global.h)
install(FILES ${liblastfm_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lastfm${LASTFM_LIB_VERSION_SUFFIX}/)
07070100000018000081A400000000000000000000000167B98EA800000B7B000000000000000000000000000000000000003D00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Chart.cpp/*
   Copyright 2011 Last.fm Ltd.
      - Primarily authored by Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "Chart.h"
#include "ws.h"

#include <QNetworkReply>


lastfm::Chart::Chart()
{
}

QNetworkReply*
lastfm::Chart::getHypedArtists( int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "chart.getHypedArtists";
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::get( map );
}

QNetworkReply*
lastfm::Chart::getHypedTracks( int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "chart.getHypedTracks";
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::get( map );
}

QNetworkReply*
lastfm::Chart::getLovedTracks( int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "chart.getLovedTracks";
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::get( map );
}

QNetworkReply*
lastfm::Chart::getTopArtists( int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "chart.getTopArtists";
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::get( map );
}

QNetworkReply*
lastfm::Chart::getTopDownloads( int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "chart.getTopDownloads";
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::get( map );
}

QNetworkReply*
lastfm::Chart::getTopTags( int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "chart.getTopTags";
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::get( map );
}

QNetworkReply*
lastfm::Chart::getTopTracks( int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "chart.getTopTracks";
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::get( map );
}
07070100000019000081A400000000000000000000000167B98EA8000005F7000000000000000000000000000000000000003B00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Chart.h/*
   Copyright 2011 Last.fm Ltd.
      - Primarily authored by Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_CHART_H
#define LASTFM_CHART_H

#include "global.h"

class QNetworkReply;

namespace lastfm
{
    class LASTFM_DLLEXPORT Chart
    {
    private:
        Chart();

    public:
        static QNetworkReply* getHypedArtists( int limit = -1, int page = -1 );
        static QNetworkReply* getHypedTracks( int limit = -1, int page = -1 );
        static QNetworkReply* getLovedTracks( int limit = -1, int page = -1 );
        static QNetworkReply* getTopArtists( int limit = -1, int page = -1 );
        static QNetworkReply* getTopDownloads( int limit = -1, int page = -1 );
        static QNetworkReply* getTopTags( int limit = -1, int page = -1 );
        static QNetworkReply* getTopTracks( int limit = -1, int page = -1 );
    };
}

#endif // LASTFM_CHART_H

0707010000001A000081A400000000000000000000000167B98EA800000AE5000000000000000000000000000000000000004500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/FingerprintId.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <QtNetwork>
#include <QtXml>

#include "FingerprintId.h"
#include "ws.h"
#include "XmlQuery.h"
 

class lastfm::FingerprintIdPrivate
{
public:
    int id;
};


lastfm::FingerprintId::FingerprintId()
    : d( new FingerprintIdPrivate )
{
    d->id = -1;
}


lastfm::FingerprintId::FingerprintId( uint i )
    : d( new FingerprintIdPrivate )
{
    d->id = i;
}


lastfm::FingerprintId::FingerprintId( const FingerprintId& that )
    : d( new FingerprintIdPrivate( *that.d ) )
{
}


lastfm::FingerprintId::~FingerprintId()
{
    delete d;
}


bool
lastfm::FingerprintId::isNull() const
{
    return d->id == -1;
}


QNetworkReply*
lastfm::FingerprintId::getSuggestions() const
{
    QMap<QString, QString> map;
    map["method"] = "track.getFingerprintMetadata";
    map["fingerprintid"] = QString::number( d->id );
    return ws::get( map );
}


QMap<float, lastfm::Track> //static
lastfm::FingerprintId::getSuggestions( QNetworkReply* reply )
{
    QMap<float, Track> tracks;
    lastfm::XmlQuery lfm;

    if ( lfm.parse( reply ) )
    {
        foreach ( const lastfm::XmlQuery& track, lfm["tracks"].children("track") )
        {
            MutableTrack t;
            t.setTitle( track["name"].text() );
            t.setArtist( track["artist"]["name"].text() );
            t.setDuration( track["duration"].text().toInt() );
            t.setUrl( track["url"].text() );
            t.setMbid( lastfm::Mbid( track["mbid"].text() ) );
            tracks.insert( track.attribute("rank").toFloat(), t );
        }
    }

    return tracks;
}


lastfm::FingerprintId::operator int() const
{
    return d->id;
}


lastfm::FingerprintId::operator QString() const
{
    return d->id == -1 ? "" : QString::number( d->id );
}

lastfm::FingerprintId&
lastfm::FingerprintId::operator=( const FingerprintId& that )
{
    d->id = that.d->id;
    return *this;
}


QDebug
operator<<( QDebug d, lastfm::FingerprintId id)
{
    if (id.isNull())
        return d << "(null)";
    else
        return d << int(id);
}
0707010000001B000081A400000000000000000000000167B98EA8000006B6000000000000000000000000000000000000004300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/FingerprintId.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_FINGERPRINT_ID_H
#define LASTFM_FINGERPRINT_ID_H

#include "Track.h"
 
namespace lastfm
{
    class LASTFM_DLLEXPORT FingerprintId
    {
    public:
        FingerprintId();
        FingerprintId( uint i );
        FingerprintId( const FingerprintId& other );

        ~FingerprintId();

        bool isNull() const;

        /** we query Last.fm for suggested metadata, how awesome is that? 
          * @returns null if isNull() */
        QNetworkReply* getSuggestions() const;
        static QMap<float,Track> getSuggestions( QNetworkReply* );

        /** -1 if you need to generate it */
        operator int() const;
        /** isEmpty() if you need to generate it */
        operator QString() const;
        FingerprintId& operator=( const FingerprintId& other );

    private:
        class FingerprintIdPrivate * const d;
    };
}


LASTFM_DLLEXPORT QDebug operator<<( QDebug d, lastfm::FingerprintId id);

#endif
0707010000001C000081A400000000000000000000000167B98EA800001050000000000000000000000000000000000000005100000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/InternetConnectionMonitor.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "InternetConnectionMonitor.h"
#include "linux/LNetworkConnectionMonitor.h"
#include "mac/MNetworkConnectionMonitor.h"
#include "win/WNetworkConnectionMonitor.h"
#include "NetworkConnectionMonitor.h"
#include "ws.h"

#include <QDebug>

class lastfm::InternetConnectionMonitorPrivate
{
public:
    bool m_up;
    NetworkConnectionMonitor* m_networkMonitor;
};

lastfm::InternetConnectionMonitor::InternetConnectionMonitor( QObject *parent )
                                 : QObject( parent )
                                 , d( new InternetConnectionMonitorPrivate)
{
    d->m_up = true;
    d->m_networkMonitor = createNetworkConnectionMonitor();

    if ( d->m_networkMonitor )
    {
        connect( d->m_networkMonitor, SIGNAL( networkUp() ), this, SLOT( onNetworkUp() ) );
        connect( d->m_networkMonitor, SIGNAL( networkDown() ), this, SLOT( onNetworkDown() ) );
    }

    connect( nam(), SIGNAL( finished( QNetworkReply* ) ), this, SLOT( onFinished( QNetworkReply* ) ) );
}

lastfm::InternetConnectionMonitor::~InternetConnectionMonitor()
{
    delete d;
}

bool lastfm::InternetConnectionMonitor::isDown() const
{
    return !d->m_up;
}

bool lastfm::InternetConnectionMonitor::isUp() const
{
    return d->m_up;
}

void
lastfm::InternetConnectionMonitor::onFinished( QNetworkReply* reply )
{
    if( reply->attribute( QNetworkRequest::SourceIsFromCacheAttribute).toBool() ) return;

    switch( reply->error() )
    {
        case QNetworkReply::NoError:
            if ( !d->m_up )
            {
                d->m_up = true;
                emit up();
                emit connectivityChanged( d->m_up );
                qDebug() << "Internet connection is reachable :)";
            }
            break;
        case QNetworkReply::HostNotFoundError:
        case QNetworkReply::TimeoutError:
        case QNetworkReply::ProxyConnectionRefusedError:
        case QNetworkReply::ProxyConnectionClosedError:
        case QNetworkReply::ProxyNotFoundError:
        case QNetworkReply::ProxyTimeoutError:
        case QNetworkReply::ProxyAuthenticationRequiredError:
            if ( d->m_up )
            {
                d->m_up = false;
                emit down();
                emit connectivityChanged( d->m_up );
            }
            break;
        default:
            break;
    }
}

void
lastfm::InternetConnectionMonitor::onNetworkUp()
{
#ifdef Q_OS_MAC
    // We don't need to check on mac as the
    // check is done as part of the reach api
    d->m_up = true;

    emit up();
    emit connectivityChanged( d->m_up );
    qDebug() << "Internet connection is reachable :)";
#else
    qDebug() << "Network seems to be up again. Let's try if there's internet connection!";
    nam()->head( QNetworkRequest( QUrl( "http://www.last.fm/" ) ) );
#endif
}

void
lastfm::InternetConnectionMonitor::onNetworkDown()
{
    qDebug() << "Internet is unreachable :(";
    d->m_up = false;
    emit down();
    emit connectivityChanged( d->m_up );
}

lastfm::NetworkConnectionMonitor*
lastfm::InternetConnectionMonitor::createNetworkConnectionMonitor()
{
    NetworkConnectionMonitor* ncm = 0;

#if defined(Q_OS_MAC)
    ncm = new MNetworkConnectionMonitor( this );
#elif defined(Q_OS_WIN) && ! defined __MINGW32__
    ncm = new WNetworkConnectionMonitor( this );
#elif defined(Q_OS_LINUX)
    ncm = new LNetworkConnectionMonitor( this );
#endif

    return ncm;
}
0707010000001D000081A400000000000000000000000167B98EA8000007B8000000000000000000000000000000000000004F00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/InternetConnectionMonitor.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef LASTFM_CONNECTION_MONITOR_H
#define LASTFM_CONNECTION_MONITOR_H

#include "global.h"
#include <QObject>

class QNetworkReply;

namespace lastfm {

class NetworkConnectionMonitor;

class LASTFM_DLLEXPORT InternetConnectionMonitor : public QObject
{
    Q_OBJECT

public:
    /** if internet is unavailable you will get a down() signal soon, otherwise
      * you won't get a signal until the net goes down */
    InternetConnectionMonitor( QObject *parent = 0 );
    ~InternetConnectionMonitor();

    bool isDown() const;
    bool isUp() const;

    NetworkConnectionMonitor* createNetworkConnectionMonitor();

Q_SIGNALS:
    /** yay! internet has returned */
    void up( const QString& connectionName = "" );
    
    /** we think the internet is unavailable, but well, still try, but show
      * an unhappy face in the statusbar or something */
    void down( const QString& connectionName = "" );
    
    /** emitted after the above */
    void connectivityChanged( bool );

private Q_SLOTS:
    void onFinished( QNetworkReply* reply );
    void onNetworkUp();
    void onNetworkDown();

private:
    class InternetConnectionMonitorPrivate * const d;
};

} //namespace lastfm

#endif
0707010000001E000081A400000000000000000000000167B98EA800001163000000000000000000000000000000000000003F00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Library.cpp/*
   Copyright 2011 Last.fm Ltd.
      - Primarily authored by Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Library.h"
#include "ws.h"

#include <QNetworkReply>


lastfm::Library::Library()
{
}

QNetworkReply*
lastfm::Library::addAlbum( const QList<lastfm::Album>& albums )
{
    QMap<QString, QString> map;
    map["method"] = "library.addAlbum";

    for ( int i = 0 ; i < albums.count() ; ++i )
    {
        map["artist[" + QString::number(i) + "]"] = albums[i].artist();
        map["album[" + QString::number(i) + "]"] = albums[i].title();
    }

    return ws::post( map );
}

QNetworkReply*
lastfm::Library::addArtist( const QList<lastfm::Artist>& artists )
{
    QMap<QString, QString> map;
    map["method"] = "library.addArtist";

    for ( int i = 0 ; i < artists.count() ; ++i )
        map["artist[" + QString::number(i) + "]"] = artists[i].name();

    return ws::post( map );
}

QNetworkReply*
lastfm::Library::addTrack( const Track &track )
{
    QMap<QString, QString> map;
    map["method"] = "library.addTrack";
    map["track"] = track.title();
    map["artist"] = track.artist().name();
    return ws::post( map );
}

QNetworkReply*
lastfm::Library::getAlbums( const QString& user, const lastfm::Artist& artist, int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "library.getAlbums";
    map["user"] = user;
    map["artist"] = artist.name();
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::post( map );
}

QNetworkReply*
lastfm::Library::getArtists( const QString& user, int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "library.getArtists";
    map["user"] = user;
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::post( map );
}

QNetworkReply*
lastfm::Library::getTracks( const QString& user, const lastfm::Artist& artist, int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "library.getTracks";
    map["user"] = user;
    map["artist"] = artist.name();
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    return ws::post( map );
}

QNetworkReply*
lastfm::Library::getTracks( const QString& user, const lastfm::Album& album, int limit, int page )
{
    QMap<QString, QString> map;
    map["method"] = "library.getTracks";
    map["user"] = user;
    map["album"] = album.title();
    map["artist"] = album.artist().name();
    if ( page != -1 ) map["page"] = QString::number( page );
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    return ws::post( map );
}

QNetworkReply*
lastfm::Library::removeAlbum( const lastfm::Album& album )
{
    QMap<QString, QString> map;
    map["method"] = "library.removeAlbum";
    map["album"] = album.title();
    map["artist"] = album.artist().name();
    return ws::post( map );
}

QNetworkReply*
lastfm::Library::removeArtist( const lastfm::Artist& artist )
{
    QMap<QString, QString> map;
    map["method"] = "library.removeArtist";
    map["artist"] = artist.name();
    return ws::post( map );
}

QNetworkReply*
lastfm::Library::removeTrack( const lastfm::Track& track )
{
    QMap<QString, QString> map;
    map["method"] = "library.removeTrack";
    map["artist"] = track.artist().name();
    map["track"] = track.title();
    return ws::post( map );
}

QNetworkReply*
lastfm::Library::removeScrobble( const lastfm::Track& track )
{
    QMap<QString, QString> map;
    map["method"] = "library.removeScrobble";
    map["artist"] = track.artist().name();
    map["track"] = track.title();
    map["timestamp"] = QString::number( track.timestamp().toMSecsSinceEpoch() / 1000 );
    return ws::post( map );
}

0707010000001F000081A400000000000000000000000167B98EA8000007E6000000000000000000000000000000000000003D00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Library.h/*
   Copyright 2011 Last.fm Ltd.
      - Primarily authored by Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_LIBRARY_H
#define LASTFM_LIBRARY_H

#include "Track.h"

namespace lastfm
{
    class LASTFM_DLLEXPORT Library
    {
    private:
        Library();

    public:
        static QNetworkReply* addAlbum( const QList<lastfm::Album>& albums );
        static QNetworkReply* addArtist( const QList<lastfm::Artist>& artists );
        static QNetworkReply* addTrack( const lastfm::Track& tracks );

        static QNetworkReply* getAlbums( const QString& user, const lastfm::Artist& artist = lastfm::Artist(), int limit = -1, int page = -1 );
        static QNetworkReply* getArtists( const QString& user, int limit = -1, int page = -1 );
        static QNetworkReply* getTracks( const QString& user, const lastfm::Artist& artist = lastfm::Artist(), int limit = -1, int page = -1 );
        static QNetworkReply* getTracks( const QString& user, const lastfm::Album& album = lastfm::Album(), int limit = -1, int page = -1 );

        static QNetworkReply* removeAlbum( const lastfm::Album& album );
        static QNetworkReply* removeArtist( const lastfm::Artist& artist );
        static QNetworkReply* removeTrack( const lastfm::Track& track );

        static QNetworkReply* removeScrobble( const lastfm::Track& track );
    };
}

#endif // LASTFM_LIBRARY_H

07070100000020000081A400000000000000000000000167B98EA800000649000000000000000000000000000000000000003C00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Mbid.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "Mbid.h"
#include "mbid_mp3.c"

#include <QFile>

namespace lastfm
{

class MbidPrivate
{
public:
    QString id;
};


Mbid::Mbid( const QString& p )
    : d( new MbidPrivate )
{
    d->id = p;
}


Mbid::Mbid( const Mbid& that )
    : d( new MbidPrivate( *that.d ) )
{
}


Mbid::~Mbid()
{
    delete d;
}


bool
Mbid::isNull() const
{
    return d->id.isNull() || d->id.isEmpty();
}


Mbid::operator QString() const
{
    return d->id;
}


Mbid&
Mbid::operator=( const Mbid& that )
{
    d->id = that.d->id;
    return *this;
}


Mbid //static
Mbid::fromLocalFile( const QString& path )
{
    char out[MBID_BUFFER_SIZE];
    QByteArray const bytes = QFile::encodeName( path );
    int const r = getMP3_MBID( bytes.data(), out );
    Mbid mbid;
    if (r == 0) mbid.d->id = QString::fromLatin1( out );
    return mbid;
}

}
07070100000021000081A400000000000000000000000167B98EA80000058C000000000000000000000000000000000000003A00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Mbid.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_MBID_H
#define LASTFM_MBID_H

#include "global.h"

namespace lastfm
{
    class LASTFM_DLLEXPORT Mbid
    {
    public:
        explicit Mbid( const QString& p = QStringLiteral("") );
        Mbid( const Mbid& that );
        ~Mbid();

        bool isNull() const;
        operator QString() const;
        Mbid& operator=( const Mbid& that );

        /** if this is not an mp3 file you will be wasting time, as it won't work
          * but we will do what you say anyway because you are the boss */ 
        static Mbid fromLocalFile( const QString& path );
    private:
        class MbidPrivate * const d;
    };
}

#endif
07070100000022000081A400000000000000000000000167B98EA800000EB9000000000000000000000000000000000000004C00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/NetworkAccessManager.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "NetworkAccessManager.h"
#include "InternetConnectionMonitor.h"
#include "ws.h"
#include "misc.h"
#include <QCoreApplication>
#include <QNetworkRequest>
#include <QNetworkProxyQuery>

class NetworkAccessManagerPrivate
{
    public:
    NetworkAccessManagerPrivate();
    QNetworkProxy userProxy;
};

NetworkAccessManagerPrivate::NetworkAccessManagerPrivate() :
    userProxy( QNetworkProxy( QNetworkProxy::DefaultProxy ) )
{
}


// TODO: Use a d-pointer on the next SONAME bump
typedef QHash< const lastfm::NetworkAccessManager *, NetworkAccessManagerPrivate *> NamPrivateHash;
Q_GLOBAL_STATIC( NamPrivateHash, d_func )
static NetworkAccessManagerPrivate * d( const lastfm::NetworkAccessManager * nam )
{
    NetworkAccessManagerPrivate * ret = d_func()->value( nam, 0 );
    if ( !ret )
    {
        ret = new NetworkAccessManagerPrivate;
        d_func()->insert( nam, ret );
    }
    return ret;
}
static void delete_d( const lastfm::NetworkAccessManager * nam )
{
    const NetworkAccessManagerPrivate * ret = d_func()->value( nam, 0 );
    delete ret;
    d_func()->remove( nam );
}


namespace lastfm 
{
    LASTFM_DLLEXPORT QByteArray UserAgent;
}


lastfm::NetworkAccessManager::NetworkAccessManager( QObject* parent )
               : QNetworkAccessManager( parent )
            #if defined WIN32 && ! defined __MINGW32__
               , m_monitor( 0 )
            #endif
{
    // can't be done in above init, as applicationName() won't be set
    if (lastfm::UserAgent.isEmpty())
    {
        QByteArray name = QCoreApplication::applicationName().toUtf8();
        QByteArray version = QCoreApplication::applicationVersion().toUtf8();
        if (version.size()) version.prepend( ' ' );
        lastfm::UserAgent = name + version + " (" + lastfm::platform() + ")";
    }
}


lastfm::NetworkAccessManager::~NetworkAccessManager()
{
    delete_d( this );
}

void
lastfm::NetworkAccessManager::setUserProxy( const QNetworkProxy& proxy )
{
    d( this )->userProxy = proxy;
}

QNetworkProxy
lastfm::NetworkAccessManager::proxy( const QNetworkRequest& request )
{   
    if ( d( this )->userProxy.type() != QNetworkProxy::DefaultProxy )
        return d( this )->userProxy;

    QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery( QNetworkProxyQuery( request.url() ) );

    return proxies[0];
}


QNetworkReply*
lastfm::NetworkAccessManager::createRequest( Operation op, const QNetworkRequest& request_, QIODevice* outgoingData )
{
    QNetworkRequest request = request_;

    request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
    request.setRawHeader( "User-Agent", lastfm::UserAgent );
    
    // PAC proxies can vary by domain, so we have to check everytime :(
    QNetworkAccessManager::setProxy( this->proxy( request ) );

    return QNetworkAccessManager::createRequest( op, request, outgoingData );
}


void
lastfm::NetworkAccessManager::onConnectivityChanged( bool up )
{
    Q_UNUSED( up );
}
07070100000023000081A400000000000000000000000167B98EA80000086A000000000000000000000000000000000000004A00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/NetworkAccessManager.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_WS_ACCESS_MANAGER_H
#define LASTFM_WS_ACCESS_MANAGER_H

#include "global.h"
#include <QtNetwork/QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkProxy>

class QNetworkReply;

namespace lastfm {

/** Sets useragent and proxy. Auto detecting the proxy where possible. */
class LASTFM_DLLEXPORT NetworkAccessManager : public QNetworkAccessManager
{
    Q_OBJECT

#ifdef Q_OS_WIN
    class InternetConnectionMonitor* m_monitor;
#endif

public:
    NetworkAccessManager( QObject *parent = 0 );
    ~NetworkAccessManager();

    void setUserProxy( const QNetworkProxy& proxy );

    /** PAC allows different proxy configurations depending on the request
      * URL and even UserAgent! Thus we allow you to pass that in, we
      * automatically configure the proxy for every request through 
      * WsAccessManager */
    QNetworkProxy proxy( const QNetworkRequest& = QNetworkRequest() );

protected:
    virtual QNetworkReply* createRequest( Operation, const QNetworkRequest&, QIODevice* outgoingdata = 0 );

private Q_SLOTS:
    void onConnectivityChanged( bool );

private:
    /** this function calls QNetworkAccessManager::setProxy, and thus 
      * configures the proxy correctly for the next request created by
      * createRequest. This is necessary due */
    void applyProxy( const QNetworkRequest& );
};

} //namespace lastfm

#endif
07070100000024000081A400000000000000000000000167B98EA8000005D4000000000000000000000000000000000000005000000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/NetworkConnectionMonitor.cpp/*
   Copyright 2010 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "NetworkConnectionMonitor.h"

class lastfm::NetworkConnectionMonitorPrivate
{
    public:
        bool connected;
};

lastfm::NetworkConnectionMonitor::NetworkConnectionMonitor( QObject* /*parent*/ )
    : d( new NetworkConnectionMonitorPrivate )
{
    d->connected = true;
}

lastfm::NetworkConnectionMonitor::~NetworkConnectionMonitor()
{
    delete d;
}

bool
lastfm::NetworkConnectionMonitor::isConnected() const
{
    return d->connected;
}

void
lastfm::NetworkConnectionMonitor::setConnected( bool connected )
{
    if ( d->connected != connected )
    {
        d->connected = connected;

        if ( connected )
            emit networkUp();
        else
            emit networkDown();
    }
}

07070100000025000081A400000000000000000000000167B98EA80000053E000000000000000000000000000000000000004E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/NetworkConnectionMonitor.h/*
   Copyright 2010 Last.fm Ltd.
      - Primarily authored by Jono Cole, Michael Coffey, and William Viana

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef NETWORK_CONNECTION_MONITOR_H
#define NETWORK_CONNECTION_MONITOR_H

#include "global.h"
#include <QObject>

namespace lastfm
{

class LASTFM_DLLEXPORT NetworkConnectionMonitor : public QObject
{
    Q_OBJECT
public:
    NetworkConnectionMonitor( QObject *parent = 0 );
    ~NetworkConnectionMonitor();
    bool isConnected() const;

Q_SIGNALS:
    void networkUp();
    void networkDown();

protected:
    void setConnected( bool connected );

private:
    class NetworkConnectionMonitorPrivate * const d;
};

}

#endif // NETWORK_CONNECTION_MONITOR_H
07070100000026000081A400000000000000000000000167B98EA80000092E000000000000000000000000000000000000004000000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Playlist.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "Playlist.h"
#include "Track.h"
#include "ws.h"


class lastfm::PlaylistPrivate
{
    public:
        int id;
};
    

lastfm::Playlist::Playlist()
    : d( new PlaylistPrivate )
{
    d->id = -1;
}


lastfm::Playlist::Playlist( const Playlist& that )
    : d( new PlaylistPrivate( *that.d ) )
{
}


lastfm::Playlist::~Playlist()
{
    delete d;
}


lastfm::Playlist::Playlist( int id )
    : d( new PlaylistPrivate )
{
    d->id = id;
}


int
lastfm::Playlist::id() const
{
    return d->id;
}


QNetworkReply*
lastfm::Playlist::addTrack( const Track& t ) const
{
    QMap<QString, QString> map;
    map["method"] = "playlist.addTrack";
    map["playlistID"] = QString::number(d->id);
    map["artist"] = t.artist();
    map["track"] = t.title();
    return lastfm::ws::post(map);
}


QNetworkReply*
lastfm::Playlist::fetch() const
{
    return fetch( QUrl("lastfm://playlist/" + QString::number( d->id )) );
}


QNetworkReply* //static
lastfm::Playlist::fetch( const QUrl& url )
{
    QMap<QString, QString> map;
    map["method"] = "playlist.fetch";
    map["playlistURL"] = url.toString();
    return lastfm::ws::get(map);
}


QNetworkReply* //static
lastfm::Playlist::create( const QString& title, const QString& description /*=""*/ )
{
    QMap<QString, QString> map;
    map["method"] = "playlist.create";
    map["title"] = title;
    if (description.size()) 
        map["description"] = description;
    return lastfm::ws::post(map);
}


lastfm::Playlist&
lastfm::Playlist::operator=( const Playlist& that )
{
    d->id = that.d->id;
    return *this;
}
07070100000027000081A400000000000000000000000167B98EA80000058D000000000000000000000000000000000000003E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Playlist.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_PLAYLIST_H
#define LASTFM_PLAYLIST_H

#include "Xspf.h"

namespace lastfm
{
    class LASTFM_DLLEXPORT Playlist
    {
        Playlist();
        class PlaylistPrivate * const d;

    public:
        Playlist( int id );
        Playlist( const Playlist& that );
        ~Playlist();
    
        int id() const;

        QNetworkReply* addTrack( const Track& ) const;
        QNetworkReply* fetch() const;

        static QNetworkReply* create( const QString& title, const QString& description = "" );
        static QNetworkReply* fetch( const QUrl& url );

        Playlist& operator=( const Playlist& that );
    };
}

#endif
07070100000028000081A400000000000000000000000167B98EA800001E1F000000000000000000000000000000000000004400000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/RadioStation.cpp/*
   Copyright 2009 Last.fm Ltd. 

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "RadioStation.h"
#include "XmlQuery.h"

#include <QDebug>
#include <QStringList>


const float k_defaultRep(0.5);
const float k_defaultMainstr(0.5);
const bool k_defaultDisco(false);

class lastfm::RadioStationData : public QSharedData
{
public:
    QUrl m_url;
    QString m_title;
    QString m_tagFilter;

    float m_rep;
    float m_mainstr;
    bool m_disco;
};


lastfm::RadioStation::RadioStation()
    : d( new RadioStationData )
{
}

lastfm::RadioStation::RadioStation( const QString& s )
    : d( new RadioStationData )
{
    // If it's a tag filtered station then extract that part
    QString tempString = s;

    if ( !tempString.startsWith("lastfm://tag/") )
    {
        int index = tempString.indexOf("/tag/");

        if ( index != -1 )
        {
            d->m_tagFilter = tempString.mid( index + 5, tempString.count() - (index + 5) );
            tempString = tempString.mid( 0, index );
        }
    }

    d->m_url = tempString;
}

lastfm::RadioStation::RadioStation( const RadioStation& other )
    : d(other.d)
{
}

lastfm::RadioStation&
lastfm::RadioStation::operator=( const RadioStation& that )
{
    d = that.d;
    return *this;
}

lastfm::RadioStation::~RadioStation()
{
}

lastfm::RadioStation
lastfm::RadioStation::library( const lastfm::User& user )
{
    QList<lastfm::User> users;
    users << user;
    return library( users );
}

lastfm::RadioStation
lastfm::RadioStation::library( QList<lastfm::User>& users )
{
    std::sort(users.begin(), users.end());

    QString url = (users.count() > 1) ? "lastfm://users/" : "lastfm://user/";

    url.append( users[0].name() );

    for ( int i = 1 ; i < users.count() ; ++i )
        url.append( "," + users[i].name() );

    url.append("/personal");

    RadioStation s( url );
    if( users.count() != 1 )
    {
        QString title;

        for( QList<lastfm::User>::const_iterator i = users.constBegin(); i != users.constEnd(); i++ )
        {
            if( i == users.constEnd() - 1 )
                title += " and " + *i;
            else
                title += ", " + *i;
        }

        s.setTitle( title );
    }

    return s;
}


lastfm::RadioStation
lastfm::RadioStation::recommendations( const lastfm::User& user )
{
    return RadioStation( "lastfm://user/" + user + "/recommended" );
}

lastfm::RadioStation
lastfm::RadioStation::friends( const lastfm::User& user )
{
    return RadioStation( "lastfm://user/" + user + "/friends" );
}

lastfm::RadioStation
lastfm::RadioStation::neighbourhood( const lastfm::User& user )
{
    return RadioStation( "lastfm://user/" + user + "/neighbours" );
}


lastfm::RadioStation
lastfm::RadioStation::tag( const lastfm::Tag& tag )
{
    QList<lastfm::Tag> tags;
    tags << tag;
    return lastfm::RadioStation::tag( tags );
}


lastfm::RadioStation
lastfm::RadioStation::tag( QList<lastfm::Tag>& tag )
{
    std::sort(tag.begin(), tag.end());

    QString url = (tag.count() > 1) ? "lastfm://tag/" : "lastfm://globaltags/";

    url.append( tag[0].name() );

    for ( int i = 1 ; i < tag.count() ; ++i )
        url.append( "*" + tag[i].name() );

    return RadioStation( url );
}


lastfm::RadioStation
lastfm::RadioStation::similar( const lastfm::Artist& artist )
{
    QList<lastfm::Artist> artists;
    artists << artist;
    return similar( artists );
}


lastfm::RadioStation
lastfm::RadioStation::similar( QList<lastfm::Artist>& artists )
{
    std::sort(artists.begin(), artists.end());

    QString url = (artists.count() > 1) ? "lastfm://artistnames/" : "lastfm://artist/";

    url.append( artists[0].name() );

    for ( int i = 1 ; i < artists.count() ; ++i )
        url.append( "," + artists[i].name() );

    if (artists.count() == 1)
        url.append( "/similarartists" );

    return RadioStation( url );
}


lastfm::RadioStation
lastfm::RadioStation::mix( const lastfm::User& user )
{
    return RadioStation( "lastfm://user/" + user + "/mix" );
}


QString
lastfm::RadioStation::url() const
{
    return d->m_url.toString() + (d->m_tagFilter.isEmpty() ? "" : "/tag/" + d->m_tagFilter);
}


void
lastfm::RadioStation::setTitle( const QString& title )
{
    // Stop the radio station getting renamed when the web services don't know what it's called
    if ( !d->m_title.isEmpty() && title.compare( "a radio station", Qt::CaseInsensitive ) == 0 )
        return;

    // do not rename the current user's stations if they already have a name
    if ( !d->m_title.isEmpty() && d->m_url.toString().startsWith( "lastfm://user/" + User().name() ) )
        return;

    d->m_title = title.trimmed();
}

void
lastfm::RadioStation::setUrl( const QString& url )
{
    d->m_url = url;
}


QString
lastfm::RadioStation::title() const
{
    return d->m_title; // + (d->m_tagFilter.isEmpty() ? "" : ": " + d->m_tagFilter);
}


void
lastfm::RadioStation::setTagFilter( const QString& tag )
{
    d->m_tagFilter = tag;
}


QNetworkReply*
lastfm::RadioStation::getSampleArtists( int limit ) const
{
    QMap<QString, QString> map;
    map["method"] = "radio.getSampleArtists";
    map["station"] = d->m_url.toString();
    map["limit"] = QString::number( limit );
    return ws::get( map );
}


QNetworkReply*
lastfm::RadioStation::getTagSuggestions( int limit ) const
{
    QMap<QString, QString> map;
    map["method"] = "radio.getTagSuggestions";
    map["station"] = d->m_url.toString();
    map["limit"] = QString::number( limit );
    return ws::get( map );
}


bool
lastfm::RadioStation::isLegacyPlaylist() const
{
    return d->m_url.toString().startsWith( "lastfm://play/" ) ||
           d->m_url.toString().startsWith( "lastfm://preview/" ) ||
           d->m_url.toString().startsWith( "lastfm://track/" ) ||
           d->m_url.toString().startsWith( "lastfm://playlist/" );
}


//static 
QList<lastfm::RadioStation> 
lastfm::RadioStation::list( QNetworkReply* r )
{
    QList<lastfm::RadioStation> result;
    XmlQuery lfm;

    if ( lfm.parse( r ) )
    {

        foreach (XmlQuery xq, lfm.children("station"))
        {
            lastfm::RadioStation rs( QUrl::fromPercentEncoding( xq["url"].text().toUtf8() ) );
            rs.setTitle(xq["name"].text());
            result.append(rs);
        }
    }
    else
    {
        qWarning() << lfm.parseError().message();
    }

    return result;
}


bool
lastfm::RadioStation::operator==( const RadioStation& that ) const
{
    return this->d->m_url == that.d->m_url && this->d->m_tagFilter == that.d->m_tagFilter;
}


void
lastfm::RadioStation::setRep(float rep)
{
    d->m_rep = rep;
}


void
lastfm::RadioStation::setMainstr(float mainstr)
{
    d->m_mainstr = mainstr;
}


void
lastfm::RadioStation::setDisco(bool disco)
{
    d->m_disco = disco;
}


float lastfm::RadioStation::rep() const
{
    return d->m_rep;
}


float lastfm::RadioStation::mainstr() const
{
    return d->m_mainstr;
}


bool lastfm::RadioStation::disco() const
{
    return d->m_disco;
}


QDebug operator<<( QDebug d, const lastfm::RadioStation& station )
{
    return d << station.url();
}
07070100000029000081A400000000000000000000000167B98EA800000C42000000000000000000000000000000000000004200000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/RadioStation.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_RADIO_STATION_H
#define LASTFM_RADIO_STATION_H

#include "User.h"
#include "Tag.h"
#include "Artist.h"

namespace lastfm
{
    /** @author <jono@last.fm> 
      */

    class LASTFM_DLLEXPORT RadioStation
    {
    public:
        RadioStation();
        RadioStation( const QString& s );
        RadioStation( const RadioStation& that );
        ~RadioStation();
    
        static RadioStation library( const lastfm::User& user );
        static RadioStation library( QList<lastfm::User>& users );

        static RadioStation similar( const lastfm::Artist& artist );
        static RadioStation similar( QList<lastfm::Artist>& artist );

        static RadioStation tag( const lastfm::Tag& tag );
        static RadioStation tag( QList<lastfm::Tag>& tag );

        static RadioStation recommendations( const lastfm::User& user );
        static RadioStation friends( const lastfm::User& user );
        static RadioStation neighbourhood( const lastfm::User& user );

        static RadioStation mix( const lastfm::User& user );

        QNetworkReply* getSampleArtists( int limit = 50 ) const;
        QNetworkReply* getTagSuggestions( int limit = 50 ) const;

        /** eg. "mxcl's Loved Tracks"
          * It is worth noting that the Radio doesn't set the title of RadioStation 
          * object until we have tuned to it, and then we only set the one we give 
          * you back.
          */    
        QString title() const;
        /** the Last.fm url, eg. lastfm://user/mxcl/loved */
        QString url() const;

        void setTitle( const QString& title );
        void setUrl( const QString& url );

        void setTagFilter( const QString& tag );

        void setRep(float rep);
        void setMainstr(float mainstr);
        void setDisco(bool disco);

        float rep() const;
        float mainstr() const;
        bool disco() const;

        bool isLegacyPlaylist() const;

        // good for getRecentStations:
        static QList<RadioStation> list( QNetworkReply* );

        bool operator==( const RadioStation& that ) const;
        RadioStation& operator=( const RadioStation& that );

    private:
        QSharedDataPointer<class RadioStationData> d;
    };
}


Q_DECLARE_METATYPE( lastfm::RadioStation )


LASTFM_DLLEXPORT QDebug operator<<( QDebug d, const lastfm::RadioStation& station );

#endif
0707010000002A000081A400000000000000000000000167B98EA800001EF9000000000000000000000000000000000000004200000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/RadioTuner.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "RadioTuner.h"
#include "RadioStation.h"
#include "XmlQuery.h"
#include "Xspf.h"
#include "ws.h"

#include <QDebug>
#include <QTimer>

using namespace lastfm;

//TODO skips left
//TODO multiple locations for the same track
//TODO set rtp flag in getPlaylist (whether user is scrobbling this radio session or not)

// limit the number of retries following empty playlists:
#define MAX_TUNING_ATTEMPTS 3


class lastfm::RadioTunerPrivate : public QObject
{
    Q_OBJECT
    public:
        QList<Track> m_playlist;
        uint m_retry_counter;
        bool m_fetchingPlaylist;
        bool m_requestedPlaylist;
        class QTimer* m_twoSecondTimer;
        RadioStation m_station;
        RadioStation m_retuneStation;

        RadioTunerPrivate( QObject * parent, const RadioStation& station );

        /** Tries again up to 5 times 
          * @returns true if we tried again, otherwise you should emit error */
        bool tryAgain();
        /** Will emit 5 tracks from tracks(), they have to played within an hour
          * or the streamer will refuse to stream them. Also the previous five are
          * invalidated apart from the one that is currently playing, so sorry, you
          * can't build up big lists of tracks.
          *
          * I feel I must point out that asking the user which one they want to play
          * is also not allowed according to our terms and conditions, which you
          * already agreed to in order to get your API key. Sorry about that dude. 
          */
        void fetchFiveMoreTracks();

    private Q_SLOTS:
        void onTwoSecondTimeout();
};


lastfm::RadioTunerPrivate::RadioTunerPrivate( QObject *parent, const RadioStation& station )
    : QObject( parent ),
    m_station( station )
{
    m_retry_counter = 0;
    m_fetchingPlaylist = false;
    m_requestedPlaylist = false;
    m_twoSecondTimer = new QTimer( this );
    m_twoSecondTimer->setSingleShot( true );
    connect( m_twoSecondTimer, SIGNAL(timeout()), SLOT(onTwoSecondTimeout()));
}


void
RadioTunerPrivate::onTwoSecondTimeout()
{
    if (m_requestedPlaylist)
    {
        m_requestedPlaylist = false;
        fetchFiveMoreTracks();
    }
}



void
RadioTunerPrivate::fetchFiveMoreTracks()
{
    if ( !m_retuneStation.url().isEmpty() )
    {
        // We have been asked to retune so do it now
        QMap<QString, QString> map;
        map["method"] = "radio.tune";
        map["station"] = m_retuneStation.url();
        map["additional_info"] = "1";

        QNetworkReply* reply = ws::post(map);
        connect( reply, SIGNAL(finished()), parent(), SLOT(onTuneReturn()) );

        //m_retuneStation = RadioStation();
        m_twoSecondTimer->stop();
    }
    else
    {
        if ( !m_twoSecondTimer->isActive() )
        {
            //TODO check documentation, I figure this needs a session key
            QMap<QString, QString> map;
            map["method"] = "radio.getPlaylist";
            map["additional_info"] = "1";
            map["rtp"] = "1"; // see above
            connect( ws::post( map ), SIGNAL(finished()), parent(), SLOT(onGetPlaylistReturn()) );
            m_fetchingPlaylist = true;
        }
        else
            m_requestedPlaylist = true;
    }
}


bool
RadioTunerPrivate::tryAgain()
{
    qDebug() << "Bad response count" << m_retry_counter;
    
    if (++m_retry_counter > MAX_TUNING_ATTEMPTS)
        return false;
    fetchFiveMoreTracks();
    return true;
}


RadioTuner::RadioTuner( const RadioStation& station )
    :d( new RadioTunerPrivate( this, station ) )
{
    qDebug() << station.url();

    //Empty RadioStation implies that the radio
    //should tune to the previous station.
    if( station.url().isEmpty() )
    {
        d->fetchFiveMoreTracks();
    }
    else
    {
        QMap<QString, QString> map;
        map["method"] = "radio.tune";
        map["station"] = station.url();
        map["additional_info"] = "1";
        connect( ws::post(map), SIGNAL(finished()), SLOT(onTuneReturn()) );
    }
}

RadioTuner::~RadioTuner()
{
}

void
RadioTuner::retune( const RadioStation& station )
{
    d->m_playlist.clear();
    d->m_retuneStation = station;

    qDebug() << station.url();
}


void
RadioTuner::onTuneReturn()
{
    if ( !d->m_retuneStation.url().isEmpty() )
    {
        d->m_station = d->m_retuneStation;
        d->m_retuneStation = RadioStation();
    }

    XmlQuery lfm;

    if ( lfm.parse( qobject_cast<QNetworkReply*>(sender()) ) )
    {
        qDebug() << "Tune successful!";

        d->m_station.setTitle( lfm["station"]["name"].text() );
        d->m_station.setUrl( lfm["station"]["url"].text() );

        emit title( lfm["station"]["name"].text() );
        emit supportsDisco( lfm["station"]["supportsdiscovery"].text() == "1" );
        d->fetchFiveMoreTracks();
    }
    else
    {
        emit error( lfm.parseError().enumValue(), lfm.parseError().message() );
    }
}


void
RadioTuner::onGetPlaylistReturn()
{   
    // We shouldn't request another playlist for 2 seconds because we'll get the same one
    // in a different order. This QTimer will block until it has finished. If one or more
    // playlists have been requested in the meantime, it will fetch one on timeout
    d->m_twoSecondTimer->start( 2000 );

    // This will block us fetching two playlists at once
    d->m_fetchingPlaylist = false;

    XmlQuery lfm;

    if ( lfm.parse( qobject_cast<QNetworkReply*>(sender()) ) )
    {
        qDebug() << "Get playlist successful!";

        d->m_station.setTitle( lfm["playlist"]["title"].text() );
        // we don't get the radio url in the playlist
        //d->m_station.setUrl( lfm["station"]["url"].text() );

        emit title( lfm["playlist"]["title"].text() );

        Xspf* xspf = new Xspf( lfm["playlist"], this );

        if ( xspf->isEmpty() )
        {
            // give up after too many empty playlists  :(
            if (!d->tryAgain())
                emit error( ws::NotEnoughContent, "Not enough content" );
        }
        else
        {
            d->m_retry_counter = 0;
            d->m_playlist << xspf->tracks();
            emit trackAvailable();
        }

        delete xspf;
    }
    else
    {
        qDebug() << lfm.parseError().message() << lfm.parseError().enumValue();
        emit error( lfm.parseError().enumValue(), lfm.parseError().message() );
    }
}

void
RadioTuner::onXspfExpired()
{
    // no-op
}

void
RadioTuner::queueTrack( lastfm::Track& track )
{
    d->m_playlist.insert( 0, track );
}

bool
trackExpired( const Track& track )
{
    return (!track.extra( "expiry" ).isEmpty()) && QDateTime::currentDateTime() > QDateTime::fromMSecsSinceEpoch( track.extra( "expiry" ).toInt() * 1000 );
}

Track
RadioTuner::takeNextTrack()
{
    if ( !d->m_playlist.isEmpty() )
    {
        Track track = d->m_playlist.takeFirst();

        while ( trackExpired( track ) && !d->m_playlist.isEmpty() )
            track = d->m_playlist.takeFirst();

        if ( !trackExpired( track ) )
            return track;
    }
    else if ( !d->m_fetchingPlaylist )
        d->fetchFiveMoreTracks();

    return Track();
}

#include "RadioTuner.moc"
0707010000002B000081A400000000000000000000000167B98EA8000007AA000000000000000000000000000000000000004000000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/RadioTuner.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_TUNER_H
#define LASTFM_TUNER_H

#include "Track.h"
#include "ws.h"

#include <QObject>

namespace lastfm
{
    class RadioStation;

    /** With regard to error handling. We handle Ws::TryAgain up to 5 times,
      * don't try again after that! Just tell the user to try again later. 
      */
    class LASTFM_DLLEXPORT RadioTuner : public QObject
    {
        Q_OBJECT
    
    public:
        /** You need to have assigned Ws::* for this to work, creating the tuner
          * automatically fetches the first 5 tracks for the station */
        explicit RadioTuner( const RadioStation& );
        ~RadioTuner();

        Track takeNextTrack();

        void retune( const RadioStation& );

        void queueTrack( lastfm::Track& track );

    Q_SIGNALS:
        void title( const QString& );
        void supportsDisco( bool supportsDisco );
        void trackAvailable();
        void error( lastfm::ws::Error, const QString& message );

    private Q_SLOTS:
        void onTuneReturn();
        void onGetPlaylistReturn();
        // no-op
        Q_DECL_DEPRECATED void onXspfExpired();

    private:
        class RadioTunerPrivate * const d;
    };
}

#endif
0707010000002C000081A400000000000000000000000167B98EA800001961000000000000000000000000000000000000004500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/ScrobbleCache.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "ScrobbleCache.h"
#include "ScrobblePoint.h"
#include "misc.h"
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QDomElement>
#include <QDomDocument>

using lastfm::ScrobbleCache;


class lastfm::ScrobbleCachePrivate
{
public:
    QString m_username;
    QString m_path;
    QList<Track> m_tracks;

    void write(); /// writes m_tracks to m_path
    void read( QDomDocument& xml );  /// reads from m_path into m_tracks   
    
};


ScrobbleCache::ScrobbleCache( const QString& username )
    : d( new ScrobbleCachePrivate )
{
    Q_ASSERT( username.length() );

    d->m_path = lastfm::dir::runtimeData().filePath( username + "_subs_cache.xml" );
    d->m_username = username;

    QDomDocument xml;
    d->read( xml );
}


ScrobbleCache::ScrobbleCache( const ScrobbleCache& that )
    : d ( new ScrobbleCachePrivate( *that.d ) )
{
}


ScrobbleCache&
ScrobbleCache::operator=( const ScrobbleCache& that )
{
    d->m_username = that.d->m_username;
    d->m_path = that.d->m_path;
    d->m_tracks = that.d->m_tracks;
    return *this;
}


ScrobbleCache::~ScrobbleCache()
{
    delete d;
}


void
lastfm::ScrobbleCachePrivate::read( QDomDocument& xml )
{
    m_tracks.clear();

    QFile file( m_path );
    file.open( QFile::Text | QFile::ReadOnly );
    QTextStream stream( &file );
#if QT_VERSION >= 0x060000
    stream.setEncoding( QStringConverter::Utf8 );
#else
    stream.setCodec( "UTF-8" );
#endif

    xml.setContent( stream.readAll() );

    for (QDomNode n = xml.documentElement().firstChild(); !n.isNull(); n = n.nextSibling())
        if (n.nodeName() == "track")
            m_tracks += Track( n.toElement() );
}


void
lastfm::ScrobbleCachePrivate::write()
{
    if (m_tracks.isEmpty())
    {
        QFile::remove( m_path );
    }
    else {
        QDomDocument xml;
        QDomElement e = xml.createElement( "submissions" );
        e.setAttribute( "product", QCoreApplication::applicationName() );
        e.setAttribute( "version", "2" );

        foreach (Track i, m_tracks)
            e.appendChild( i.toDomElement( xml ) );

        xml.appendChild( e );

        QFile file( m_path );
        file.open( QIODevice::WriteOnly | QIODevice::Text );

        QTextStream stream( &file );
#if QT_VERSION >= 0x060000
        stream.setEncoding( QStringConverter::Utf8 );
#else
        stream.setCodec( "UTF-8" );
#endif
        stream << "<?xml version='1.0' encoding='utf-8'?>\n";
        stream << xml.toString( 2 );
        file.close();
    }
}


void
ScrobbleCache::add( const QList<lastfm::Track>& tracks )
{
    foreach (const Track& track, tracks)
    {
        Invalidity invalidity;
        
        if ( !isValid( track, &invalidity ) )
        {
            qWarning() << invalidity;
            MutableTrack mt = MutableTrack( track );
            mt.setScrobbleStatus( Track::Error );
            mt.setScrobbleError( Track::Invalid );
            mt.setScrobbleErrorText( "Invalid" );
        }
        else if (track.isNull()) 
            qDebug() << "Will not cache an empty track";
        else 
        {
            bool ok;
            int plays = track.extra( "playCount" ).toInt( &ok );
            if ( !ok ) plays = 1;

            // Add the track that the app is sharing and set it's scrobble status
            d->m_tracks += track;
            MutableTrack( track ).setScrobbleStatus( Track::Cached );

            // now add any duplicate plays as clones that are all 1 second apart
            for ( int i = 1 ; i < plays ; ++i )
            {
                MutableTrack mt = MutableTrack( track.clone() );
                mt.setTimeStamp( mt.timestamp().addSecs( -i ) );
                d->m_tracks += mt;
            }
        }
    }

    d->write();
}


int
ScrobbleCache::remove( const QList<lastfm::Track>& toremove )
{
    QMutableListIterator<Track> i( d->m_tracks );
    while (i.hasNext()) {
        Track t = i.next();
        for (int x = 0; x < toremove.count(); ++x)
            if (toremove[x] == t)
                i.remove();
    }

    d->write();

    // yes we return # remaining, rather # removed, but this is an internal 
    // function and the behaviour is documented so it's alright imo --mxcl
    return d->m_tracks.count();
}


bool
ScrobbleCache::isValid( const lastfm::Track& track, Invalidity* v )
{
    #define TEST( test, x ) \
        if (test) { \
            if (v) *v = x; \
            return false; \
        }

    TEST( track.duration() < ScrobblePoint::scrobbleTimeMin(), ScrobbleCache::TooShort );

    TEST( !track.timestamp().isValid(), ScrobbleCache::NoTimestamp );

    // actual spam prevention is something like 12 hours, but we are only
    // trying to weed out obviously bad data, server side criteria for
    // "the future" may change, so we should let the server decide, not us
    TEST( track.timestamp() > QDateTime::currentDateTime().addMonths( 1 ), ScrobbleCache::FromTheFuture );

    TEST( track.timestamp().daysTo( QDateTime::currentDateTime() ) > 14, ScrobbleCache::FromTheDistantPast );

    // Check if any required fields are empty
    TEST( track.artist().isNull(), ScrobbleCache::ArtistNameMissing );
    TEST( track.title().isEmpty(), ScrobbleCache::TrackNameMissing );

    TEST( (QStringList() << "unknown artist"
                         << "unknown"
                         << "[unknown]"
                         << "[unknown artist]").contains( track.artist().name().toLower() ),
           ScrobbleCache::ArtistInvalid );

    return true;
}


QList<lastfm::Track>
ScrobbleCache::tracks() const
{
    return d->m_tracks;
}


QString
ScrobbleCache::path() const
{
    return d->m_path;
}


QString
ScrobbleCache::username() const
{
    return d->m_username;
}
0707010000002D000081A400000000000000000000000167B98EA80000078F000000000000000000000000000000000000004300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/ScrobbleCache.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_SCROBBLE_CACHE_H
#define LASTFM_SCROBBLE_CACHE_H

#include "Track.h"
#include <QList>

namespace lastfm {

/** absolutely not thread-safe */
class LASTFM_DLLEXPORT ScrobbleCache
{
public:
    enum Invalidity
    {
        TooShort,
        ArtistNameMissing,
        TrackNameMissing,
        ArtistInvalid,
        NoTimestamp,
        FromTheFuture,
        FromTheDistantPast
    };

    explicit ScrobbleCache( const QString& username );
    ScrobbleCache( const ScrobbleCache& that );
    ~ScrobbleCache();

    /** note this is unique for Track::sameAs() and equal timestamps 
      * obviously playcounts will not be increased for the same timestamp */
    void add( const QList<Track>& );

    /** returns the number of tracks left in the queue */
    int remove( const QList<Track>& );

    static bool isValid( const lastfm::Track& track, Invalidity* v = 0 );

    ScrobbleCache& operator=( const ScrobbleCache& that );

    QList<Track> tracks() const;
    QString path() const;
    QString username() const;

private:
    bool operator==( const ScrobbleCache& ); //undefined
    class ScrobbleCachePrivate * const d;
};

}

#endif
0707010000002E000081A400000000000000000000000167B98EA800000ABB000000000000000000000000000000000000004500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/ScrobblePoint.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "ScrobblePoint.h"

#define SCROBBLE_PERCENT_MIN      50
#define SCROBBLE_PERCENT_MAX     100
#define DEFAULT_SCROBBLE_PERCENT  50
#define SCROBBLE_TIME_MIN         31
#define SCROBBLE_TIME_MAX        240

class lastfm::ScrobblePointPrivate
{
public:
    uint i;
    bool enforceScrobbleTimeMax;
};


lastfm::ScrobblePoint::ScrobblePoint()
    : d( new ScrobblePointPrivate )
{
    d->i = SCROBBLE_TIME_MAX;
    d->enforceScrobbleTimeMax = true;
}


lastfm::ScrobblePoint::ScrobblePoint( uint j )
    : d( new ScrobblePointPrivate )
{
    // we special case 0, returning SCROBBLE_TIME_MAX because we are
    // cruel and callous people
    if (j == 0) --j;

    d->i = j;
    d->enforceScrobbleTimeMax = true;
}


lastfm::ScrobblePoint::ScrobblePoint( const ScrobblePoint& that )
    : d( new ScrobblePointPrivate )
{
    d->i = that.d->i;
    d->enforceScrobbleTimeMax = that.d->enforceScrobbleTimeMax;
}


lastfm::ScrobblePoint::~ScrobblePoint()
{
    delete d;
}


void
lastfm::ScrobblePoint::setEnforceScrobbleTimeMax( bool enforceScrobbleTimeMax )
{
    d->enforceScrobbleTimeMax = enforceScrobbleTimeMax;
}


lastfm::ScrobblePoint::operator uint() const
{
    return qBound( uint(SCROBBLE_TIME_MIN),
                        d->i,
                        d->enforceScrobbleTimeMax ? uint(SCROBBLE_TIME_MAX) : 0xFFFFFFFF );
}


lastfm::ScrobblePoint&
lastfm::ScrobblePoint::operator=( const ScrobblePoint& that )
{
    d->i = that.d->i;
    d->enforceScrobbleTimeMax = that.d->enforceScrobbleTimeMax;
    return *this;
}


uint
lastfm::ScrobblePoint::scrobblePercentMin()
{
    return SCROBBLE_PERCENT_MIN;
}


uint
lastfm::ScrobblePoint::scrobblePercentMax()
{
    return SCROBBLE_PERCENT_MAX;
}


uint
lastfm::ScrobblePoint::defaultScrobblePercent()
{
    return DEFAULT_SCROBBLE_PERCENT;
}


uint
lastfm::ScrobblePoint::scrobbleTimeMin()
{
    return SCROBBLE_TIME_MIN;
}


uint
lastfm::ScrobblePoint::scrobbleTimeMax()
{
    return SCROBBLE_TIME_MAX;
}
0707010000002F000081A400000000000000000000000167B98EA8000006FE000000000000000000000000000000000000004300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/ScrobblePoint.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_SCROBBLE_POINT_H
#define LASTFM_SCROBBLE_POINT_H

#include "global.h"


namespace lastfm
{

class LASTFM_DLLEXPORT ScrobblePoint
{
public:
    ScrobblePoint();
    ~ScrobblePoint();
    
    /** j is in seconds, and should be 50% the duration of a track */
    explicit ScrobblePoint( uint j );
    ScrobblePoint( const ScrobblePoint& that );
    operator uint() const;
    ScrobblePoint& operator=( const ScrobblePoint& that );

    void setEnforceScrobbleTimeMax( bool enforceScrobbleTimeMax );

    // scrobbles can occur between these two percentages of track duration
    static uint scrobblePercentMin();      //  50
    static uint scrobblePercentMax();      // 100
    static uint defaultScrobblePercent();  //  50

    // Shortest track length allowed to scrobble in seconds
    static uint scrobbleTimeMin();         //  31
    // Upper limit for scrobble time in seconds
    static uint scrobbleTimeMax();         // 240

private:
    class ScrobblePointPrivate * const d;
};

}

#endif
07070100000030000081A400000000000000000000000167B98EA800000A73000000000000000000000000000000000000003B00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Tag.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "Tag.h"
#include "User.h"
#include "UrlBuilder.h"
#include "XmlQuery.h"
#include "ws.h"

#include <QDebug>

using lastfm::Tag;
using lastfm::User;


class lastfm::TagPrivate
{
public:
    QString name;
};


Tag::Tag( const QString& name )
    : d( new TagPrivate )
{
    d->name = name;
}


Tag::Tag( const Tag& that )
    : d( new TagPrivate( *that.d ) )
{
}


Tag::~Tag()
{
    delete d;
}

Tag::operator QString() const
{
    return d->name;
}


QString
Tag::name() const
{
    return d->name;
}


lastfm::Tag
Tag::operator=( const Tag& that ) const
{
    return Tag( that.name() );
}


bool
Tag::operator<( const Tag& that ) const
{
    return this->d->name < that.d->name;
}


QUrl
Tag::www() const
{
    return UrlBuilder( "tag" ).slash( d->name ).url();
}


QUrl
Tag::www( const User& user ) const
{
    return UrlBuilder( "user" ).slash( user.name() ).slash( "tags" ).slash( Tag::name() ).url();
}


QNetworkReply*
Tag::search() const
{
    QMap<QString, QString> map;
    map["method"] = "tag.search";
    map["tag"] = d->name;
    return ws::get(map);
}

//static
QNetworkReply* 
Tag::getTopTags()
{
    QMap<QString, QString> map;
    map["method"] = "tag.getTopTags";
    return ws::get(map);
}

QMap<int, QString> //static
Tag::list( QNetworkReply* r )
{
    QMap<int, QString> tags;

    XmlQuery lfm;

    if ( lfm.parse( r ) )
    {

        foreach ( XmlQuery xq, lfm.children("tag") )
            // we toLower always as otherwise it is ugly mixed case, as first
            // ever tag decides case, and Last.fm is case insensitive about it 
            // anyway
            tags.insert( xq["count"].text().toInt(), xq["name"].text().toLower() ); // FIXME values with the same key will be overwritten, switch to QMultiMap
    }
    else
    {
        qDebug() << lfm.parseError().message() << lfm.parseError().enumValue();
    }

    return QMap<int, QString>(tags);
}
07070100000031000081A400000000000000000000000167B98EA80000080D000000000000000000000000000000000000003900000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Tag.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_TAG_H
#define LASTFM_TAG_H

#include "global.h"
#include <QMap>
#include <QUrl>

class QNetworkReply;

namespace lastfm
{
    class User;

    class LASTFM_DLLEXPORT Tag
    {
    public:
        Tag( const QString& name );
        Tag( const Tag& that );
        ~Tag();
    
        operator QString() const;
        QString name() const;

        lastfm::Tag operator=( const Tag& that ) const;
        bool operator<( const Tag& that ) const;
    
        /** the global tag page at www.last.fm */
        QUrl www() const;
        /** the tag page for user @p user at www.last.fm */
        QUrl www( const User& user ) const;
        /** pass the finished QNetworkReply to Tag::list() */
        QNetworkReply* search() const;

        /** the top global tags on Last.fm, sorted by popularity (number of times used) */
        static QNetworkReply* getTopTags();
    
        /** the integer is the weighting, not all list type return requests
          * have a weighting, so the int may just be zero, if you don't care
          * about the weight just do this: 
          * QStringList tags = Tag::list( reply ).values();
          */
        static QMap<int, QString> list( QNetworkReply* );

    private:
        class TagPrivate * const d;
    };
}

#endif
07070100000032000081A400000000000000000000000167B98EA8000004AE000000000000000000000000000000000000004300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Tasteometer.cpp/*
   Copyright 2011 Last.fm Ltd.
      - Primarily authored by Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Tasteometer.h"
#include "User.h"

lastfm::Tasteometer::Tasteometer()
{
}


lastfm::Tasteometer::~Tasteometer()
{
}


QNetworkReply*
lastfm::Tasteometer::compare( const User& left, const User& right )
{
    QMap<QString, QString> map;
    map["method"] = "Tasteometer.compare";
    map["type1"] = "user";
    map["value1"] = left.name();
    map["type2"] = "user";
    map["value2"] = right.name();
    return lastfm::ws::get( map );
}
07070100000033000081A400000000000000000000000167B98EA800000452000000000000000000000000000000000000004100000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Tasteometer.h/*
   Copyright 2011 Last.fm Ltd.
      - Primarily authored by Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef TASTEOMETER_H
#define TASTEOMETER_H

#include "global.h"

class QNetworkReply;

namespace lastfm
{
    class User;

    class LASTFM_DLLEXPORT Tasteometer
    {
    public:
        Tasteometer();
        ~Tasteometer();

    public:
        static QNetworkReply* compare( const User& left, const User& right );
    };
}

#endif // TASTEOMETER_H
07070100000034000081A400000000000000000000000167B98EA800007AFD000000000000000000000000000000000000003D00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Track.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Track.h"
#include "User.h"
#include "UrlBuilder.h"
#include "XmlQuery.h"
#include "ws.h"

#include <QFileInfo>
#include <QStringList>
#include <QAbstractNetworkCache>
#include <QRegularExpression>
#include <QDebug>


class lastfm::TrackContextPrivate
{
    public:
        TrackContext::Type m_type;
        QList<QString> m_values;
        static TrackContext::Type getType( const QString& typeString );
};

lastfm::TrackContext::Type
lastfm::TrackContextPrivate::getType( const QString& typeString )
{
    lastfm::TrackContext::Type type = lastfm::TrackContext::UnknownType;

    if ( typeString == "artist" )
        type = lastfm::TrackContext::Artist;
    else if ( typeString == "user" )
        type = lastfm::TrackContext::User;
    else if ( typeString == "neighbour" )
        type = lastfm::TrackContext::Neighbour;
    else if ( typeString == "friend" )
        type = lastfm::TrackContext::Friend;

    return type;
}


lastfm::TrackContext::TrackContext()
    :d( new TrackContextPrivate )
{
    d->m_type = UnknownType;
}

lastfm::TrackContext::TrackContext( const QString& type, const QList<QString>& values )
    :d( new TrackContextPrivate )
{
    d->m_values = values;
    d->m_type = d->getType( type );
}

lastfm::TrackContext::TrackContext( const TrackContext& that )
    :d( new TrackContextPrivate( *that.d ) )
{
}

lastfm::TrackContext::~TrackContext()
{
    delete d;
}

lastfm::TrackContext::Type
lastfm::TrackContext::type() const
{
    return d->m_type;
}


QList<QString>
lastfm::TrackContext::values() const
{
    return d->m_values;
}

lastfm::TrackContext&
lastfm::TrackContext::operator=( const TrackContext& that )
{
    d->m_type = that.d->m_type;
    d->m_values = that.d->m_values;
    return *this;
}

class TrackObject : public QObject
{
    Q_OBJECT
public:
    TrackObject( lastfm::TrackData& data ) : m_data( data ) {;}

public:
    void forceLoveToggled( bool love );
    void forceScrobbleStatusChanged();
    void forceCorrected( QString correction );

private Q_SLOTS:
    void onLoveFinished();
    void onUnloveFinished();
    void onGotInfo();

Q_SIGNALS:
    void loveToggled( bool love );
    void scrobbleStatusChanged( short scrobbleStatus );
    void corrected( QString correction );

private:
    lastfm::TrackData& m_data;
};

class lastfm::TrackData : public QSharedData
{
    friend class TrackObject;

public:
    TrackData();
    ~TrackData();

public:
    lastfm::Artist artist;
    lastfm::Artist albumArtist;
    lastfm::Album album;
    QString title;
    lastfm::Artist correctedArtist;
    lastfm::Artist correctedAlbumArtist;
    lastfm::Album correctedAlbum;
    QString correctedTitle;
    TrackContext context;
    uint trackNumber;
    uint duration;
    short source;
    short rating;
    QString mbid; /// musicbrainz id
    uint fpid;
    QUrl url;
    QDateTime time; /// the time the track was started at
    lastfm::Track::LoveStatus loved;
    QMap<AbstractType::ImageSize, QUrl> m_images;
    short scrobbleStatus;
    short scrobbleError;
    QString scrobbleErrorText;

    //FIXME I hate this, but is used for radio trackauth etc.
    QMap<QString,QString> extras;

    struct Observer
    {
        QNetworkReply* reply;
        QPointer<QObject> receiver;
        const char* method;
    };

    QList<Observer> observers;

    bool null;

    bool podcast;
    bool video;

    TrackObject* trackObject;
};



lastfm::TrackData::TrackData()
             : trackNumber( 0 ),
               duration( 0 ),
               source( Track::UnknownSource ),
               rating( 0 ),
               fpid( -1 ),
               loved( Track::UnknownLoveStatus ),
               scrobbleStatus( Track::Null ),
               scrobbleError( Track::None ),
               null( false ),
               podcast( false ),
               video( false )
{
    trackObject = new TrackObject( *this );
}

lastfm::TrackData::~TrackData()
{
    delete trackObject;
}

lastfm::Track::Track()
    :AbstractType()
{
    d = new TrackData;
    d->null = true;
}

lastfm::Track::Track( const Track& that )
    :AbstractType(), d( that.d )
{
}

lastfm::Track::Track( const QDomElement& e )
    :AbstractType()
{
    d = new TrackData;

    if (e.isNull()) { d->null = true; return; }

    //TODO: not sure of lastfm's xml changed, but <track> nodes have
    // <artist><name>Artist Name</name><mbid>..<url></artist>
    // as children isntead of <artist>Artist Name<artist>
    // we detect both here.
    QDomNode artistName = e.namedItem( "artist" ).namedItem( "name" );
    if( artistName.isNull() ) {
          d->artist = e.namedItem( "artist" ).toElement().text();
    } else {
        d->artist = artistName.toElement().text();

    }

    //TODO: not sure if lastfm xml's changed, or if chart.getTopTracks uses
    //a different format, but the title is stored at
    //<track><name>Title</name>...
    //we detect both here.
    QDomNode trackTitle = e.namedItem( "name" );
    if( trackTitle.isNull() )
        d->title = e.namedItem( "track" ).toElement().text();
    else
        d->title = trackTitle.toElement().text();

    d->albumArtist = e.namedItem( "albumArtist" ).toElement().text();
    d->album =  Album( d->artist, e.namedItem( "album" ).toElement().text() );
    d->correctedArtist = e.namedItem( "correctedArtist" ).toElement().text();
    d->correctedAlbumArtist = e.namedItem( "correctedAlbumArtist" ).toElement().text();
    d->correctedAlbum =  Album( d->correctedArtist, e.namedItem( "correctedAlbum" ).toElement().text() );
    d->correctedTitle = e.namedItem( "correctedTrack" ).toElement().text();
    d->trackNumber = 0;
    d->duration = e.namedItem( "duration" ).toElement().text().toInt();
    d->url = e.namedItem( "url" ).toElement().text();
    d->rating = e.namedItem( "rating" ).toElement().text().toUInt();
    d->source = e.namedItem( "source" ).toElement().text().toInt(); //defaults to 0, or lastfm::Track::UnknownSource
    d->time = QDateTime::fromMSecsSinceEpoch( e.namedItem( "timestamp" ).toElement().text().toUInt() * 1000 );
    d->loved = static_cast<LoveStatus>(e.namedItem( "loved" ).toElement().text().toInt());
    d->scrobbleStatus = e.namedItem( "scrobbleStatus" ).toElement().text().toInt();
    d->scrobbleError = e.namedItem( "scrobbleError" ).toElement().text().toInt();
    d->scrobbleErrorText = e.namedItem( "scrobbleErrorText" ).toElement().text();
    d->podcast = e.namedItem( "podcast" ).toElement().text().toInt();
    d->video = e.namedItem( "video" ).toElement().text().toInt();

    for (QDomElement image = e.firstChildElement("image") ; !image.isNull() ; image = image.nextSiblingElement("image"))
        d->m_images[static_cast<ImageSize>(image.attribute("size").toInt())] = image.text();

    QDomNode artistNode = e.namedItem("artistImages");

    for (QDomElement artistImage = artistNode.firstChildElement("image") ; !artistImage.isNull() ; artistImage = artistImage.nextSiblingElement("image"))
        artist().setImageUrl( static_cast<ImageSize>(artistImage.attribute("size").toInt()), artistImage.text() );

    QDomNode albumNode = e.namedItem("albumImages");

    for (QDomElement albumImage = albumNode.firstChildElement("image") ; !albumImage.isNull() ; albumImage = albumImage.nextSiblingElement("image"))
        album().setImageUrl( static_cast<ImageSize>(albumImage.attribute("size").toInt()), albumImage.text() );


    QDomNodeList nodes = e.namedItem( "extras" ).childNodes();
    for (int i = 0; i < nodes.count(); ++i)
    {
        QDomNode n = nodes.at(i);
        QString key = n.nodeName();
        d->extras[key] = n.toElement().text();
    }
}

void
TrackObject::onLoveFinished()
{
    lastfm::XmlQuery lfm;

    if ( lfm.parse( static_cast<QNetworkReply*>(sender()) ) )
    {
        if ( lfm.attribute( "status" ) == "ok")
            m_data.loved = lastfm::Track::Loved;

    }

    emit loveToggled( m_data.loved == lastfm::Track::Loved );
}


void
TrackObject::onUnloveFinished()
{
    lastfm::XmlQuery lfm;

    if ( lfm.parse( static_cast<QNetworkReply*>(sender()) ) )
    {
        if ( lfm.attribute( "status" ) == "ok")
            m_data.loved = lastfm::Track::Unloved;
    }

    emit loveToggled( m_data.loved == lastfm::Track::Loved );
}

void
TrackObject::onGotInfo()
{
    lastfm::TrackData::Observer observer;

    for ( int i = 0 ; i < m_data.observers.count() ; ++i )
    {
        if ( m_data.observers.at( i ).reply == sender() )
        {
            observer = m_data.observers.takeAt( i );
            break;
        }
    }
    
    QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
    reply->deleteLater();
    const QByteArray data = reply->readAll();

    lastfm::XmlQuery lfm;

    if ( lfm.parse( data ) )
    {
        QString imageUrl = lfm["track"]["image size=small"].text();
        if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::SmallImage] = imageUrl;
        imageUrl = lfm["track"]["image size=medium"].text();
        if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::MediumImage] = imageUrl;
        imageUrl = lfm["track"]["image size=large"].text();
        if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::LargeImage] = imageUrl;
        imageUrl = lfm["track"]["image size=extralarge"].text();
        if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::ExtraLargeImage] = imageUrl;
        imageUrl = lfm["track"]["image size=mega"].text();
        if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::MegaImage] = imageUrl;

        if ( lfm["track"]["userloved"].text().length() > 0 )
            m_data.loved = lfm["track"]["userloved"].text() == "0" ? lastfm::Track::Unloved : lastfm::Track::Loved;

        if ( observer.receiver )
            if ( !QMetaObject::invokeMethod( observer.receiver, observer.method, Q_ARG(QByteArray, data) ) )
                QMetaObject::invokeMethod( observer.receiver, observer.method );

        emit loveToggled( m_data.loved == lastfm::Track::Loved );
    }
    else
    {
        if ( observer.receiver )
            if  ( !QMetaObject::invokeMethod( observer.receiver, observer.method, Q_ARG(QByteArray, data) ) )
                QMetaObject::invokeMethod( observer.receiver, observer.method );
    }
}

void
TrackObject::forceLoveToggled( bool love )
{
    emit loveToggled( love );
}

void
TrackObject::forceScrobbleStatusChanged()
{
    emit scrobbleStatusChanged( m_data.scrobbleStatus );
}

void
TrackObject::forceCorrected( QString correction )
{
    emit corrected( correction );
}


lastfm::Track&
lastfm::Track::operator=( const Track& that )
{
    d = that.d;
    return *this;
}

lastfm::Track::~Track()
{
}


lastfm::Track
lastfm::Track::clone() const
{
    Track clone = *this;
    clone.d.detach();
    return clone;
}


QDomElement
lastfm::Track::toDomElement( QDomDocument& xml ) const
{
    QDomElement item = xml.createElement( "track" );
    
    #define makeElement( tagname, getter ) { \
        QString v = getter; \
        if (!v.isEmpty()) \
        { \
            QDomElement e = xml.createElement( tagname ); \
            e.appendChild( xml.createTextNode( v ) ); \
            item.appendChild( e ); \
        } \
    }

    makeElement( "artist", d->artist );
    makeElement( "albumArtist", d->albumArtist );
    makeElement( "album", d->album );
    makeElement( "track", d->title );
    makeElement( "correctedArtist", d->correctedArtist );
    makeElement( "correctedAlbumArtist", d->correctedAlbumArtist );
    makeElement( "correctedAlbum", d->correctedAlbum );
    makeElement( "correctedTrack", d->correctedTitle );
    makeElement( "duration", QString::number( d->duration ) );
    makeElement( "timestamp", QString::number( d->time.toMSecsSinceEpoch() / 1000 ) );
    makeElement( "url", d->url.toString() );
    makeElement( "source", QString::number( d->source ) );
    makeElement( "rating", QString::number(d->rating) );
    makeElement( "fpId", QString::number(d->fpid) );
    makeElement( "mbId", mbid() );
    makeElement( "loved", QString::number( d->loved ) );
    makeElement( "scrobbleStatus", QString::number( scrobbleStatus() ) );
    makeElement( "scrobbleError", QString::number( scrobbleError() ) );
    makeElement( "scrobbleErrorText", scrobbleErrorText() );
    makeElement( "podcast", QString::number( isPodcast() ) );
    makeElement( "video", QString::number( isVideo() ) );

    // put the images urls in the dom
    QMapIterator<ImageSize, QUrl> imageIter( d->m_images );
    while (imageIter.hasNext()) {
        QDomElement e = xml.createElement( "image" );
        e.appendChild( xml.createTextNode( imageIter.next().value().toString() ) );
        e.setAttribute( "size", imageIter.key() );
        item.appendChild( e );
    }

    QDomElement artistElement = xml.createElement( "artistImages" );

    for ( int size = SmallImage ; size <= MegaImage ; ++size )
    {
        QString imageUrl = d->artist.imageUrl( static_cast<ImageSize>(size) ).toString();

        if ( !imageUrl.isEmpty() )
        {
            QDomElement e = xml.createElement( "image" );
            e.appendChild( xml.createTextNode( d->artist.imageUrl( static_cast<ImageSize>(size) ).toString() ) );
            e.setAttribute( "size", size );
            artistElement.appendChild( e );
        }
    }

    if ( artistElement.childNodes().count() != 0 )
        item.appendChild( artistElement );

    QDomElement albumElement = xml.createElement( "albumImages" );

    for ( int size = SmallImage ; size <= MegaImage ; ++size )
    {
        QString imageUrl = d->album.imageUrl( static_cast<ImageSize>(size) ).toString();

        if ( !imageUrl.isEmpty() )
        {
            QDomElement e = xml.createElement( "image" );
            e.appendChild( xml.createTextNode( d->album.imageUrl( static_cast<ImageSize>(size) ).toString() ) );
            e.setAttribute( "size", size );
            albumElement.appendChild( e );
        }
    }

    if ( albumElement.childNodes().count() != 0 )
        item.appendChild( albumElement );

    // add the extras to the dom
    QDomElement extras = xml.createElement( "extras" );
    QMapIterator<QString, QString> extrasIter( d->extras );
    while (extrasIter.hasNext()) {
        QDomElement e = xml.createElement( extrasIter.next().key() );
        e.appendChild( xml.createTextNode( extrasIter.value() ) );
        extras.appendChild( e );
    }
    item.appendChild( extras );

    return item;
}


bool
lastfm::Track::corrected() const
{
    // If any of the corrected string have been set and they are different
    // from the initial strings then this track has been corrected.
    return ( (!d->correctedTitle.isEmpty() && (d->correctedTitle != d->title))
            || (!d->correctedAlbum.toString().isEmpty() && (d->correctedAlbum.toString() != d->album.toString()))
            || (!d->correctedArtist.isNull() && (d->correctedArtist.name() != d->artist.name()))
            || (!d->correctedAlbumArtist.isNull() && (d->correctedAlbumArtist.name() != d->albumArtist.name())));
}

lastfm::Artist
lastfm::Track::artist( Corrections corrected ) const
{
    if ( corrected == Corrected && !d->correctedArtist.name().isEmpty() )
        return d->correctedArtist;

    return d->artist;
}

lastfm::Artist
lastfm::Track::albumArtist( Corrections corrected ) const
{
    if ( corrected == Corrected && !d->correctedAlbumArtist.name().isEmpty() )
        return d->correctedAlbumArtist;

    return d->albumArtist;
}

lastfm::Album
lastfm::Track::album( Corrections corrected ) const
{
    if ( corrected == Corrected && !d->correctedAlbum.title().isEmpty() )
        return d->correctedAlbum;

    return d->album;
}

QString
lastfm::Track::title( Corrections corrected ) const
{
    /** if no title is set, return the musicbrainz unknown identifier
      * in case some part of the GUI tries to display it anyway. Note isNull
      * returns false still. So you should have queried this! */

    if ( corrected == Corrected && !d->correctedTitle.isEmpty() )
        return d->correctedTitle;

    return d->title;
}


QUrl
lastfm::Track::imageUrl( ImageSize size, bool square ) const
{
    if( !square ) return d->m_images.value( size );

    QUrl url = d->m_images.value( size );
    QRegularExpression re( "/serve/(\\d*)s?/" );
    return QUrl( url.toString().replace( re, "/serve/\\1s/" ));
}


QString
lastfm::Track::toString( const QChar& separator, Corrections corrections ) const
{
    if ( d->artist.name().isEmpty() )
    {
        if ( d->title.isEmpty() )
            return QFileInfo( d->url.path() ).fileName();
        else
            return title( corrections );
    }

    if ( d->title.isEmpty() )
        return artist( corrections );

    return artist( corrections ) + ' ' + separator + ' ' + title( corrections );
}


QString //static
lastfm::Track::durationString( int const duration )
{
    QTime t = QTime().addSecs( duration );
    if (duration < 60*60)
        return t.toString( "m:ss" );
    else
        return t.toString( "hh:mm:ss" );
}


QNetworkReply*
lastfm::Track::share( const QStringList& recipients, const QString& message, bool isPublic ) const
{
    QMap<QString, QString> map = params("share");
    map["recipient"] = recipients.join(",");
    map["public"] = isPublic ? "1" : "0";
    if (message.size()) map["message"] = message;
    return ws::post(map);
}

void
lastfm::MutableTrack::setFromLfm( const XmlQuery& lfm )
{
    QString imageUrl = lfm["track"]["image size=small"].text();
    if ( !imageUrl.isEmpty() ) d->m_images[SmallImage] = imageUrl;
    imageUrl = lfm["track"]["image size=medium"].text();
    if ( !imageUrl.isEmpty() ) d->m_images[MediumImage] = imageUrl;
    imageUrl = lfm["track"]["image size=large"].text();
    if ( !imageUrl.isEmpty() ) d->m_images[LargeImage] = imageUrl;
    imageUrl = lfm["track"]["image size=extralarge"].text();
    if ( !imageUrl.isEmpty() ) d->m_images[ExtraLargeImage] = imageUrl;
    imageUrl = lfm["track"]["image size=mega"].text();
    if ( !imageUrl.isEmpty() ) d->m_images[MegaImage] = imageUrl;

    if ( lfm["track"]["userloved"].text().length() > 0)
        d->loved = lfm["track"]["userloved"].text() == "0" ? Unloved : Loved;

    d->trackObject->forceLoveToggled( d->loved == Loved );
}

void
lastfm::MutableTrack::setImageUrl( ImageSize size, const QString& url )
{
    if ( !url.isEmpty() )
        d->m_images[size] = url;
}


void
lastfm::MutableTrack::love()
{
    QNetworkReply* reply = ws::post(params("love"));
    QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onLoveFinished()));
}


void
lastfm::MutableTrack::unlove()
{
    QNetworkReply* reply = ws::post(params("unlove"));
    QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onUnloveFinished()));
}

QNetworkReply*
lastfm::MutableTrack::ban()
{
    d->extras["rating"] = "B";
    return ws::post(params("ban"));
}


QMap<QString, QString>
lastfm::Track::params( const QString& method, bool use_mbid ) const
{
    QMap<QString, QString> map;
    map["method"] = "Track."+method;
    if (d->mbid.size() && use_mbid)
        map["mbid"] = d->mbid;
    else {
        map["artist"] = d->artist;
        map["track"] = d->title;
    }
    return map;
}


QNetworkReply* 
lastfm::Track::getSimilar( int limit ) const
{
    QMap<QString, QString> map = params("getSimilar");
    if ( limit != -1 ) map["limit"] = QString::number( limit );
    map["autocorrect"] = "1";
    return ws::get( map );
}


QMap<int, QPair< QString, QString > > /* static */
lastfm::Track::getSimilar( QNetworkReply* r )
{
    QMap<int, QPair< QString, QString > > tracks;
    try
    {
        XmlQuery lfm;
        
        if ( lfm.parse( r ) )
        {
            foreach (XmlQuery e, lfm.children( "track" ))
            {
                QPair< QString, QString > track;
                track.first = e["name"].text();

                XmlQuery artist = e.children( "artist" ).first();
                track.second = artist["name"].text();

                // convert floating percentage to int in range 0 to 10,000
                int const match = e["match"].text().toFloat() * 100;
                tracks.insert( match, track ); // FIXME values with the same key will be overwritten, switch to QMultiMap
            }
        }
    }
    catch (ws::ParseError& e)
    {
        qWarning() << e.message();
    }
    
    return tracks;
}


QNetworkReply*
lastfm::Track::getTopTags() const
{
    return ws::get( params("getTopTags", true) );
}


QNetworkReply*
lastfm::Track::getTopFans() const
{
    return ws::get( params("getTopFans", true) );
}


QNetworkReply*
lastfm::Track::getTags() const
{
    return ws::get( params("getTags", true) );
}

void
lastfm::Track::getInfo( QObject *receiver, const char *method, const QString &username ) const
{
    QMap<QString, QString> map = params("getInfo", true);
    if (!username.isEmpty()) map["username"] = username;

    // this is so the web services knows whether to use corrections or not
    if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey;

    QNetworkReply* reply = ws::get( map );

    TrackData::Observer observer;
    observer.receiver = receiver;
    observer.method = method;
    observer.reply = reply;
    d->observers << observer;

    QObject::connect( reply, SIGNAL(finished()), d->trackObject, SLOT(onGotInfo()));
}


QNetworkReply*
lastfm::Track::getBuyLinks( const QString& country ) const
{
    QMap<QString, QString> map = params( "getBuyLinks", true );
    map["country"] = country;
    return ws::get( map );
}

QNetworkReply*
lastfm::Track::playlinks( const QList<Track>& tracks )
{
    QMap<QString, QString> map;

    map["method"] = "Track.playlinks";

    for ( int i = 0 ; i < tracks.count() ; ++i )
    {
        if ( tracks[i].d->mbid.size())
            map["mbid[" + QString::number( i ) + "]"] = tracks[i].d->mbid;
        else
        {
            map["artist[" + QString::number( i ) + "]"] = tracks[i].d->artist;
            map["track[" + QString::number( i ) + "]"] = tracks[i].d->title;
        }
    }

    return ws::get( map );
}


QNetworkReply*
lastfm::Track::addTags( const QStringList& tags ) const
{
    if (tags.isEmpty())
        return 0;
    QMap<QString, QString> map = params("addTags");
    map["tags"] = tags.join( QChar(',') );
    return ws::post(map);
}


QNetworkReply*
lastfm::Track::removeTag( const QString& tag ) const
{
    if (tag.isEmpty())
        return 0;
    QMap<QString, QString> map = params( "removeTag" );
    map["tag"] = tag;
    return ws::post(map);
}


QNetworkReply*
lastfm::Track::updateNowPlaying() const
{
    return updateNowPlaying(duration());
}

QNetworkReply* 
lastfm::Track::updateNowPlaying( int duration ) const
{
    QMap<QString, QString> map = params("updateNowPlaying");
    map["duration"] = QString::number( duration );
    map["albumArtist"] = d->albumArtist;
    if ( !album().isNull() ) map["album"] = album();
    map["context"] = extra("playerId");

    return ws::post(map);
}

QNetworkReply* 
lastfm::Track::removeNowPlaying() const
{
    QMap<QString, QString> map;
    map["method"] = "track.removeNowPlaying";

    return ws::post(map);
}


QNetworkReply*
lastfm::Track::scrobble() const
{
    QMap<QString, QString> map = params("scrobble");
    map["duration"] = QString::number( d->duration );
    map["timestamp"] = QString::number( d->time.toMSecsSinceEpoch() / 1000 );
    map["context"] = extra("playerId");
    map["albumArtist"] = d->albumArtist;
    if ( !d->album.title().isEmpty() ) map["album"] = d->album.title();
    map["chosenByUser"] = source() == Track::LastFmRadio ? "0" : "1";

    return ws::post(map);
}

QNetworkReply*
lastfm::Track::scrobble(const QList<lastfm::Track>& tracks)
{
    QMap<QString, QString> map;
    map["method"] = "track.scrobble";

    for ( int i(0) ; i < tracks.count() ; ++i )
    {
        map["duration[" + QString::number(i) + "]"] = QString::number( tracks[i].duration() );
        map["timestamp[" + QString::number(i)  + "]"] = QString::number( tracks[i].timestamp().toMSecsSinceEpoch() / 1000 );
        map["track[" + QString::number(i)  + "]"] = tracks[i].title();
        map["context[" + QString::number(i)  + "]"] = tracks[i].extra("playerId");
        if ( !tracks[i].album().isNull() ) map["album[" + QString::number(i)  + "]"] = tracks[i].album();
        map["artist[" + QString::number(i) + "]"] = tracks[i].artist();
        map["albumArtist[" + QString::number(i) + "]"] = tracks[i].albumArtist();
        if ( !tracks[i].mbid().isNull() ) map["mbid[" + QString::number(i)  + "]"] = tracks[i].mbid();
        map["chosenByUser[" + QString::number(i) + "]"] = tracks[i].source() == Track::LastFmRadio ? "0" : "1";
    }

    return ws::post(map);
}


QUrl
lastfm::Track::www() const
{
    return UrlBuilder( "music" ).slash( artist( Corrected ) ).slash( album(  Corrected  ).isNull() ? QString("_") : album( Corrected )).slash( title( Corrected ) ).url();
}


bool
lastfm::Track::isMp3() const
{
    //FIXME really we should check the file header?
    return d->url.scheme() == "file" &&
           d->url.path().endsWith( ".mp3", Qt::CaseInsensitive );
}

bool
lastfm::Track::sameObject( const Track& that )
{
    return (this->d == that.d);
}

bool
lastfm::Track::operator==( const Track& that ) const
{
    return ( title( Corrected ) == that.title( Corrected )
             // if either album is empty, assume they are the same album
             && ( album( Corrected ).title().isEmpty() || that.album( Corrected ).title().isEmpty() || album( Corrected ) == that.album( Corrected ))
             && artist( Corrected ) == that.artist( Corrected ));
}

bool
lastfm::Track::operator!=( const Track& that ) const
{
    return !operator==( that );
}

const QObject*
lastfm::Track::signalProxy() const
{
    return d->trackObject;
}

bool
lastfm::Track::isNull() const
{
    return d->null;
}

uint
lastfm::Track::trackNumber() const
{ return d->trackNumber; }
uint
lastfm::Track::duration() const
{
    // in seconds
    return d->duration;
}

lastfm::Mbid
lastfm::Track::mbid() const
{
    return lastfm::Mbid(d->mbid); }
QUrl
lastfm::Track::url() const
{
    return d->url; }
QDateTime
lastfm::Track::timestamp() const
{
    return d->time;
}

lastfm::Track::Source
lastfm::Track::source() const
{
    return static_cast<Source>(d->source);
}

uint
lastfm::Track::fingerprintId() const
{
    return d->fpid;
}

bool
lastfm::Track::isLoved() const
{
    return d->loved == Loved;
}

lastfm::Track::LoveStatus
lastfm::Track::loveStatus() const
{
    return d->loved;
}


QString
lastfm::Track::durationString() const
{
    return durationString( d->duration );
}


lastfm::Track::ScrobbleStatus
lastfm::Track::scrobbleStatus() const
{
    return static_cast<ScrobbleStatus>(d->scrobbleStatus);
}

lastfm::Track::ScrobbleError
lastfm::Track::scrobbleError() const
{
    return static_cast<ScrobbleError>(d->scrobbleError);
}
QString
lastfm::Track::scrobbleErrorText() const
{
    return d->scrobbleErrorText;
}

/** default separator is an en-dash */
QString
lastfm::Track::toString() const
{
    return toString( Corrected );
}

QString
lastfm::Track::toString( Corrections corrections ) const
{
    return toString( QChar(8211), corrections );
}

lastfm::TrackContext
lastfm::Track::context() const
{
    return d->context;
}

// iTunes tracks might be podcasts or videos
bool
lastfm::Track::isPodcast() const
{
    return d->podcast;
}

bool
lastfm::Track::isVideo() const
{
    return d->video;
}

QString
lastfm::Track::extra( const QString& key ) const
{
    return d->extras[ key ];
}

bool lastfm::Track::operator<( const Track &that ) const
{
    return this->d->time < that.d->time;
}

lastfm::Track::operator QVariant() const
{
    return QVariant::fromValue( *this );
}

void
lastfm::MutableTrack::setCorrections( QString title, QString album, QString artist, QString albumArtist )
{
    d->correctedTitle = title;
    d->correctedArtist = artist;
    d->correctedAlbum = Album( artist, album );
    d->correctedAlbumArtist = albumArtist;

    d->trackObject->forceCorrected( toString() );
}

lastfm::MutableTrack::MutableTrack()
{
    d->null = false;
}


lastfm::MutableTrack::MutableTrack( const Track& that )
    : Track( that )
{
    d->null = false;
}

void
lastfm::MutableTrack::setArtist( QString artist )
{
    d->artist.setName( artist.trimmed() );
    d->album.setArtist( artist.trimmed() );
    d->correctedAlbum.setArtist( artist.trimmed() );
}

void
lastfm::MutableTrack::setAlbumArtist( QString albumArtist )
{
    d->albumArtist.setName( albumArtist.trimmed() );
}

void
lastfm::MutableTrack::setAlbum( QString album )
{
    d->album = Album( d->artist.name(), album.trimmed() );
}

void
lastfm::MutableTrack::setTitle( QString title )
{
    d->title = title.trimmed();
}

void
lastfm::MutableTrack::setTrackNumber( uint n )
{
    d->trackNumber = n;
}

void
lastfm::MutableTrack::setDuration( uint duration )
{
    d->duration = duration;
}

void
lastfm::MutableTrack::setUrl( QUrl url )
{
    d->url = url;
}

void
lastfm::MutableTrack::setSource( Source s )
{
    d->source = s;
}

void
lastfm::MutableTrack::setLoved( bool loved )
{
    d->loved = loved ? Loved : Unloved;
}

void
lastfm::MutableTrack::setMbid( Mbid id )
{
    d->mbid = id;
}

void
lastfm::MutableTrack::setFingerprintId( uint id )
{
    d->fpid = id;
}

void
lastfm::MutableTrack::setScrobbleStatus( ScrobbleStatus scrobbleStatus )
{
    if ( scrobbleStatus != d->scrobbleStatus )
    {
        d->scrobbleStatus = scrobbleStatus;
        d->trackObject->forceScrobbleStatusChanged();
    }
}

void
lastfm::MutableTrack::setScrobbleError( ScrobbleError scrobbleError )
{
    d->scrobbleError = scrobbleError;
}

void
lastfm::MutableTrack::setScrobbleErrorText( const QString& scrobbleErrorText )
{
    d->scrobbleErrorText = scrobbleErrorText;
}

void
lastfm::MutableTrack::stamp()
{
    d->time = QDateTime::currentDateTime();
}

void
lastfm::MutableTrack::setExtra( const QString& key, const QString& value )
{
    d->extras[key] = value;
}

void
lastfm::MutableTrack::removeExtra( QString key )
{
    d->extras.remove( key );
}

void
lastfm::MutableTrack::setTimeStamp( const QDateTime& dt )
{
    d->time = dt;
}

void
lastfm::MutableTrack::setContext( TrackContext context )
{
    d->context = context;
}

// iTunes tracks might be podcasts or videos
void
lastfm::MutableTrack::setPodcast( bool podcast )
{
    d->podcast = podcast;
}
void
lastfm::MutableTrack::setVideo( bool video )
{
    d->video = video;
}

QDebug
operator<<( QDebug d, const lastfm::Track& t )
{
    return !t.isNull()
            ? d << t.toString( '-' ) << t.url()
            : d << "Null Track object";
}

#include "Track.moc"
07070100000035000081A400000000000000000000000167B98EA80000260E000000000000000000000000000000000000003B00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Track.h/*
   Copyright 2009-2010 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_TRACK_H
#define LASTFM_TRACK_H

#include <QDateTime>
#include <QExplicitlySharedDataPointer>
#include <QPointer>

#include "Album.h"

namespace lastfm {

class TrackData;

class LASTFM_DLLEXPORT TrackContext
{
public:
    enum Type
    {
        UnknownType,
        User,
        Friend,
        Neighbour,
        Artist
    };

    TrackContext();
    TrackContext( const QString& type, const QList<QString>& values );
    TrackContext( const TrackContext& that );
    ~TrackContext();

    Type type() const;
    QList<QString> values() const;
    TrackContext& operator=( const TrackContext& that );

private:
    class TrackContextPrivate * const d;
};


/** Our track type. It's quite good, you may want to use it as your track type
  * in general. It is explicitly shared. Which means when you make a copy, they
  * both point to the same data still. This is like Qt's implicitly shared
  * classes, eg. QString, however if you mod a copy of a QString, the copy
  * detaches first, so then you have two copies. Our Track object doesn't
  * detach, which is very handy for our usage in the client, but perhaps not
  * what you want. If you need a deep copy for eg. work in a thread, call 
  * clone(). */
class LASTFM_DLLEXPORT Track : public AbstractType
{
public:
    enum Source
    {
        // DO NOT UNDER ANY CIRCUMSTANCES CHANGE THE ORDER OR VALUES OF THIS ENUM!
        // you will cause broken settings and b0rked scrobbler cache submissions

        UnknownSource = 0,
        LastFmRadio,
        Player,
        MediaDevice,
        NonPersonalisedBroadcast, // eg Shoutcast, BBC Radio 1, etc.
        PersonalisedRecommendation // eg Pandora, but not Last.fm
    };

    enum LoveStatus
    {
        UnknownLoveStatus = 0,
        Loved,
        Unloved
    };

    enum ScrobbleStatus
    {
        Null = 0,
        Cached,
        Submitted,
        Error
    };

    enum Corrections
    {
        Original = 0,
        Corrected
    };

    enum ScrobbleError
    {
        None = 0,
        FilteredArtistName = 113,
        FilteredTrackName = 114,
        FilteredAlbumName = 115,
        FilteredTimestamp = 116,
        ExceededMaxDailyScrobbles = 118,
        InvalidStreamAuth = 119,
        Invalid = 300
    };

    Track();
    explicit Track( const QDomElement& );
    Track( const Track& that );
    ~Track();

    Track clone() const;

    /** this track and that track point to the same object, so they are the same
      * in fact. This doesn't do a deep data comparison. So even if all the 
      * fields are the same it will return false if they aren't in fact spawned
      * from the same initial Track object */
    bool sameObject( const Track& that );
    bool operator==( const Track& that ) const;
    bool operator!=( const Track& that ) const;
    Track& operator=( const Track& that );

    /**
     * Track's private class emits three signals that may be useful for
     * applications:
     *
     *   loveToggled( bool love )
     *   scrobbleStatusChanged( short scrobbleStatus )
     *   corrected( QString correction )
     *
     * signalProxy() lets applications connect to them.
     * */
    const QObject* signalProxy() const;

    /** only a Track() is null */
    bool isNull() const;

    bool corrected() const;

    Artist artist( Corrections corrected = Original ) const;
    Artist albumArtist( Corrections corrected = Original ) const;
    Album album( Corrections corrected = Original ) const;
    QString title( Corrections corrected = Original ) const;

    uint trackNumber() const;
    uint duration() const; // in seconds
    Mbid mbid() const;
    QUrl url() const;
    QDateTime timestamp() const;
    Source source() const;
    uint fingerprintId() const;
    bool isLoved() const;
    LoveStatus loveStatus() const;
    QUrl imageUrl( ImageSize size, bool square ) const;

    QString durationString() const;
    static QString durationString( int seconds );

    ScrobbleStatus scrobbleStatus() const;
    ScrobbleError scrobbleError() const;
    QString scrobbleErrorText() const;

    /** default separator is an en-dash */
    QString toString() const;
    QString toString( Corrections corrections ) const;
    QString toString( const QChar& separator, Corrections corrections = Original ) const;
    /** the standard representation of this object as an XML node */
    QDomElement toDomElement( class QDomDocument& ) const;

    TrackContext context() const;

    // iTunes tracks might be podcasts or videos
    bool isPodcast() const;
    bool isVideo() const;
    
    QString extra( const QString& key ) const;

    bool operator<( const Track &that ) const;
    
    bool isMp3() const;
    
    operator QVariant() const;
    
//////////// lastfm::Ws
    
    /** See last.fm/api Track section */
    QNetworkReply* share( const QStringList& recipients, const QString& message = QStringLiteral(""), bool isPublic = true ) const;

    QNetworkReply* getSimilar( int limit = -1 ) const;
    /** The match percentage is returned from last.fm as a 4 significant
      * figure floating point value. So we multply it by 100 to make an
      * integer in the range of 0 to 10,000. This is possible confusing
      * for you, but I felt it best not to lose any precision, and floats
      * aren't much fun. */
    static QMap<int, QPair< QString, QString > > getSimilar( QNetworkReply* );

    /** you can get any QNetworkReply TagList using Tag::list( QNetworkReply* ) */
    QNetworkReply* getTags() const; // for the logged in user
    QNetworkReply* getTopTags() const;
    QNetworkReply* getTopFans() const;

    /** method should be a method name of reciever that takes a QByteArray
    If that fails it will try invoking method with no arguments.
    */
    void getInfo( QObject* receiver, const char * method, const QString& username = QStringLiteral("") ) const;
    QNetworkReply* getBuyLinks( const QString& country ) const;

    static QNetworkReply* playlinks( const QList<Track>& tracks );

    /** you can only add 10 tags, we submit everything you give us, but the
      * docs state 10 only. Will return 0 if the list is empty. */
    QNetworkReply* addTags( const QStringList& ) const;
    /** will return 0 if the string is "" */
    QNetworkReply* removeTag( const QString& ) const;

    /** scrobble the track */
    QNetworkReply* updateNowPlaying() const;
    QNetworkReply* updateNowPlaying( int duration ) const;
    QNetworkReply* removeNowPlaying() const;
    QNetworkReply* scrobble() const;
    static QNetworkReply* scrobble(const QList<lastfm::Track>& tracks);

    /** the url for this track's page at last.fm */
    QUrl www() const;

protected:
    QExplicitlySharedDataPointer<TrackData> d;
    QMap<QString, QString> params( const QString& method, bool use_mbid = false ) const;
};



/** This class allows you to change Track objects, it is easy to use:
  * MutableTrack( some_track_object ).setTitle( "Arse" );
  *
  * We have a separate MutableTrack class because in our usage, tracks
  * only get mutated once, and then after that, very rarely. This pattern
  * encourages such usage, which is generally sensible. You can feel more
  * comfortable that the data hasn't accidently changed behind your back.
  */
class LASTFM_DLLEXPORT MutableTrack : public Track
{
public:
    MutableTrack();

    /** NOTE that passing a Track() to this ctor will automatically make it non
      * null. Which may not be what you want. So be careful
      * Rationale: this is the most maintainable way to do it 
      */
    MutableTrack( const Track& that );

    void setFromLfm( const XmlQuery& lfm );
    void setImageUrl( ImageSize size, const QString& url );
    
    void setArtist( QString artist );
    void setAlbumArtist( QString albumArtist );
    void setAlbum( QString album );
    void setTitle( QString title );
    void setCorrections( QString title, QString album, QString artist, QString albumArtist );
    void setTrackNumber( uint n );
    void setDuration( uint duration );
    void setUrl( QUrl url );
    void setSource( Source s );
    void setLoved( bool loved );
    
    void setMbid( Mbid id );
    void setFingerprintId( uint id );

    void setScrobbleStatus( ScrobbleStatus scrobbleStatus );
    void setScrobbleError( ScrobbleError scrobbleError );
    void setScrobbleErrorText( const QString& scrobbleErrorText );
    
    void love();
    void unlove();
    QNetworkReply* ban();
    
    void stamp();

    void setExtra( const QString& key, const QString& value );
    void removeExtra( QString key );
    void setTimeStamp( const QDateTime& dt );

    void setContext( TrackContext context );

    // iTunes tracks might be podcasts or videos
    void setPodcast( bool podcast );
    void setVideo( bool video );

};


} //namespace lastfm


LASTFM_DLLEXPORT QDebug operator<<( QDebug d, const lastfm::Track& t );

Q_DECLARE_METATYPE( lastfm::Track )

#endif //LASTFM_TRACK_H
07070100000036000081A400000000000000000000000167B98EA800000610000000000000000000000000000000000000003B00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Url.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Url.h"

#include <QUrlQuery>

class lastfm::UrlPrivate
{
    public:
        UrlPrivate( const QUrl& url );
        QUrl url;
        QUrlQuery query;
};

lastfm::UrlPrivate::UrlPrivate( const QUrl& u )
    : url( u )
    , query( u.query() )
{
}

lastfm::Url::Url( const QUrl& url )
    :d( new UrlPrivate( url ) )
{
}

lastfm::Url::~Url()
{
    delete d;
}

void
lastfm::Url::addQueryItem( const QString& key, const QString& value )
{
    d->query.addQueryItem( key, value );
    d->url.setQuery( d->query );
}

QUrl
lastfm::Url::operator()()
{
    return url();
}

lastfm::Url&
lastfm::Url::operator=( const lastfm::Url& that )
{
    d->url = that.d->url;
    d->query = that.d->query;
    return *this;
}


QUrl
lastfm::Url::url() const
{
    return d->url;
}
07070100000037000081A400000000000000000000000167B98EA8000004B5000000000000000000000000000000000000003900000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Url.h/*
   Copyright 2009-2010 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once

#include "global.h"

#include <QUrl>


namespace lastfm
{
    class LASTFM_DLLEXPORT Url
    {
    public:
        explicit Url( const QUrl& url );
        ~Url();

        void addQueryItem( const QString& key, const QString& value );

        QUrl url() const;

        QUrl operator()();
        Url& operator=( const Url& that );

    private:
        class UrlPrivate * const d;
    };
}

07070100000038000081A400000000000000000000000167B98EA800000D3D000000000000000000000000000000000000004200000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/UrlBuilder.cpp/*
   Copyright 2009 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "UrlBuilder.h"
#include <QRegularExpression>
#include <QStringList>


class lastfm::UrlBuilderPrivate
{
public:
    QByteArray path;
};


lastfm::UrlBuilder::UrlBuilder( const QString& base )
    : d( new UrlBuilderPrivate )
{
    d->path = '/' + base.toLatin1();
}


lastfm::UrlBuilder::UrlBuilder( const UrlBuilder& that )
    : d( new UrlBuilderPrivate( *that.d ) )
{
}


lastfm::UrlBuilder::~UrlBuilder()
{
    delete d;
}


lastfm::UrlBuilder&
lastfm::UrlBuilder::slash( const QString& path )
{
    this->d->path += '/' + encode( path );
    return *this;
}


QUrl
lastfm::UrlBuilder::url() const
{
    QUrl url( "https://" + host() );
    url.setPath( url.path() + d->path );
    return url;
}


QByteArray //static
lastfm::UrlBuilder::encode( QString s )
{
    foreach (QChar c, QList<QChar>() << '%' << '&' << '/' << ';' << '+' << '#' << '"')
        if (s.contains( c ))
            // the middle step may seem odd but this is what the site does
            // eg. search for the exact string "Radiohead 2 + 2 = 5"
            return QUrl::toPercentEncoding( s ).replace( "%20", "+" );

    return QUrl::toPercentEncoding( s.replace( ' ', '+' ), "+" );
}


QString //static
lastfm::UrlBuilder::host( const QLocale& locale )
{
    switch (locale.language())
    {
        case QLocale::Portuguese: return "www.last.fm/pt";
        case QLocale::Turkish:    return "www.last.fm/tr";
        case QLocale::French:     return "www.last.fm/fr";
        case QLocale::Italian:    return "www.last.fm/it";
        case QLocale::German:     return "www.last.fm/de";
        case QLocale::Spanish:    return "www.last.fm/es";
        case QLocale::Polish:     return "www.last.fm/pl";
        case QLocale::Russian:    return "www.last.fm/ru";
        case QLocale::Japanese:   return "www.last.fm/jp";
        case QLocale::Swedish:    return "www.last.fm/se";
        case QLocale::Chinese:    return "www.last.fm/zh";
        default:                  return "www.last.fm";
    }
}


bool // static
lastfm::UrlBuilder::isHost( const QUrl& url )
{
    QStringList hosts = QStringList() << "www.last.fm";

    return hosts.contains( url.host() );
}

QUrl //static
lastfm::UrlBuilder::localize( QUrl url)
{
    url.setHost( url.host().replace( QRegularExpression("^(www.)?last.fm"), host() ) );
    return url;
}


QUrl //static
lastfm::UrlBuilder::mobilize( QUrl url )
{
    url.setHost( url.host().replace( QRegularExpression("^(www.)?last"), "m.last" ) );
    return url;
}

lastfm::UrlBuilder&
lastfm::UrlBuilder::operator=( const UrlBuilder& that )
{
    d->path = that.d->path;
    return *this;
}
07070100000039000081A400000000000000000000000167B98EA8000009BF000000000000000000000000000000000000004000000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/UrlBuilder.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_URL_BUILDER_H
#define LASTFM_URL_BUILDER_H

#include "global.h"
#include <QLocale>
#include <QUrl>
  

namespace lastfm
{
    /** For building www.last.fm urls. We have special rules for encoding and that */
    class LASTFM_DLLEXPORT UrlBuilder
    {
    public:
        /** Careful, the base is not encoded at all, we assume it is ASCII!
          * If you need it encoded at all you must use the slash function.
          * eg. UrlBuilder( "user" ).slash( "mxcl" ) ==> http://last.fm/user/mxcl
          */
        UrlBuilder( const QString& base );

        UrlBuilder& slash( const QString& path );

        UrlBuilder( const UrlBuilder& that );
        ~UrlBuilder();

        QUrl url() const;

        /** www.last.fm becomes the local version, eg www.lastfm.de */
        static QUrl localize( QUrl );
        /** www.last.fm becomes m.last.fm, localisation is preserved */
        static QUrl mobilize( QUrl );

        /** Use this to URL encode any database item (artist, track, album). It
          * internally calls UrlEncodeSpecialChars to double encode some special
          * symbols according to the same pattern as that used on the website.
          *
          * &, /, ;, +, #
          *
          * Use for any urls that go to www.last.fm
          * Do not use for ws.audioscrobbler.com
          */
        static QByteArray encode( QString );

        /** returns eg. www.lastfm.de */
        static QString host( const QLocale& = QLocale() );

        /** return true if url is a last.fm url */
        static bool isHost( const QUrl& url );

        UrlBuilder& operator=( const UrlBuilder& that );

    private:
        class UrlBuilderPrivate * const d;
    };
}

#endif
0707010000003A000081A400000000000000000000000167B98EA8000031AF000000000000000000000000000000000000003C00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/User.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "User.h"
#include "Track.h"
#include "Artist.h"
#include "UrlBuilder.h"
#include "XmlQuery.h"

#include <QStringList>
#include <QAbstractNetworkCache>
#include <QRegularExpression>
#include <QDebug>

using lastfm::Gender;
using lastfm::User;
using lastfm::UserList;
using lastfm::XmlQuery;


class lastfm::UserListPrivate
{
public:
    UserListPrivate()
        : total( 0 ), page( 0 ), perPage( 0 ), totalPages( 0 )
    {}
    int total;
    int page;
    int perPage;
    int totalPages;
    QList<lastfm::User> users;
};

lastfm::UserList::UserList()
    : d( new UserListPrivate )
{
}

lastfm::UserList::UserList( const XmlQuery &lfm )
    : d( new UserListPrivate )
{
    if ( lfm.parseError().enumValue() == lastfm::ws::NoError )
    {
        foreach (XmlQuery e, lfm.children( "user" ))
        {
            User u( e );
            d->users.append( u );
        }

        d->total = lfm["friends"].attribute("total").toInt();
        d->page = lfm["friends"].attribute("page").toInt();
        d->perPage = lfm["friends"].attribute("perPage").toInt();
        d->totalPages = lfm["friends"].attribute("totalPages").toInt();
    }
    else
    {
        qDebug() << lfm.parseError().message() << lfm.parseError().enumValue();
    }
}

lastfm::UserList::~UserList()
{
    delete d;
}

lastfm::UserList::UserList( const UserList& other )
    : d( new UserListPrivate( *other.d ) )
{
}

QList<lastfm::User>
lastfm::UserList::users()
{
    return d->users;
}

lastfm::UserList&
lastfm::UserList::operator=( const UserList& other )
{
    d->total = other.d->total;
    d->page = other.d->page;
    d->perPage = other.d->perPage;
    d->totalPages = other.d->totalPages;
    d->users = other.d->users;
    return *this;
}

int
lastfm::UserList::totalUsers()
{
    return d->total;
}

int
lastfm::UserList::currentPage()
{
    return d->page;
}

int
lastfm::UserList::usersPerPage()
{
    return d->perPage;
}

int
lastfm::UserList::totalPages()
{
    return d->totalPages;
}


class lastfm::Gender::GenderPrivate
{
    public:
        QString s;
};


Gender::Gender()
    :d( new GenderPrivate )
{
}

Gender::Gender( const Gender& other )
    :d( new GenderPrivate( *other.d ) )
{
}


Gender::Gender( const QString& ss )
    :d( new GenderPrivate )
{
    d->s = ss.toLower();
}


Gender::~Gender()
{
    delete d;
}


bool
Gender::known() const
{
    return male() || female();
}


bool
Gender::male() const
{
    return d->s == "m";
}


bool
Gender::female() const
{
    return d->s == "f";
}

QString Gender::toString() const
{
    QString result;

    if (male())
        result = "Male";
    else if (female())
        result = "Female";
    else
        result = "Neuter";

    return result;
}


Gender&
Gender::operator=( const lastfm::Gender& that )
{
    d->s = that.d->s;
    return *this;
}



class lastfm::User::UserPrivate
{
    public:
        UserPrivate()
            : m_name( lastfm::ws::Username )
            , m_match( -1.0f )
            , m_age( 0 )
            , m_scrobbles( 0 )
            , m_registered( QDateTime() )
            , m_isSubscriber( false )
            , m_canBootstrap( false )
        {}
        QString m_name;
        Type m_type;
        QList<QUrl> m_images;
        float m_match;
        QString m_realName;
        Gender m_gender;
        unsigned short m_age;
        unsigned int m_scrobbles;
        QDateTime m_registered;
        QString m_country;
        bool m_isSubscriber;
        bool m_canBootstrap;
};



User::User( const XmlQuery& xml ) 
     :AbstractType(),
     d( new UserPrivate )
{
    d->m_name = xml["name"].text();
    d->m_images << xml["image size=small"].text()
             << xml["image size=medium"].text()
             << xml["image size=large"].text();
    d->m_realName = xml["realname"].text();

    QString type = xml["type"].text();

    if ( type == "subscriber" ) d->m_type = TypeSubscriber;
    else if ( type == "moderator" ) d->m_type = TypeModerator;
    else if ( type == "staff" ) d->m_type = TypeStaff;
    else if ( type == "alumni" ) d->m_type = TypeAlumni;
    else d->m_type = TypeUser;

    d->m_age = xml["age"].text().toUInt();
    d->m_scrobbles = xml["playcount"].text().toUInt();
    d->m_registered = QDateTime::fromMSecsSinceEpoch(xml["registered"].attribute("unixtime").toUInt() * 1000);
    d->m_country = xml["country"].text();
    d->m_isSubscriber = ( xml["subscriber"].text() == "1" );
    d->m_canBootstrap = ( xml["bootstrap"].text() == "1" );
    d->m_gender = xml["gender"].text();
    d->m_images << xml["image size=small"].text()
             << xml["image size=medium"].text()
             << xml["image size=large"].text()
             << xml["image size=extralarge"].text();
}

User::User( const User& other )
    :AbstractType(),
    d( new UserPrivate( *other.d ) )
{
}

User::User()
    :AbstractType(),
    d( new UserPrivate )
{
}

User::User( const QString& name )
    :AbstractType(),
    d( new UserPrivate )
{
    d->m_name = name;
    d->m_match = -1.0f;
    d->m_age = 0;
    d->m_scrobbles = 0;
    d->m_registered = QDateTime();
    d->m_isSubscriber = false;
    d->m_canBootstrap = false;
}


User::~User()
{
    delete d;
}


lastfm::User&
User::operator=( const User& that )
{
    d->m_name = that.name();
    d->m_images = that.d->m_images;
    d->m_realName = that.d->m_realName;
    d->m_match = that.d->m_match;
    d->m_type = that.d->m_type;
    d->m_age = that.d->m_age;
    d->m_scrobbles = that.d->m_scrobbles;
    d->m_registered = that.d->m_registered;
    d->m_country = that.d->m_country;
    d->m_isSubscriber = that.d->m_isSubscriber;
    d->m_canBootstrap = that.d->m_canBootstrap;
    d->m_gender = that.d->m_gender;
    d->m_images = that.d->m_images;
    return *this;
}


bool
User::operator==(const lastfm::User& that) const
{
    return d->m_name == that.d->m_name;
}


bool
User::operator<(const lastfm::User& that) const
{
    return d->m_name < that.d->m_name;
}



User::operator QString() const
{
    return d->m_name;
}


QString
User::name() const
{
    return d->m_name;
}


void
User::setName( const QString& name )
{
    d->m_name = name;
}


User::Type
User::type() const
{
    return d->m_type;
}


void
User::setType( Type type )
{
    d->m_type = type;
}


QUrl 
User::imageUrl( ImageSize size, bool square ) const
{
    if( !square ) return d->m_images.value( size );

    QUrl url = d->m_images.value( size );
    QRegularExpression re( "/serve/(\\d*)s?/" );
    return QUrl( url.toString().replace( re, "/serve/\\1s/" ));
}


QMap<QString, QString>
User::params(const QString& method) const
{
    QMap<QString, QString> map;
    map["method"] = "user."+method;
    map["user"] = d->m_name;
    return map;
}


QNetworkReply*
User::getFriends( bool recentTracks, int limit, int page ) const
{
    QMap<QString, QString> map = params( "getFriends" );
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    if ( recentTracks ) map["recenttracks"] = "1";
    return ws::get( map );
}


QNetworkReply*
User::getFriendsListeningNow( int limit, int page ) const
{
    QMap<QString, QString> map = params( "getFriendsListeningNow" );
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    return ws::get( map );
}


QNetworkReply*
User::getFriendsThatListenTo( const lastfm::Artist& artist, int limit, int page ) const
{
    QMap<QString, QString> map = params( "getFriendsThatListenTo" );
    map["artist"] = artist.name();
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    return ws::get( map );
}


QNetworkReply*
User::getLovedTracks( int limit, int page ) const
{
    QMap<QString, QString> map = params( "getLovedTracks" );
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    return ws::get( map );
}


QNetworkReply*
User::getTopTags() const
{
    return ws::get( params( "getTopTags" ) );
}


QNetworkReply*
User::getTopArtists( QString period, int limit, int page ) const
{
    QMap<QString, QString> map = params( "getTopArtists" );
    map["period"] = period;
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    return ws::get( map );
}


QNetworkReply*
User::getRecentArtists() const
{
    return ws::get( params( "getRecentArtists" ) );
}


QNetworkReply*
User::getRecentTracks( int limit , int page ) const
{
    QMap<QString, QString> map = params( "getRecentTracks" );
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    map["extended"] = "true";
    return ws::get( map );
}

QNetworkReply*
User::getRecentStations( int limit, int page ) const
{
    QMap<QString, QString> map = params( "getRecentStations" );
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    return ws::get( map );
}


QNetworkReply*
User::getRecommendedArtists( int limit, int page ) const
{
    QMap<QString, QString> map = params( "getRecommendedArtists" );
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    return ws::get( map );
}


QNetworkReply*
User::getNeighbours( int limit, int page ) const
{
    QMap<QString, QString> map = params( "getNeighbours" );
    map["limit"] = QString::number( limit );
    map["page"] = QString::number( page );
    return ws::get( map );
}


QNetworkReply*
User::getPlaylists() const
{
    return ws::get( params( "getPlaylists" ) );
}


UserList //static
User::list( QNetworkReply* r )
{

    XmlQuery lfm;

    lfm.parse( r );
    return UserList( lfm );
}


QString
User::toString() const
{
    return name();
}


QDomElement
User::toDomElement( QDomDocument& ) const
{
    return QDomElement();
}


QNetworkReply* //static
User::getInfo( const QString& username )
{
    QMap<QString, QString> map;
    map["method"] = "user.getInfo";
    map["user"] = username;
    return ws::post( map );
}




/*
QNetworkReply* //static
User::getRecommendedArtists()
{
    QMap<QString, QString> map;
    map["method"] = "user.getRecommendedArtists";
    return ws::post( map );
}
*/

QUrl
User::www() const
{ 
    return UrlBuilder( "user" ).slash( d->m_name ).url();
}


float
User::match() const
{
    return d->m_match;
}


QString
User::getInfoString() const
{
    QString text;

    text = QString("%1").arg( d->m_realName.isEmpty() ? d->m_name : d->m_realName );
    if ( d->m_age ) text.append( QString(", %1").arg( d->m_age ) );
    if ( d->m_gender.known() ) text.append( QString(", %1").arg( d->m_gender.toString() ) );
    if ( !d->m_country.isEmpty() ) text.append( QString(", %1").arg( d->m_country ) );

    return text;
}


quint32
User::scrobbleCount() const
{
    return d->m_scrobbles;
}


void 
User::setScrobbleCount( quint32 scrobbleCount )
{
    d->m_scrobbles = scrobbleCount;
}


QDateTime
User::dateRegistered() const
{
    return d->m_registered;
}


void
User::setDateRegistered( const QDateTime& date )
{
    d->m_registered = date;
}


Gender
User::gender() const
{
    return d->m_gender;
}


QString
User::country() const
{
    return d->m_country;
}


QString
User::realName() const
{
    return d->m_realName;
}


void 
User::setRealName( const QString& realName )
{
    d->m_realName = realName;
}


void
User::setImages( const QList<QUrl>& images )
{
    d->m_images = images;
}

unsigned short
User::age() const
{
    return d->m_age;
}

void 
User::setAge( unsigned short age )
{
    d->m_age = age;
}


bool
User::isSubscriber() const
{
    return d->m_isSubscriber;
}


void 
User::setIsSubscriber( bool subscriber )
{
    d->m_isSubscriber = subscriber;
}


bool
User::canBootstrap() const
{
    return d->m_canBootstrap;
}


void 
User::setCanBootstrap( bool canBootstrap )
{
    d->m_canBootstrap = canBootstrap;
}

void 
User::setGender( const QString& s )
{
    d->m_gender = Gender( s );
}

void 
User::setCountry( const QString& country )
{
    d->m_country = country;
}

0707010000003B000081A400000000000000000000000167B98EA80000150B000000000000000000000000000000000000003A00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/User.h/*
   Copyright 2009-2010 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_USER_H
#define LASTFM_USER_H

#include <QStringList>

#include "AbstractType.h"
#include "ws.h"

namespace lastfm
{
    class UserList;
    class XmlQuery;
    class Artist;

    class LASTFM_DLLEXPORT Gender
    {

    public:
        Gender();
        Gender( const Gender& gender );
        Gender( const QString& ss );
        ~Gender();

        bool known() const;
        bool male() const;
        bool female() const;

        QString toString() const;
        lastfm::Gender& operator=( const lastfm::Gender& that );

    private:
        class GenderPrivate;
        GenderPrivate * const d;
    };

    class LASTFM_DLLEXPORT User : public AbstractType
    {
    public:
        enum Type
        {
            TypeUser,
            TypeSubscriber,
            TypeModerator,
            TypeStaff,
            TypeAlumni
        };

    public:
        User();
        User( const QString& name );
        User( const XmlQuery& xml );
        User( const User& user );
        ~User();

        lastfm::User& operator=( const lastfm::User& that );
        bool operator==(const lastfm::User& that) const;
        bool operator<(const lastfm::User& that) const;

        operator QString() const;

        QString name() const;
        void setName( const QString& name );

        Type type() const;
        void setType( Type type );

        bool isSubscriber() const;
        void setIsSubscriber( bool subscriber );

        bool canBootstrap() const;
        void setCanBootstrap( bool canBootstrap );

        quint32 scrobbleCount() const;
        void setScrobbleCount( quint32 scrobblesCount );

        QDateTime dateRegistered() const;
        void setDateRegistered( const QDateTime& date );

        Gender gender() const;
        QString country() const;

        QString realName() const;
        void setRealName( const QString& realName );

        QUrl imageUrl( ImageSize size = LargeImage, bool square = false ) const;
        void setImages( const QList<QUrl>& images );

        unsigned short age() const;
        void setAge( unsigned short age );

        void setGender( const QString& s );
        void setCountry( const QString& country );
    
        /** use Tag::list() on the response to get a WeightedStringList */
        QNetworkReply* getTopTags() const;

        /** use User::list() on the response to get a UserList */
        QNetworkReply* getFriends(  bool recentTracks = false, int limit = 50, int page = 1 ) const;
        QNetworkReply* getFriendsListeningNow( int limit = 50, int page = 1 ) const;
        QNetworkReply* getFriendsThatListenTo( const lastfm::Artist& artist, int limit = 50, int page = 1 ) const;
        QNetworkReply* getNeighbours( int limit = 50, int page = 1 ) const;
    
        QNetworkReply* getLovedTracks( int limit = 50, int page = 1 ) const;
        QNetworkReply* getPlaylists() const;
        QNetworkReply* getTopArtists( QString period = QStringLiteral("overall"), int limit = 50, int page = 1 ) const;
        QNetworkReply* getRecentTracks( int limit = 50, int page = 1 ) const;
        QNetworkReply* getRecentArtists() const;
        QNetworkReply* getRecentStations(  int limit = 10, int page = 1  ) const;
        QNetworkReply* getRecommendedArtists( int limit = 50, int page = 1 ) const;

        /** you can only get information about the any user */
        static QNetworkReply* getInfo( const QString& username = lastfm::ws::Username );

        /** a verbose string, eg. "A man with 36,153 scrobbles" */
        QString getInfoString() const;
    
        static UserList list( QNetworkReply* );

        QString toString() const;
        QDomElement toDomElement( QDomDocument& ) const;
    
        /** the user's profile page at www.last.fm */
        QUrl www() const;
    
        /** Returns the match between the logged in user and the user which this
          * object represents (if < 0.0f then not set) */
        float match() const;
    
    protected:        
        QMap<QString, QString> params( const QString& method ) const;

    protected:
        class UserPrivate;
        UserPrivate * const d;
    };

    class LASTFM_DLLEXPORT UserList
    {
    public:
        UserList();
        UserList( const XmlQuery& query );
        UserList( const UserList& other );
        ~UserList();
        UserList& operator=( const UserList& other );

        int totalUsers();
        int totalPages();
        int currentPage();
        int usersPerPage();
        QList<User> users();

    private:
        class UserListPrivate * const d;
    };
}

#endif
0707010000003C000081A400000000000000000000000167B98EA8000014E6000000000000000000000000000000000000004000000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/XmlQuery.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "XmlQuery.h"

#include <QCoreApplication>
#include <QDebug>
#include <QDomDocument>
#include <QStringList>

using lastfm::XmlQuery;

class lastfm::XmlQueryPrivate
{
public:
    XmlQueryPrivate();
    QDomDocument domdoc;
    QDomElement e;
    lastfm::ws::ParseError error;
};


lastfm::XmlQueryPrivate::XmlQueryPrivate()
    : error( lastfm::ws::ParseError( lastfm::ws::NoError, "" ) )
{
}


XmlQuery::XmlQuery()
    : d( new XmlQueryPrivate )
{
}


XmlQuery::XmlQuery( const XmlQuery& that )
    : d( new XmlQueryPrivate( *that.d ) )
{
}

XmlQuery::XmlQuery( const QDomElement& e, const char* name )
    : d( new XmlQueryPrivate )
{
    d->e = e;
    if (e.isNull())
        qWarning() << "Expected node absent:" << name;
}


XmlQuery::~XmlQuery()
{
    delete d;
}


bool
XmlQuery::parse( const QByteArray& bytes )
{  
    if ( !bytes.size() )
        d->error = lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "No data" );
    else
    {
        if( !d->domdoc.setContent( bytes ) )
            d->error = lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "Invalid XML" );
        else
        {
            d->e = d->domdoc.documentElement();

            if (d->e.isNull())
                d->error = lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "Lfm is null" );
            else
            {
                QString const status = d->e.attribute( "status" );
                QDomElement error = d->e.firstChildElement( "error" );
                uint const n = d->e.childNodes().count();

                // no elements beyond the lfm is perfectably acceptable for example when
                // XmlQuery is used parse response of a POST request.
                // if (n == 0) // nothing useful in the response
                if (status == "failed" || (n == 1 && !error.isNull()) )
                    d->error = error.isNull()
                            ? lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "" )
                            : lastfm::ws::ParseError( lastfm::ws::Error( error.attribute( "code" ).toUInt() ), error.text() );
            }
        }
    }

    if ( d->error.enumValue() != lastfm::ws::NoError )
    {
        qDebug() << bytes;

        switch ( d->error.enumValue() )
        {
            case lastfm::ws::OperationFailed:
            case lastfm::ws::InvalidApiKey:
            case lastfm::ws::InvalidSessionKey:
                // NOTE will never be received during the LoginDialog stage
                // since that happens before this slot is registered with
                // QMetaObject in App::App(). Neat :)
                QMetaObject::invokeMethod( qApp, "onWsError", Q_ARG( lastfm::ws::Error, d->error.enumValue() ) );
                break;
            default:
                //do nothing
            break;
        }
    }

    return d->error.enumValue() == lastfm::ws::NoError;
}

bool
XmlQuery::parse( QNetworkReply* reply )
{
    reply->deleteLater();
    return parse( reply->readAll() ); 
}


lastfm::ws::ParseError
XmlQuery::parseError() const
{
    return d->error;
}


QString XmlQuery::text() const
{
    return d->e.text();
}


QString XmlQuery::attribute( const QString& name ) const
{
    return d->e.attribute( name );
}


XmlQuery
XmlQuery::operator[]( const QString& name ) const
{
    QStringList parts = name.split( ' ' );
    if (parts.size() >= 2)
    {
        QString tagName = parts[0];
        parts = parts[1].split( '=' );
        QString attributeName = parts.value( 0 );
        QString attributeValue = parts.value( 1 );

        foreach (XmlQuery e, children( tagName ))
            if (e.d->e.attribute( attributeName ) == attributeValue)
                return e;
    }
    XmlQuery xq( d->e.firstChildElement( name ), name.toUtf8().data() );
    xq.d->domdoc = this->d->domdoc;
    return xq;
}


QList<XmlQuery>
XmlQuery::children( const QString& named ) const
{
    QList<XmlQuery> elements;
    QDomNodeList nodes = d->e.elementsByTagName( named );
    for (int x = 0; x < nodes.count(); ++x) {
        XmlQuery xq( nodes.at( x ).toElement() );
        xq.d->domdoc = this->d->domdoc;
        elements += xq;
    }
    return elements;
}

XmlQuery::operator QDomElement() const
{
    return d->e;
}


XmlQuery&
XmlQuery::operator=( const XmlQuery& that )
{
    d->domdoc = that.d->domdoc;
    d->e = that.d->e;
    d->error = that.d->error;
    return *this;
}

QDebug operator<<( QDebug d, const lastfm::XmlQuery& xq )
{
    QString s;
    QTextStream t( &s, QIODevice::WriteOnly );
    QDomElement(xq).save( t, 2 );
    return d << s;
}
0707010000003D000081A400000000000000000000000167B98EA800000C40000000000000000000000000000000000000003E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/XmlQuery.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_XMLQUERY_H
#define LASTFM_XMLQUERY_H

#include "ws.h"
#include <QDomElement>

namespace lastfm
{
    /** Qt's XmlQuery implementation is totally unimpressive, so this is a 
      * hack that feels like jQuery */
    class LASTFM_DLLEXPORT XmlQuery
    {
    public:
        /** we assume the bytearray is an XML document, this object will then
          * represent the documentElement of that document, eg. if this is a
          * Last.fm webservice response:
          *
          * XmlQuery xq = lastfm::ws::parse(response);
          * qDebug() << xq["artist"].text()
          *
          * Notice the lfm node is not referenced, that is because it is the
          * document-element of the XML document.
          */
        XmlQuery();
        XmlQuery( const XmlQuery& that );
        ~XmlQuery();

        /**
         * Fills in the XmlQuery response by parsing raw reply @param data from the
         * webservice.
         *
         * @return true if successfully parsed and the response does not signify an error,
         * false otherwise. When false is returned, parseError() contains the error.
         */
        bool parse( const QByteArray& data );

        /**
         * Convenience parse() overload that takes data from the @param reply and calls
         * deleteLater() on it.
         *
         * @return true if successfully parsed and the response does not signify an error,
         * false otherwise. When false is returned, parseError() contains the error.
         */
        bool parse( QNetworkReply* reply );

        ws::ParseError parseError() const;

        XmlQuery( const QDomElement& e, const char* name = "" );

        /** Selects a DIRECT child element, you can specify attributes like so:
          *
          * e["element"]["element attribute=value"].text();
          */
        XmlQuery operator[]( const QString& name ) const;
        QString text() const;
        QString attribute( const QString& name ) const;

        /** selects all children with specified name, recursively */
        QList<XmlQuery> children( const QString& named ) const;

        operator QDomElement() const;
        XmlQuery& operator=( const XmlQuery& that );

    private:
        class XmlQueryPrivate * const d;
    };
}

LASTFM_DLLEXPORT QDebug operator<<( QDebug d, const lastfm::XmlQuery& xq );

#endif
0707010000003E000081A400000000000000000000000167B98EA800000CBC000000000000000000000000000000000000003C00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Xspf.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Xspf.h"
#include "XmlQuery.h"

#include <QTimer>
#include <QUrl>


class lastfm::XspfPrivate
{
    public:
        QList<Track> tracks;
        QString title;
};


lastfm::Xspf::Xspf( const QDomElement& playlist_node, QObject* parent )
    :QObject( parent )
    , d( new XspfPrivate )
{
    XmlQuery e( playlist_node );

    int expirySeconds = e["link rel=http://www.last.fm/expiry"].text().toInt();
    QTimer::singleShot( expirySeconds * 1000, this, SLOT(onExpired()));
    
    d->title = e["title"].text();
        
    //FIXME should we use UnicornUtils::urlDecode()?
    //The title is url encoded, has + instead of space characters 
    //and has a + at the begining. So it needs cleaning up:
    d->title.replace( '+', ' ' );
    d->title = QUrl::fromPercentEncoding( d->title.toUtf8() );
    d->title = d->title.trimmed();
    
    foreach (XmlQuery e, e["trackList"].children( "track" ))
    {
        MutableTrack t;
        t.setUrl( e["location"].text() );
        t.setImageUrl( AbstractType::LargeImage, e["image"].text() );
        t.setExtra( "trackauth", e["extension"]["trackauth"].text() );
        t.setTitle( e["title"].text() );
        t.setArtist( e["creator"].text() );
        t.setAlbum( e["album"].text() );
        t.setDuration( e["duration"].text().toInt() / 1000 );
        t.setLoved( e["extension"]["loved"].text() == "1" );
        t.setSource( Track::LastFmRadio );
        t.setExtra( "expiry", QString::number( QDateTime::currentDateTime().addSecs( expirySeconds ).toMSecsSinceEpoch() / 1000) );
        t.setExtra( "playlistTitle", d->title );

        QList<QString> contexts;
        QDomNodeList contextsNodeList = QDomElement(e["extension"]["context"]).childNodes();

        for ( int i = 0 ; i < contextsNodeList.count() ; ++i )
            contexts.append( contextsNodeList.item(i).toElement().text() );

        if ( contexts.count() > 0 )
            t.setContext( TrackContext( contextsNodeList.item(0).toElement().tagName(), contexts ) );

        d->tracks << t; // outside try block since location is enough basically
    }
}


lastfm::Xspf::~Xspf()
{
    delete d;
}


QString
lastfm::Xspf::title() const
{
    return d->title;
}


bool
lastfm::Xspf::isEmpty() const
{
    return d->tracks.isEmpty();
}


lastfm::Track
lastfm::Xspf::takeFirst()
{
    return d->tracks.takeFirst();
}

QList<lastfm::Track>
lastfm::Xspf::tracks() const
{
    return d->tracks;
}

void
lastfm::Xspf::onExpired()
{
    emit expired();
}
0707010000003F000081A400000000000000000000000167B98EA800000580000000000000000000000000000000000000003A00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/Xspf.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_XSPF_H
#define LASTFM_XSPF_H

#include "Track.h"
#include <QList>

namespace lastfm
{
    class LASTFM_DLLEXPORT Xspf : public QObject
    {
        Q_OBJECT
    public:
        /** pass in the playlist node! */
        Xspf( const QDomElement& playlist_node, QObject* parent );
        ~Xspf();

        QString title() const;

        bool isEmpty() const;
        Track takeFirst();

        QList<Track> tracks() const;

    Q_SIGNALS:
        Q_DECL_DEPRECATED void expired();

    private Q_SLOTS:
        Q_DECL_DEPRECATED void onExpired();

    private:
        class XspfPrivate * const d;
    };
}

#endif
07070100000040000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000003F00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint07070100000041000081A400000000000000000000000167B98EA8000005EE000000000000000000000000000000000000004E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/CMakeLists.txtinclude_directories(${CMAKE_CURRENT_LIST_DIR}/..)
include_directories(${CMAKE_CURRENT_BINARY_DIR}/..)

set(lastfm_fingerprint_SOURCES
  Collection.cpp
  Fingerprint.cpp
  Sha256.cpp
  fplib/Filter.cpp
  fplib/FingerprintExtractor.cpp
  fplib/OptFFT.cpp
)

set(lastfm_fingerprint_HEADERS
    Fingerprint.h
    FingerprintableSource.h
)

add_library(${FINGERPRINT_LIB_TARGET_NAME} SHARED ${lastfm_fingerprint_SOURCES})

set_target_properties(${FINGERPRINT_LIB_TARGET_NAME} PROPERTIES
    COMPILE_DEFINITIONS LASTFM_FINGERPRINT_LIB
    VERSION ${LASTFM_VERSION_STRING}
    SOVERSION ${LASTFM_SOVERSION}
)

if(WIN32)
    set_target_properties(${FINGERPRINT_LIB_TARGET_NAME} PROPERTIES
        RUNTIME_OUTPUT_NAME "${FINGERPRINT_LIB_TARGET_NAME}-${LASTFM_SOVERSION}")
endif()

target_link_libraries(${FINGERPRINT_LIB_TARGET_NAME}
    ${LASTFM_LIB_TARGET_NAME}
    PkgConfig::LibSamplerate
    PkgConfig::FFTW3F
)

target_link_libraries(${FINGERPRINT_LIB_TARGET_NAME} Qt::Network Qt::Sql Qt::Xml)

install(TARGETS ${FINGERPRINT_LIB_TARGET_NAME}
    EXPORT "${FINGERPRINT_LIB_TARGET_NAME}Config"
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(EXPORT "${FINGERPRINT_LIB_TARGET_NAME}Config"
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/lastfm${LASTFM_LIB_VERSION_SUFFIX}
    NAMESPACE LASTFM::
)

install(FILES ${lastfm_fingerprint_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lastfm${LASTFM_LIB_VERSION_SUFFIX}/)
07070100000042000081A400000000000000000000000167B98EA80000202E000000000000000000000000000000000000004E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/Collection.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Collection.h"

#include "misc.h"

#include <QCoreApplication>
#include <QFileInfo>
#include <QMutexLocker>
#include <QSqlQuery>
#include <QSqlError>
#include <QStringList>
#include <QVariant>
#include <QDebug>

static const int k_collectionDbVersion = 1;

// Singleton instance needs to be initialised
Collection* Collection::s_instance = NULL;


Collection::Collection()
{
    m_db = QSqlDatabase::addDatabase( "QSQLITE", "collection" );
    m_db.setDatabaseName( lastfm::dir::runtimeData().filePath( "collection.db" ) );
    
    if (!m_db.open()) {
        qDebug() << m_db.lastError();
        return;
    }

    if (!m_db.isValid()) {
        qWarning() << "collection.db connection is not valid";
        return;
    }

    if (!m_db.tables().contains( "files" ))
    {
        qDebug() << "Creating Collection database";

        query( "CREATE TABLE artists ("
                    "id          INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "serverUid   INTEGER,"
                    "lcName      TEXT NOT NULL,"
                    "displayName TEXT NOT NULL );" );

        query( "CREATE TABLE albums ("
                    "id            INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "serverUid     INTEGER,"
                    "lcName        TEXT NOT NULL,"
                    "displayName   TEXT NOT NULL,"
                    "primaryArtist INTEGER NOT NULL );" );

        query( "CREATE UNIQUE INDEX album_names_idx ON albums ( primaryArtist, lcName );" );

        query( "CREATE TABLE tracks ("
                    "id                INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "lcName            TEXT NOT NULL,"
                    "displayName       TEXT NOT NULL,"
                    "primaryArtist     INTEGER NOT NULL,"
                    "primaryAlbum      INTEGER );" );

        query( "CREATE UNIQUE INDEX track_names_idx ON tracks ( primaryArtist, lcName );" );

        query( "CREATE TABLE files ("
                    "id                INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "uri               TEXT NOT NULL,"
                    "track             INTEGER NOT NULL,"
                    "bitrate           INTEGER,"
                    "samplerate        INTEGER,"
                    "duration          INTEGER,"
                    "filesize          INTEGER,"
                    "source            INTEGER,"
                    "modificationDate  INTEGER,"
                    "lastPlayDate      INTEGER,"
                    "playCounter       INTEGER,"
                    "mbId              VARCHAR( 36 ),"
                    "fpId              INTEGER );" );

        query( "CREATE UNIQUE INDEX files_uri_idx ON files ( uri );" );
        query( "CREATE INDEX files_track_idx ON files ( track );" );
        query( "CREATE INDEX files_fpId_idx ON files ( fpId );" );
        query( "CREATE INDEX files_source_idx ON files ( source );" );

        query( "CREATE TABLE sources ("
                    "id          INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "name        TEXT UNIQUE,"
                    "available   INTEGER,"
                    "host        TEXT,"
                    "cost        INTEGER );" );

        query( "CREATE TABLE genres ("
                    "id          INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "name        TEXT UNIQUE );" );

        query( "CREATE TABLE labels ("
                    "id          INTEGER PRIMARY KEY AUTOINCREMENT,"
                    "serverUid   INTEGER UNIQUE,"
                    "name        TEXT );" );
    }
    
    int const v = version();
    if ( v < k_collectionDbVersion )
    {
        qDebug() << "Upgrading Collection::db from" << v << "to" << k_collectionDbVersion;

        /**********************************************
         * README!!!!!!!                              *
         * Ensure you use v < x                       *
         * Ensure you do upgrades in ascending order! *
         **********************************************/
    
        if ( v < 1 )
        {
            // Norman discovered that he stored some fpId's wrong prior to 17th December 2007
            // So we have to wipe the fpIds for databases without the metadata table
            // we didn't store version information before that, which was a bad decision wasn't it?

            // this will trigger refingerprinting of every track
            query( "UPDATE files SET fpId = NULL;" );

            query( "CREATE TABLE metadata ("
                        "key         TEXT UNIQUE NOT NULL,"
                        "value       TEXT );" );

            query( "INSERT INTO metadata (key, value) VALUES ('version', '1');" );
        }


        // do last, update DB version number
        query( "UPDATE metadata set key='version', value='"
                    + QString::number( k_collectionDbVersion ) + "';" );
    }
}


Collection& //static
Collection::instance()
{
    static QMutex mutex;
    QMutexLocker locker( &mutex );
    
    if ( !s_instance )
    {
        s_instance = new Collection;
        qAddPostRoutine(destroy);
    }
    
    return *s_instance;
}


void //static
Collection::destroy()
{
    delete s_instance;
    QSqlDatabase::removeDatabase( "collection" );
}


int
Collection::version() const 
{   
    QSqlQuery sql( m_db );
    sql.exec( "SELECT value FROM metadata WHERE key='version';" );

    if ( sql.next() )
    {
        return sql.value( 0 ).toInt();
    }

    return 0;
}


bool
Collection::query( const QString& queryToken )
{
    QSqlQuery query( m_db );
    query.exec( queryToken );

    if ( query.lastError().isValid() )
    {
        qDebug() << "SQL query failed:" << query.lastQuery() << '\n'
                 << "SQL error was:"    << query.lastError().databaseText() << '\n'
                 << "SQL error type:"   << query.lastError().type();

        return false;
    }

    return true;
}


QString
Collection::fileURI( const QString& filePath )
{
    QString prefix( "file:/" );

#ifdef WIN32
    prefix = "file://";
#endif

    return prefix + QFileInfo( filePath ).absoluteFilePath();
}


QString
Collection::getFingerprintId( const QString& filePath )
{
    QSqlQuery query( m_db );
    query.prepare( "SELECT fpId FROM files WHERE uri = :uri" );
    query.bindValue( ":uri", fileURI( filePath ) );

    query.exec();
    if ( query.lastError().isValid() )
    {
        qDebug() << "SQL query failed:" << query.lastQuery() << '\n'
                 << "SQL error was:"    << query.lastError().databaseText() << '\n'
                 << "SQL error type:"   << query.lastError().type();
    }
    else if (query.next())
        return query.value( 0 ).toString();

    return "";
}


bool
Collection::setFingerprintId( const QString& filePath, QString fpId )
{
    bool isNumeric;
    int intFpId = fpId.toInt( &isNumeric );
    Q_ASSERT( isNumeric );

    QSqlQuery query( m_db );
    query.prepare( "REPLACE INTO files ( uri, track, fpId ) VALUES ( :uri, 0, :fpId )" );
    query.bindValue( ":uri", fileURI( filePath ) );
    query.bindValue( ":fpId", intFpId );
    query.exec();

    if ( query.lastError().isValid() )
    {
        qDebug() << "SQL query failed:" << query.lastQuery() << '\n'
                 << "SQL error was:"    << query.lastError().databaseText() << '\n'
                 << "SQL error type:"   << query.lastError().type();

        return false;
    }

    return true;
}
07070100000043000081A400000000000000000000000167B98EA800000673000000000000000000000000000000000000004C00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/Collection.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

/** Class that we use to store fingerprints, basically
  */

#ifndef COLLECTION_H
#define COLLECTION_H

#include <QObject>
#include <QSqlDatabase>


/** @author: <chris@last.fm> */
class Collection
{
public:
    static Collection& instance();

    /** \brief Temp method: Gets a fingerprint id. Returns "" if none found. */
    QString getFingerprintId( const QString& filePath );

    /** \brief Temp method: Sets a fingerprint id. */
    bool setFingerprintId( const QString& filePath, QString fpId );

private:
    Collection();

    /** the database version
        * version 0: up until 1.4.1
        * version 1: from 1.4.2 */
    int version() const;
    bool query( const QString& queryToken );
    QString fileURI( const QString& filePath );

    static void destroy();    
    
    static Collection* s_instance;
    QSqlDatabase m_db;
};

#endif // COLLECTION_H
07070100000044000081A400000000000000000000000167B98EA80000091D000000000000000000000000000000000000004B00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/EXAMPLE.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <lastfm/Fingerprint>
#include <lastfm/FingerprintId>
#include <QtCore>
#include <QtNetwork>

using namespace lastfm;


static void finish( QNetworkReply* reply )
{
    QEventLoop loop;
    loop.connect( reply, SIGNAL(finished()), SLOT(quit()) );
    loop.exec();
}


int main( int argc, char** argv )
{
    QCoreApplication app( argc, argv );
    
    // these fields are required
    MutableTrack t;
    t.setArtist( "Air" );
    t.setTitle( "Redhead Girl" );
    t.setAlbum( "Pocket Symphony" );
    t.setUrl( QUrl::fromLocalFile( "/Users/mxcl/Music/iTunes/iTunes Music/Air/Pocket Symphony/1-11 Redhead Girl.mp3") );
    
    try
    {
        Fingerprint fp( t );
        
        // we cache FingerprintIds in an sqlite3 db, as the generate() function
        // is expensive
        if (fp.id().isNull())
        {
            // this generates the full fingerprint hash, which is about 20kB
            fp.generate();
            
            // this asks Last.fm for a FingerprintId
            // the finish function is a Qt hack to allow syncronous HTTP
            finish( fp.submit() );
            
            // the decode step sets the FingerprintId
            // the FingerprintId is required to obtain suggestions
            // id will now be valid, or this function throws
            fp.decode( reply );
        }

        finish( fp.id().getSuggestions() );

        qDebug() << FingerprintId::getSuggestions( reply );
    }
    catch (Fingerprint::Error e)
    {
        qWarning() << e; //TODO enum debug thing
    }    
}
07070100000045000081A400000000000000000000000167B98EA800002866000000000000000000000000000000000000004F00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/Fingerprint.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Fingerprint.h"
#include "FingerprintableSource.h"
#include "Collection.h"
#include "Sha256.h"
#include "fplib/FingerprintExtractor.h"
#include "ws.h"
#include "Url.h"
#include <QDebug>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QStringList>
#include <QUrlQuery>
#include <fstream>

#include "User.h"

using lastfm::Track;

static const uint k_bufferSize = 1024 * 8;
static const int k_minTrackDuration = 30;

class lastfm::FingerprintPrivate
{
    public:
        FingerprintPrivate( const Track& t )
                    : m_track( t )
                    , m_id( -1 )
                    , m_duration( 0 )
                    , m_complete( false )
        {
        }
        lastfm::Track m_track;
        QByteArray m_data;
        int m_id;
        int m_duration;
        bool m_complete;
};


lastfm::Fingerprint::Fingerprint( const Track& t )
                   : d( new FingerprintPrivate( t ) )
{
    QString id = Collection::instance().getFingerprintId( t.url().toLocalFile() );   
    if (id.size()) {
        bool b;
        d->m_id = id.toInt( &b );
        if (!b) d->m_id = -1;
    }
}


lastfm::Fingerprint::~Fingerprint()
{
    delete d;
}


lastfm::FingerprintId
lastfm::Fingerprint::id() const
{
    return d->m_id;
}


QByteArray
lastfm::Fingerprint::data() const
{
    return d->m_data;
}


void
lastfm::Fingerprint::generate( FingerprintableSource* ms )
{
    //TODO throw if we can't get required metadata from the track object
    
//TODO    if (!QFileInfo( path ).isReadable())
//TODO        throw ReadError;

    int sampleRate, bitrate, numChannels;

    if ( !ms )
        throw ReadError;

    try
    {
        ms->init( d->m_track.url().toLocalFile() );
        ms->getInfo( d->m_duration, sampleRate, bitrate, numChannels );
    }
    catch (std::exception& e)
    {
        qWarning() << e.what();
        throw HeadersError;
    }
    

    if (d->m_duration < k_minTrackDuration)
        throw TrackTooShortError;
    
    ms->skipSilence();
    
    bool fpDone = false;
    fingerprint::FingerprintExtractor* extractor;
    try
    {
        extractor = new fingerprint::FingerprintExtractor;
        
        if (d->m_complete)
        {
            extractor->initForFullSubmit( sampleRate, numChannels );
        }
        else 
        {
            extractor->initForQuery( sampleRate, numChannels, d->m_duration );
            
            // Skippety skip for as long as the skipper sez (optimisation)
            ms->skip( extractor->getToSkipMs() );
            float secsToSkip = extractor->getToSkipMs() / 1000.0f;
            fpDone = extractor->process( 0,
                                         (size_t) sampleRate * numChannels * secsToSkip,
                                         false );
        }
    }
    catch (std::exception& e)
    {
        qWarning() << e.what();
        delete extractor;
        throw DecodeError;
    }
    
    const size_t PCMBufSize = 131072; 
    short* pPCMBuffer = new short[PCMBufSize];
    
    while (!fpDone)
    {
        size_t readData = ms->updateBuffer( pPCMBuffer, PCMBufSize );
        if (readData == 0)
            break;
        
        try
        {
            fpDone = extractor->process( pPCMBuffer, readData, ms->eof() );
        }
        catch ( const std::exception& e )
        {
            qWarning() << e.what();
            delete[] pPCMBuffer;
            delete extractor;
            throw InternalError;
        }
    }
    
    delete[] pPCMBuffer;
    
    if (!fpDone)
    {
        delete extractor;
        throw InternalError;
    }

    // We succeeded
    std::pair<const char*, size_t> fpData = extractor->getFingerprint();
    
    if (fpData.first == NULL || fpData.second == 0)
        {
        delete extractor;
        throw InternalError;
        }

    // Make a deep copy before extractor gets deleted
    d->m_data = QByteArray( fpData.first, fpData.second );
    delete extractor;
}


static QString sha256( const QString& path )
{
    // no clue why this is static, there was no comment when I refactored it
    // initially --mxcl
    static uint8_t pBuffer[SHA_BUFFER_SIZE+7];
    
    unsigned char hash[SHA256_HASH_SIZE];

    {
        QByteArray path8 = QFile::encodeName( path );
        std::ifstream inFile( path8.data(), std::ios::binary);
        
        SHA256Context sha256;
        SHA256Init( &sha256 );
        
        uint8_t* pMovableBuffer = pBuffer;
        
        // Ensure it is on a 64-bit boundary. 
        INTPTR offs;
        if ((offs = reinterpret_cast<INTPTR>(pBuffer) & 7L))
            pMovableBuffer += 8 - offs;
        
        unsigned int len;
        
        for (;;)
        {
            inFile.read( reinterpret_cast<char*>(pMovableBuffer), SHA_BUFFER_SIZE );
            len = inFile.gcount();
            
            if (len == 0)
                break;
            
            SHA256Update( &sha256, pMovableBuffer, len );
        }
        
        SHA256Final( &sha256, hash );
    }

    QString sha;
    for (int i = 0; i < SHA256_HASH_SIZE; ++i) 
    {
        QString hex = QString("%1").arg(uchar(hash[i]), 2, 16,
                                        QChar('0'));
        sha.append(hex);
    }

    return sha;
}


static QByteArray number( uint n )
{
    return n ? QByteArray::number( n ) : "";
}


QNetworkReply*
lastfm::Fingerprint::submit() const
{    
    if (d->m_data.isEmpty())
        return 0;
    
    //Parameters understood by the server according to the MIR team: 
    //{ "trackid", "recordingid", "artist", "album", "track", "duration", 
    //  "tracknum", "username", "sha256", "ip", "fpversion", "mbid", 
    //  "filename", "genre", "year", "samplerate", "noupdate", "fulldump" }
    
    Track const t = d->m_track;
    QString const path = t.url().toLocalFile();
    QFileInfo const fi( path );

    lastfm::Url url( QUrl( "http://ws.audioscrobbler.com/fingerprint/query/" ) );
    url.addQueryItem( "username", lastfm::User().name() );
    url.addQueryItem( "artist", t.artist() );
    url.addQueryItem( "album", t.album() );
    url.addQueryItem( "track", t.title() );
    url.addQueryItem( "duration", number( d->m_duration > 0 ? d->m_duration : t.duration() ) );
    url.addQueryItem( "mbid", t.mbid() );
    url.addQueryItem( "filename", fi.completeBaseName() );
    url.addQueryItem( "fileextension", fi.completeSuffix() );
    url.addQueryItem( "tracknum", number( t.trackNumber() ) );
    url.addQueryItem( "sha256", sha256( path ) );
    url.addQueryItem( "time", number(QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000) );
    url.addQueryItem( "fpversion", QByteArray::number((int)fingerprint::FingerprintExtractor::getVersion()) );
    url.addQueryItem( "fulldump", d->m_complete ? "true" : "false" );
    url.addQueryItem( "noupdate", "false" );

    //FIXME: talk to mir about submitting fplibversion

    QNetworkRequest request( url.url() );
    request.setHeader( QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=----------------------------8e61d618ca16" );

    QByteArray bytes;
    bytes += "------------------------------8e61d618ca16\r\n";
    bytes += "Content-Disposition: ";
    bytes += "form-data; name=\"fpdata\"";
    bytes += "\r\n\r\n";
    bytes += d->m_data;
    bytes += "\r\n";
    bytes += "------------------------------8e61d618ca16--\r\n";

    qDebug() << url.url();
    qDebug() << "Fingerprint size:" << bytes.size() << "bytes";

    return lastfm::nam()->post( request, bytes );
}


void
lastfm::Fingerprint::decode( QNetworkReply* reply, bool* complete_fingerprint_requested )
{
    // The response data will consist of a number and a string.
    // The number is the fpid and the string is either FOUND or NEW
    // (or NOT FOUND when noupdate was used). NEW means we should
    // schedule a full fingerprint.
    //
    // In the case of an error, there will be no initial number, just
    // an error string.
    
    reply->deleteLater();
    QString const response( reply->readAll() );
    QStringList const list = response.split( ' ' );

    QString const fpid = list.value( 0 );
    QString const status = list.value( 1 );
       
    if (response.isEmpty() || list.count() < 2 || response == "No response to client error")
        goto bad_response;
    if (list.count() != 2)
        qWarning() << "Response looks bad but continuing anyway:" << response;

    {
        // so variables go out of scope before jump to label
        // otherwise compiler error on GCC 4.2
        bool b;
        uint fpid_as_uint = fpid.toUInt( &b );
        if (!b) goto bad_response;
    
        Collection::instance().setFingerprintId( d->m_track.url().toLocalFile(), fpid );
    
        if (complete_fingerprint_requested)
            *complete_fingerprint_requested = (status == "NEW");

        d->m_id = (int)fpid_as_uint;
        return;
    }

bad_response:
    qWarning() << "Response is bad:" << response;
    throw BadResponseError;
}


lastfm::CompleteFingerprint::CompleteFingerprint( const lastfm::Track& t ) : Fingerprint( t )
{
    d->m_complete = true;
}


lastfm::CompleteFingerprint::~CompleteFingerprint()
{
}


QDebug operator<<( QDebug d, lastfm::Fingerprint::Error e )
{
    #define CASE(x) case lastfm::Fingerprint::x: return d << #x;
    switch (e)
    {
        CASE(ReadError)
        CASE(HeadersError)
        CASE(DecodeError)
        CASE(TrackTooShortError)
        CASE(BadResponseError)
        CASE(InternalError)
    }
    #undef CASE

    return d;
}
07070100000046000081A400000000000000000000000167B98EA800000D1B000000000000000000000000000000000000004D00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/Fingerprint.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_FINGERPRINT_H
#define LASTFM_FINGERPRINT_H

#include "global.h"

#include "FingerprintId.h"

namespace lastfm
{
    class FingerprintableSource;
    class Track;

    class LASTFM_FINGERPRINT_DLLEXPORT Fingerprint
    {
    protected:
        class FingerprintPrivate * const d;

    public:
        /** represents a partial fingerprint of 20 seconds of music, this is 
          * considered 99.9999...9999% unique and so we use it for most stuff as 
          * it is much quicker than a complete fingerprint, still though, you
          * should do the generate step in a thread. */
        Fingerprint( const lastfm::Track& );
        ~Fingerprint();
    
        /** if the id isNull(), then you'll need to do generate, submit and decode */
        FingerprintId id() const;

        /** The actual data that is the fingerprint, this is about 70kB or so,
          * there isn't anything in it until you call generate. */
        QByteArray data() const;

        enum Error
        {
            ReadError = 0,

            /** failed to extract samplerate, bitrate, channels, duration etc */
            HeadersError,

            DecodeError,
        
            /** there is a minimum track duration for fingerprinting */
            TrackTooShortError,
            
            /** the fingerprint service went wrong, or we submitted bad data, 
              * or myabe the request failed, whatever, we couldn't parse the 
              * result */
            BadResponseError,
        
            /** sorry, liblastfm sucks, report bug with log! */
            InternalError            
        };

        /** This is CPU intensive, do it in a thread in your GUI application */
        void generate( FingerprintableSource* );

        /** Submits the fingerprint data to Last.fm in order to get a FingerprintId
          * back. You need to wait for the QNetworkReply to finish before you can
          * pass it to decode clearly. */
        QNetworkReply* submit() const;

        /** Pass a finished reply from submit(), if the response is sound, id()
          * will be valid. Otherwise we will throw. You always get a valid id
          * or a throw.
          */
        void decode( QNetworkReply*, bool* lastfm_needs_a_complete_fingerprint = 0 );
    };


    class LASTFM_FINGERPRINT_DLLEXPORT CompleteFingerprint : public Fingerprint
    {
        CompleteFingerprint( const lastfm::Track& t );
        ~CompleteFingerprint();
    };
}


QDebug LASTFM_FINGERPRINT_DLLEXPORT operator<<( QDebug d, lastfm::Fingerprint::Error e );

#endif
07070100000047000081A400000000000000000000000167B98EA800000677000000000000000000000000000000000000005700000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/FingerprintableSource.h/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef LASTFM_FINGERPRINTABLE_SOURCE_H
#define LASTFM_FINGERPRINTABLE_SOURCE_H

#include "global.h"
#include <QString>

namespace lastfm
{
    class LASTFM_FINGERPRINT_DLLEXPORT FingerprintableSource
    {
    public:
        virtual ~FingerprintableSource() {}

        /** do all initialisation here and throw if there is problems */
        virtual void init( const QString& path ) = 0;

        virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) = 0;

        /** put a chunk of PCM data in pBuffer, don't exceed size, return the
          * number of bytes you put in the buffer */
        virtual int updateBuffer( signed short* buffer, size_t bufferSize ) = 0;

        virtual void skip( const int mSecs ) = 0;
        virtual void skipSilence( double silenceThreshold = 0.0001 ) = 0;

        virtual bool eof() const = 0;
    };
}

#endif
07070100000048000081A400000000000000000000000167B98EA8000030E2000000000000000000000000000000000000004A00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/Sha256.cpp/*-
 * Copyright (c) 2001-2003 Allan Saddi <allan@saddi.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY ALLAN SADDI AND HIS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL ALLAN SADDI OR HIS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: sha256.c 680 2003-07-25 21:57:49Z asaddi $
 */

/*
 * Define WORDS_BIGENDIAN if compiling on a big-endian architecture.
 *
 * Define SHA256_TEST to test the implementation using the NIST's
 * sample messages. The output should be:
 *
 *   ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad
 *   248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1
 *   cdc76e5c 9914fb92 81a1c7e2 84d73e67 f1809a48 a497200e 046d39cc c7112cd0
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */

#if HAVE_INTTYPES_H
# include <inttypes.h>
#else
# if HAVE_STDINT_H
#  include <stdint.h>
# endif
#endif

#include <string.h>

#include "Sha256.h"

#ifndef lint
static const char rcsid[] =
    "$Id: sha256.c 680 2003-07-25 21:57:49Z asaddi $";
#endif /* !lint */

#define ROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))

#define Ch(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define Maj(x, y, z) (((x) & ((y) | (z))) | ((y) & (z)))
#define SIGMA0(x) (ROTR((x), 2) ^ ROTR((x), 13) ^ ROTR((x), 22))
#define SIGMA1(x) (ROTR((x), 6) ^ ROTR((x), 11) ^ ROTR((x), 25))
#define sigma0(x) (ROTR((x), 7) ^ ROTR((x), 18) ^ ((x) >> 3))
#define sigma1(x) (ROTR((x), 17) ^ ROTR((x), 19) ^ ((x) >> 10))

#define DO_ROUND() { \
  t1 = h + SIGMA1(e) + Ch(e, f, g) + *(Kp++) + *(W++); \
  t2 = SIGMA0(a) + Maj(a, b, c); \
  h = g; \
  g = f; \
  f = e; \
  e = d + t1; \
  d = c; \
  c = b; \
  b = a; \
  a = t1 + t2; \
}

static const uint32_t K[64] = {
  0x428a2f98L, 0x71374491L, 0xb5c0fbcfL, 0xe9b5dba5L,
  0x3956c25bL, 0x59f111f1L, 0x923f82a4L, 0xab1c5ed5L,
  0xd807aa98L, 0x12835b01L, 0x243185beL, 0x550c7dc3L,
  0x72be5d74L, 0x80deb1feL, 0x9bdc06a7L, 0xc19bf174L,
  0xe49b69c1L, 0xefbe4786L, 0x0fc19dc6L, 0x240ca1ccL,
  0x2de92c6fL, 0x4a7484aaL, 0x5cb0a9dcL, 0x76f988daL,
  0x983e5152L, 0xa831c66dL, 0xb00327c8L, 0xbf597fc7L,
  0xc6e00bf3L, 0xd5a79147L, 0x06ca6351L, 0x14292967L,
  0x27b70a85L, 0x2e1b2138L, 0x4d2c6dfcL, 0x53380d13L,
  0x650a7354L, 0x766a0abbL, 0x81c2c92eL, 0x92722c85L,
  0xa2bfe8a1L, 0xa81a664bL, 0xc24b8b70L, 0xc76c51a3L,
  0xd192e819L, 0xd6990624L, 0xf40e3585L, 0x106aa070L,
  0x19a4c116L, 0x1e376c08L, 0x2748774cL, 0x34b0bcb5L,
  0x391c0cb3L, 0x4ed8aa4aL, 0x5b9cca4fL, 0x682e6ff3L,
  0x748f82eeL, 0x78a5636fL, 0x84c87814L, 0x8cc70208L,
  0x90befffaL, 0xa4506cebL, 0xbef9a3f7L, 0xc67178f2L
};

#ifndef RUNTIME_ENDIAN

#ifdef WORDS_BIGENDIAN

#define BYTESWAP(x) (x)
#define BYTESWAP64(x) (x)

#else /* WORDS_BIGENDIAN */

#define BYTESWAP(x) ((ROTR((x), 8) & 0xff00ff00L) | \
             (ROTL((x), 8) & 0x00ff00ffL))
#define BYTESWAP64(x) _byteswap64(x)

static inline uint64_t _byteswap64(uint64_t x)
{
  uint32_t a = x >> 32;
  uint32_t b = (uint32_t) x;
  return ((uint64_t) BYTESWAP(b) << 32) | (uint64_t) BYTESWAP(a);
}

#endif /* WORDS_BIGENDIAN */

#else /* !RUNTIME_ENDIAN */

#define BYTESWAP(x) _byteswap(sc->littleEndian, x)
#define BYTESWAP64(x) _byteswap64(sc->littleEndian, x)

#define _BYTESWAP(x) ((ROTR((x), 8) & 0xff00ff00L) | \
              (ROTL((x), 8) & 0x00ff00ffL))
#define _BYTESWAP64(x) __byteswap64(x)

static inline uint64_t __byteswap64(uint64_t x)
{
  uint32_t a = x >> 32;
  uint32_t b = (uint32_t) x;
  return ((uint64_t) _BYTESWAP(b) << 32) | (uint64_t) _BYTESWAP(a);
}

static inline uint32_t _byteswap(int littleEndian, uint32_t x)
{
  if (!littleEndian)
    return x;
  else
    return _BYTESWAP(x);
}

static inline uint64_t _byteswap64(int littleEndian, uint64_t x)
{
  if (!littleEndian)
    return x;
  else
    return _BYTESWAP64(x);
}

static inline void setEndian(int *littleEndianp)
{
  union {
    uint32_t w;
    uint8_t b[4];
  } endian;

  endian.w = 1L;
  *littleEndianp = endian.b[0] != 0;
}

#endif /* !RUNTIME_ENDIAN */

static const uint8_t padding[64] = {
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

void
SHA256Init (SHA256Context *sc)
{
#ifdef RUNTIME_ENDIAN
  setEndian (&sc->littleEndian);
#endif /* RUNTIME_ENDIAN */

  sc->totalLength = 0LL;
  sc->hash[0] = 0x6a09e667L;
  sc->hash[1] = 0xbb67ae85L;
  sc->hash[2] = 0x3c6ef372L;
  sc->hash[3] = 0xa54ff53aL;
  sc->hash[4] = 0x510e527fL;
  sc->hash[5] = 0x9b05688cL;
  sc->hash[6] = 0x1f83d9abL;
  sc->hash[7] = 0x5be0cd19L;
  sc->bufferLength = 0L;
}

static void
burnStack (int size)
{
  char buf[128];

  memset (buf, 0, sizeof (buf));
  size -= sizeof (buf);
  if (size > 0)
    burnStack (size);
}

static void
SHA256Guts (SHA256Context *sc, const uint32_t *cbuf)
{
  uint32_t buf[64];
  uint32_t *W, *W2, *W7, *W15, *W16;
  uint32_t a, b, c, d, e, f, g, h;
  uint32_t t1, t2;
  const uint32_t *Kp;
  int i;

  W = buf;

  for (i = 15; i >= 0; i--) {
    *(W++) = BYTESWAP(*cbuf);
    cbuf++;
  }

  W16 = &buf[0];
  W15 = &buf[1];
  W7 = &buf[9];
  W2 = &buf[14];

  for (i = 47; i >= 0; i--) {
    *(W++) = sigma1(*W2) + *(W7++) + sigma0(*W15) + *(W16++);
    W2++;
    W15++;
  }

  a = sc->hash[0];
  b = sc->hash[1];
  c = sc->hash[2];
  d = sc->hash[3];
  e = sc->hash[4];
  f = sc->hash[5];
  g = sc->hash[6];
  h = sc->hash[7];

  Kp = K;
  W = buf;

#ifndef SHA256_UNROLL
#define SHA256_UNROLL 1
#endif /* !SHA256_UNROLL */

#if SHA256_UNROLL == 1
  for (i = 63; i >= 0; i--)
    DO_ROUND();
#elif SHA256_UNROLL == 2
  for (i = 31; i >= 0; i--) {
    DO_ROUND(); DO_ROUND();
  }
#elif SHA256_UNROLL == 4
  for (i = 15; i >= 0; i--) {
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  }
#elif SHA256_UNROLL == 8
  for (i = 7; i >= 0; i--) {
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  }
#elif SHA256_UNROLL == 16
  for (i = 3; i >= 0; i--) {
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  }
#elif SHA256_UNROLL == 32
  for (i = 1; i >= 0; i--) {
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
    DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  }
#elif SHA256_UNROLL == 64
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
  DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
#else
#error "SHA256_UNROLL must be 1, 2, 4, 8, 16, 32, or 64!"
#endif

  sc->hash[0] += a;
  sc->hash[1] += b;
  sc->hash[2] += c;
  sc->hash[3] += d;
  sc->hash[4] += e;
  sc->hash[5] += f;
  sc->hash[6] += g;
  sc->hash[7] += h;
}

void
SHA256Update (SHA256Context *sc, const void *vdata, uint32_t len)
{
  const uint8_t *data = (const uint8_t*)vdata;
  uint32_t bufferBytesLeft;
  uint32_t bytesToCopy;
  int needBurn = 0;

#ifdef SHA256_FAST_COPY
  if (sc->bufferLength) {
    bufferBytesLeft = 64L - sc->bufferLength;

    bytesToCopy = bufferBytesLeft;
    if (bytesToCopy > len)
      bytesToCopy = len;

    memcpy (&sc->buffer.bytes[sc->bufferLength], data, bytesToCopy);

    sc->totalLength += bytesToCopy * 8L;

    sc->bufferLength += bytesToCopy;
    data += bytesToCopy;
    len -= bytesToCopy;

    if (sc->bufferLength == 64L) {
      SHA256Guts (sc, sc->buffer.words);
      needBurn = 1;
      sc->bufferLength = 0L;
    }
  }

  while (len > 63L) {
    sc->totalLength += 512L;

    SHA256Guts (sc, data);
    needBurn = 1;

    data += 64L;
    len -= 64L;
  }

  if (len) {
    memcpy (&sc->buffer.bytes[sc->bufferLength], data, len);

    sc->totalLength += len * 8L;

    sc->bufferLength += len;
  }
#else /* SHA256_FAST_COPY */
  while (len) {
    bufferBytesLeft = 64L - sc->bufferLength;

    bytesToCopy = bufferBytesLeft;
    if (bytesToCopy > len)
      bytesToCopy = len;

    memcpy (&sc->buffer.bytes[sc->bufferLength], data, bytesToCopy);

    sc->totalLength += bytesToCopy * 8L;

    sc->bufferLength += bytesToCopy;
    data += bytesToCopy;
    len -= bytesToCopy;

    if (sc->bufferLength == 64L) {
      SHA256Guts (sc, sc->buffer.words);
      needBurn = 1;
      sc->bufferLength = 0L;
    }
  }
#endif /* SHA256_FAST_COPY */

  if (needBurn)
    burnStack (sizeof (uint32_t[74]) + sizeof (uint32_t *[6]) + sizeof (int));
}

void
SHA256Final (SHA256Context *sc, uint8_t hash[SHA256_HASH_SIZE])
{
  uint32_t bytesToPad;
  uint64_t lengthPad;
  int i;

  bytesToPad = 120L - sc->bufferLength;
  if (bytesToPad > 64L)
    bytesToPad -= 64L;

  lengthPad = BYTESWAP64(sc->totalLength);

  SHA256Update (sc, padding, bytesToPad);
  SHA256Update (sc, &lengthPad, 8L);

  if (hash) {
    for (i = 0; i < SHA256_HASH_WORDS; i++) {
#ifdef SHA256_FAST_COPY
      *((uint32_t *) hash) = BYTESWAP(sc->hash[i]);
#else /* SHA256_FAST_COPY */
      hash[0] = (uint8_t) (sc->hash[i] >> 24);
      hash[1] = (uint8_t) (sc->hash[i] >> 16);
      hash[2] = (uint8_t) (sc->hash[i] >> 8);
      hash[3] = (uint8_t) sc->hash[i];
#endif /* SHA256_FAST_COPY */
      hash += 4;
    }
  }
}

#ifdef SHA256_TEST

#include <stdio.h>
#include <stdlib.h>

int
main (int argc, char *argv[])
{
  SHA256Context foo;
  uint8_t hash[SHA256_HASH_SIZE];
  char buf[1000];
  int i;

  SHA256Init (&foo);
  SHA256Update (&foo, "abc", 3);
  SHA256Final (&foo, hash);

  for (i = 0; i < SHA256_HASH_SIZE;) {
    printf ("%02x", hash[i++]);
    if (!(i % 4))
      printf (" ");
  }
  printf ("\n");

  SHA256Init (&foo);
  SHA256Update (&foo,
        "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
        56);
  SHA256Final (&foo, hash);

  for (i = 0; i < SHA256_HASH_SIZE;) {
    printf ("%02x", hash[i++]);
    if (!(i % 4))
      printf (" ");
  }
  printf ("\n");

  SHA256Init (&foo);
  memset (buf, 'a', sizeof (buf));
  for (i = 0; i < 1000; i++)
    SHA256Update (&foo, buf, sizeof (buf));
  SHA256Final (&foo, hash);

  for (i = 0; i < SHA256_HASH_SIZE;) {
    printf ("%02x", hash[i++]);
    if (!(i % 4))
      printf (" ");
  }
  printf ("\n");

  exit (0);
}

#endif /* SHA256_TEST */
07070100000049000081A400000000000000000000000167B98EA8000012EC000000000000000000000000000000000000004800000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/Sha256.h/*-
 * Copyright (c) 2001-2003 Allan Saddi <allan@saddi.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY ALLAN SADDI AND HIS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL ALLAN SADDI OR HIS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: sha256.h 348 2003-02-23 22:12:06Z asaddi $
 */
//
/////////// EXAMPLE /////////////////////////////////
//
// SHA256Context sha256;
// SHA256Init (&sha256);
//
// uint8_t* pBuffer = new uint8_t[SHA_BUFFER_SIZE + 7];
// // Ensure it is on a 64-bit boundary. 
// INTPTR offs;
// if ((offs = reinterpret_cast<INTPTR>(pBuffer) & 7L))
//    pBuffer += 8 - offs;
//
// unsigned int len;
//
// ifstream inFile("test.txt", ios::binary);
//
// for (;;)
// {
//    inFile.read( reinterpret_cast<char*>(pBuffer), SHA_BUFFER_SIZE );
//    len = inFile.gcount();
//
//    if ( len == 0)
//      break;
//
//    SHA256Update (&sha256, pBuffer, len);
// }
//
// uint8_t hash[SHA256_HASH_SIZE];
// SHA256Final (&sha256, hash);
//
// cout << "Hash: ";
// for (int i = 0; i < SHA256_HASH_SIZE; ++i)
//    printf ("%02x", hash[i]);
// cout << endl;


#ifndef _SHA256_H
#define _SHA256_H

// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------

/* Define to 1 if you have the <inttypes.h> header file. */
#ifndef WIN32
#define HAVE_INTTYPES_H 1
#endif

/* Define to 1 if you have the <memory.h> header file. */
#define HAVE_MEMORY_H 1

/* Define to 1 if you have the <stdint.h> header file. */
#ifndef WIN32
#define HAVE_STDINT_H 1
#endif

/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1

/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1

/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1

/* Define to 1 if you have the `strerror' function. */
#ifndef WIN32
#define HAVE_STRERROR 1
#endif

/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1

/* Define to 1 if you have the <sys/types.h> header file. */
#ifndef WIN32
#define HAVE_SYS_TYPES_H 1
#endif

/* Define to 1 if you have the <unistd.h> header file. */
#ifndef WIN32
#define HAVE_UNISTD_H 1
#endif

/* Define to 1 if you have the ANSI C header files. */
#define STDC_HEADERS 1

/* Define as `__inline' if that's what the C compiler calls it, or to nothing
   if it is not supported. */
#ifdef WIN32
#define inline __inline
#endif

/* Define to `unsigned' if <sys/types.h> does not define. */
/* #undef size_t */

#ifdef WIN32
#define uint64_t  unsigned __int64
#define uint32_t  unsigned int 
#define uint8_t   unsigned char
#endif // WIN32

#ifdef WIN32
#define INTPTR intptr_t
#else
#define INTPTR long
#endif

#define SHA_BUFFER_SIZE 65536

// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------


#if HAVE_INTTYPES_H
# include <inttypes.h>
#else
# if HAVE_STDINT_H
#  include <stdint.h>
# endif
#endif

#define SHA256_HASH_SIZE 32

/* Hash size in 32-bit words */
#define SHA256_HASH_WORDS 8

struct _SHA256Context {
  uint64_t totalLength;
  uint32_t hash[SHA256_HASH_WORDS];
  uint32_t bufferLength;
  union {
    uint32_t words[16];
    uint8_t bytes[64];
  } buffer;
#ifdef RUNTIME_ENDIAN
  int littleEndian;
#endif /* RUNTIME_ENDIAN */
};

typedef struct _SHA256Context SHA256Context;

#ifdef __cplusplus
extern "C" {
#endif

void SHA256Init (SHA256Context *sc);
void SHA256Update (SHA256Context *sc, const void *data, uint32_t len);
void SHA256Final (SHA256Context *sc, uint8_t hash[SHA256_HASH_SIZE]);

#ifdef __cplusplus
}
#endif

#endif /* !_SHA256_H */
0707010000004A000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000004700000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib0707010000004B000081A400000000000000000000000167B98EA8000064EF000000000000000000000000000000000000005500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/AacSource.cpp/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>
   Portions Copyright 2003-2005 M. Bakker, Nero AG, http://www.nero.com
      - Adapted from main.c found in the FAAD2 source tarball.

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "AacSource.h"
#include "AacSource_p.h"

#include <QFile>
#include <algorithm>
#include <cassert>
#include <limits>
#include <iostream>
#include <stdexcept>
#include <errno.h>
#include <string.h>


////////////////////////////////////////////////////////////////////////
//
// AAC_File
//
////////////////////////////////////////////////////////////////////////
AAC_File::AAC_File(const QString& fileName, int headerType)
    : m_fileName(fileName)
    , m_inBuf(NULL)
    , m_inBufSize(0)
    , m_decoder(0)
    , m_overflow(static_cast<unsigned char*>(malloc( sizeof(unsigned char) * 1024 )))
    , m_overflowSize(0)
    , m_header(headerType)
{
}


AAC_File::~AAC_File()
{
    // common
    if ( m_decoder )
    {
        NeAACDecClose( m_decoder );
        m_decoder = NULL;
    }
    if ( m_inBuf )
    {
        free( m_inBuf );
        m_inBufSize = 0;
        m_inBuf = NULL;
    }
    if ( m_overflow )
    {
        free( m_overflow );
        m_overflowSize = 0;
        m_overflow = NULL;
    }
}



////////////////////////////////////////////////////////////////////////
//
// AAC with ADTS or ADIF headers
//
////////////////////////////////////////////////////////////////////////


#define MAX_CHANNELS 6 // Output will get mixed down to 2 channels
#define ADTS_HEADER_SIZE 8

static int adts_sample_rates[] =
{
    96000,
    88200,
    64000,
    48000,
    44100,
    32000,
    24000,
    22050,
    16000,
    12000,
    11025,
     8000,
     7350,
        0,
        0,
        0
};

AAC_ADTS_File::AAC_ADTS_File( const QString& fileName, int headerType ) : AAC_File(fileName, headerType)
    , m_file( NULL )
    , m_adifSamplerate( 0 )
    , m_adifChannels( 0 )
{
}


AAC_ADTS_File::~AAC_ADTS_File()
{
    if ( m_file )
    {
        fclose( m_file );
    }
}


void AAC_ADTS_File::fillBuffer( FILE*& fp, unsigned char*& buf, size_t& bufSize, const size_t bytesConsumed )
{
    size_t bread;

    if ( bytesConsumed > 0 )
    {
        if ( bufSize )
            memmove( (void*)buf, (void*)(buf + bytesConsumed), bufSize*sizeof(unsigned char) );

        bread = fread( (void*)(buf + bufSize), 1, bytesConsumed, fp );
        bufSize += bread;

        if ( bufSize > 3 )
        {
            if ( memcmp( buf, "TAG", 3 ) == 0 )
                bufSize = 0;
        }
        if ( bufSize > 11 )
        {
            if ( memcmp( buf, "LYRICSBEGIN", 11 ) == 0 )
                bufSize = 0;
        }
        if ( bufSize > 8 )
        {
            if ( memcmp( buf, "APETAGEX", 8 ) == 0 )
                bufSize = 0;
        }
    }
}


void AAC_ADTS_File::parse( FILE*& fp, unsigned char*& buf, size_t& bufSize, int &bitrate, double &length )
{
    unsigned int frames, frame_length = 0;
    int t_framelength = 0;
    int samplerate = 0;
    double frames_per_sec, bytes_per_frame;

    // Read all frames to ensure correct time and bitrate
    for ( frames = 0; /* */; frames++ )
    {
        fillBuffer( fp, buf, bufSize, frame_length );

        if ( bufSize > 7 )
        {
            /* check syncword */
            if ( !( (buf[0] == 0xFF) && ((buf[1] & 0xF6) == 0xF0) ) )
                break;

            if ( frames == 0 )
                samplerate = adts_sample_rates[ (buf[2] & 0x3c) >> 2 ];

            frame_length = (  ((buf[3] & 0x3) << 11)
                            | ((buf[4]) << 3)
                            | (buf[5] >> 5) );

            t_framelength += frame_length - ADTS_HEADER_SIZE;

            if ( frame_length > bufSize )
                break;

            bufSize -= frame_length;
        }
        else
        {
            break;
        }
    }

    frames_per_sec = samplerate / 1024.0;

    if ( frames != 0 )
        bytes_per_frame = t_framelength / frames;
    else
        bytes_per_frame = 0;

    bitrate = static_cast<int>(8 * bytes_per_frame * frames_per_sec + 0.5);

    if ( frames_per_sec != 0 )
        length = frames / frames_per_sec;
    else
        length = 1;
}


int32_t AAC_ADTS_File::commonSetup( FILE*& fp, NeAACDecHandle& decoder, unsigned char*& buf, size_t& bufSize, uint32_t& samplerate, uint8_t& channels )
{
    samplerate = 0;
    channels = 0;

    fp = fopen(QFile::encodeName(m_fileName), "rb" );
    if( !fp )
    {
        std::cerr << "ERROR: Failed to open " << strerror( errno ) << std::endl;
        return -1;
    }

    if ( !(buf = static_cast<unsigned char*>( malloc(FAAD_MIN_STREAMSIZE*MAX_CHANNELS)) ) )
    {
        std::cerr << "Memory allocation error" << std::endl;
        fclose ( fp );
        return -1;
    }

    memset( buf, 0, FAAD_MIN_STREAMSIZE*MAX_CHANNELS );

    bufSize = fread( buf, 1, FAAD_MIN_STREAMSIZE * MAX_CHANNELS, fp );

    int tagsize = 0;
    if ( !memcmp( buf, "ID3", 3 ) )
    {
        /* high bit is not used */
        tagsize = (buf[6] << 21) | (buf[7] << 14) |
            (buf[8] <<  7) | (buf[9] <<  0);

        tagsize += 10;
        bufSize -= tagsize;
        fillBuffer( fp, buf, bufSize, tagsize );
    }

    decoder = NeAACDecOpen();

    /* Set configuration */
    NeAACDecConfigurationPtr config;
    config = NeAACDecGetCurrentConfiguration(decoder);
    config->outputFormat = FAAD_FMT_16BIT;
    config->downMatrix = 1; // Turn 5.1 channels into 2
    NeAACDecSetConfiguration( decoder, config);

    int32_t initval = 0;
    if ((initval = NeAACDecInit(decoder, buf,
        FAAD_MIN_STREAMSIZE*MAX_CHANNELS, &samplerate, &channels)) < 0)
    {
        std::cerr << "Error: could not set up AAC decoder" << std::endl;
        if ( buf )
            free( buf );
        buf = NULL;
        NeAACDecClose( decoder );
        decoder = NULL;
        fclose( fp );
        fp = NULL;
    }
    return initval;
}


bool AAC_ADTS_File::init()
{
    uint32_t initSamplerate = 0;
    uint8_t initChannels = 0;
    int32_t initval = commonSetup( m_file, m_decoder, m_inBuf, m_inBufSize, initSamplerate, initChannels );

     if ( initval >= 0 )
     {
        m_inBufSize -= initval;
        fillBuffer( m_file, m_inBuf, m_inBufSize, initval );

        // These two only needed for skipping AAC ADIF files
        m_adifSamplerate = initSamplerate;
        m_adifChannels = initChannels;

        return true;
     }

     throw std::runtime_error( "ERROR: Could not initialize AAC file reader!" );
     return false;
}


/*QString AAC_ADTS_File::getMbid()
{
    char out[MBID_BUFFER_SIZE];
    int const r = getMP3_MBID(QFile::encodeName(m_fileName), out);
    if ( r == 0 )
        return QString::fromLatin1( out );
    return QString();
}*/

void AAC_ADTS_File::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels )
{
    long fileread;
    uint32_t initSamplerate;
    uint8_t initChannels;
    double initLength = 0;
    unsigned char *tempBuf = NULL;
    size_t tempBufSize;
    FILE *fp = NULL;
    NeAACDecHandle decoder = NULL;
    commonSetup( fp, decoder, tempBuf, tempBufSize, initSamplerate, initChannels );

    long origpos = ftell( fp );
    fseek( fp, 0, SEEK_END );
    fileread = ftell( fp );
    fseek( fp, origpos, SEEK_SET );

    if ( (tempBuf[0] == 0xFF) && ((tempBuf[1] & 0xF6) == 0xF0) )
    {
        parse( fp, tempBuf, tempBufSize, bitrate, initLength );
    }
    else if (memcmp(tempBuf, "ADIF", 4) == 0)
    {
        int skip_size = (tempBuf[4] & 0x80) ? 9 : 0;
        bitrate = ((tempBuf[4 + skip_size] & 0x0F)<<19) |
            (tempBuf[5 + skip_size]<<11) |
            (tempBuf[6 + skip_size]<<3) |
            (tempBuf[7 + skip_size] & 0xE0);

        if ( fileread != 0)
        {
            initLength = static_cast<double>(fileread) * 8 / bitrate + 0.5;
        }
    }

    lengthSecs = static_cast<int>(initLength);
    nchannels = initChannels;
    samplerate = initSamplerate;

    if ( decoder )
        NeAACDecClose( decoder );
    if ( fp )
        fclose( fp );
    if ( tempBuf )
        free( tempBuf );
}


void AAC_ADTS_File::skip( const int mSecs )
{
    if ( m_header == AAC_ADTS )
    {
        // As AAC is VBR we need to check all ADTS headers to enable seeking...
        // There is no other solution
        unsigned char header[8];
        unsigned int frameCount, frameLength;
        double seconds = 0;

        // We need to find the ATDS syncword so rewind to the beginning
        // of the unprocessed data.
        if ( m_inBufSize > 0 )
        {
            fseek ( m_file, -m_inBufSize, SEEK_CUR );
            m_inBufSize = 0;
        }

        for( frameCount = 1; seconds * 1000 < mSecs; frameCount++ )
        {
            if ( fread( header, 1, ADTS_HEADER_SIZE, m_file ) != ADTS_HEADER_SIZE )
            {
                break;
            }
            if ( !strncmp( (char*)header, "ID3", 3 ) )
            {
                // high bit is not used
                unsigned char rest[2];
                fread( rest, 1, 2, m_file );
                int tagsize = (header[6] << 21) | (header[7] << 14) |
                    (rest[0] <<  7) | (rest[1] <<  0);

                fseek( m_file, tagsize, SEEK_CUR );
                fread( header, 1, ADTS_HEADER_SIZE, m_file );
            }
            if ( !((header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0)) )
            {
                std::cerr << "Error: Bad frame header; file may be corrupt!" << std::endl;
                break;
            }

            int samplerate = adts_sample_rates[ (header[2] & 0x3c) >> 2 ];
            frameLength = ( ( header[3] & 0x3 ) << 11 )
                          | ( header[4] << 3 )
                          | ( header[5] >> 5 );

            if ( samplerate > 0 )
                seconds += 1024.0 / samplerate;
            else
            {
                std::cerr << "Error: Bad frame header; file may be corrupt!" << std::endl;
                break;
            }

            if ( fseek( m_file, frameLength - ADTS_HEADER_SIZE, SEEK_CUR ) == -1 )
                break;
        }
        m_inBufSize = fread( m_inBuf, 1, FAAD_MIN_STREAMSIZE * MAX_CHANNELS, m_file );
    }
    else if ( m_header == AAC_ADIF )
    {
        // AAC ADIF is even worse.  There's only the one header at the
        // beginning of the file.  If you want to skip forward, you have to
        // decode block by block and check how far along you are.  Lovely, eh?

        unsigned long totalSamples = 0;
        void *sampleBuffer = NULL;

        do
        {
            NeAACDecFrameInfo frameInfo;
            sampleBuffer = NeAACDecDecode(m_decoder, &frameInfo, m_inBuf, static_cast<uint32_t>(m_inBufSize) );
            totalSamples += frameInfo.samples;
            if ( frameInfo.bytesconsumed > 0 )
            {
                m_inBufSize -= frameInfo.bytesconsumed;
                fillBuffer( m_file, m_inBuf, m_inBufSize, frameInfo.bytesconsumed );
            }
            if ( totalSamples >= ( mSecs * m_adifSamplerate * m_adifChannels / 1000 ) )
                break;
        } while ( sampleBuffer != NULL );
    }
}


void AAC_ADTS_File::postDecode(unsigned long bytesConsumed)
{
    m_inBufSize -= bytesConsumed;
    fillBuffer( m_file, m_inBuf, m_inBufSize, bytesConsumed );
}


////////////////////////////////////////////////////////////////////////
//
// AAC in an MP4 wrapper
//
////////////////////////////////////////////////////////////////////////


uint32_t read_callback( void *user_data, void *buffer, uint32_t length )
{
    return static_cast<uint32_t>(fread( buffer, 1, length, static_cast<FILE*>(user_data) ));
}


uint32_t seek_callback( void *user_data, uint64_t position )
{
    return fseek( static_cast<FILE*>(user_data), static_cast<long>(position), SEEK_SET );
}

AAC_MP4_File::AAC_MP4_File( const QString& fileName, int headerType ) : AAC_File(fileName, headerType)
    , m_mp4AudioTrack( -1 )
    , m_mp4SampleId( 0 )
    , m_mp4File ( NULL )
    , m_mp4cb ( NULL )
{
}

int32_t AAC_MP4_File::readSample()
{
    unsigned int bsize;
    int32_t rc = mp4ff_read_sample( m_mp4File, m_mp4AudioTrack, m_mp4SampleId, &m_inBuf,  &bsize );
    m_inBufSize = bsize;
    // Not necessarily an error.  Could just mean end of file.
    //if ( rc == 0 )
    //    std::cerr << "Reading samples failed." << std::endl;
    return rc;
}


int32_t AAC_MP4_File::getTrack( const mp4ff_t *f )
{
    // find AAC track
    int32_t numTracks = mp4ff_total_tracks( f );

    for ( int32_t i = 0; i < numTracks; i++ )
    {
        unsigned char *buff = NULL;
        unsigned int buff_size = 0;
        mp4AudioSpecificConfig mp4ASC;

        mp4ff_get_decoder_config( f, i, &buff, &buff_size );

        if ( buff )
        {
            int8_t rc = NeAACDecAudioSpecificConfig( buff, buff_size, &mp4ASC );
            free( buff );

            if ( rc < 0 )
                continue;
            return i;
        }
    }

    // can't decode this, probably DRM
    return -1;
}


bool AAC_MP4_File::commonSetup( NeAACDecHandle& decoder, mp4ff_callback_t*& cb, FILE*& fp, mp4ff_t*& mp4, int32_t& audioTrack )
{
    fp = fopen(QFile::encodeName(m_fileName), "rb");
    if ( !fp )
    {
        throw std::runtime_error( "Error: failed to open AAC file!" );
        return false;
    }

    decoder = NeAACDecOpen();

    // Set configuration
    NeAACDecConfigurationPtr config;
    config = NeAACDecGetCurrentConfiguration( decoder );
    config->outputFormat = FAAD_FMT_16BIT;
    config->downMatrix = 1; // Turn 5.1 channels into 2
    NeAACDecSetConfiguration( decoder, config );

    // initialise the callback structure
    cb = static_cast<mp4ff_callback_t*>( malloc( sizeof(mp4ff_callback_t) ) );

    cb->read = read_callback;
    cb->seek = seek_callback;
    cb->user_data = fp;

    mp4 = mp4ff_open_read( cb );

    if ( !mp4 )
    {
        // unable to open file
        free( cb );
        cb = NULL;
        NeAACDecClose( decoder );
        decoder = NULL;
        fclose( fp );
        fp = NULL;
        throw std::runtime_error( "Error: failed to set up AAC decoder!" );
        return false;
    }

    if ( ( audioTrack = getTrack( mp4 )) < 0 )
    {
        free( cb );
        cb = NULL;
        NeAACDecClose( decoder );
        decoder = NULL;
        fclose( fp );
        fp = NULL;
        mp4ff_close( mp4 );
        mp4 = NULL;
        audioTrack = 0;
        throw std::runtime_error( "Error: Unable to find an audio track. Is the file DRM protected?" );
        return false;
    }
    return true;
}


/*QString AAC_MP4_File::getMbid()
{
    int j = mp4ff_meta_get_num_items( m_mp4File );
    if ( j > 0 )
    {
        int k;
        for ( k = 0; k < j; k++ )
        {
            char *tag = NULL, *item = NULL;
            if ( mp4ff_meta_get_by_index( m_mp4File, k, &item, &tag ) )
            {
                if ( item != NULL && tag != NULL )
                {
                    QString key(item);
                    if ( key.toLower() == "musicbrainz track id" )
                    {
                        QString ret(tag);
                        free( item );
                        free( tag );
                        return ret;
                    }
                    free( item );
                    free( tag );
                }
            }
        }
    }
    return QString();
}*/


void AAC_MP4_File::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels )
{
    FILE* fp = NULL;
    mp4ff_callback_t *cb = NULL;
    NeAACDecHandle decoder = NULL;
    mp4ff_t* mp4 = NULL;
    int32_t audioTrack;

    bool success = commonSetup( decoder, cb, fp, mp4, audioTrack );

    if ( success )
    {
        // get basic file info
        mp4AudioSpecificConfig mp4ASC;
        unsigned char* buffer = NULL;
        unsigned int buffer_size = 0;
        double f = 1024.0;
        unsigned int framesize = 1024;

        int32_t samples = mp4ff_num_samples( mp4, audioTrack );

        if ( buffer )
        {
            if ( NeAACDecAudioSpecificConfig(buffer, buffer_size, &mp4ASC) >= 0 )
            {
                if ( mp4ASC.frameLengthFlag == 1 )
                    framesize = 960;
                if ( mp4ASC.sbr_present_flag == 1 )
                    framesize *= 2;
                if ( mp4ASC.sbr_present_flag == 1 )
                    f = f * 2.0;
            }
            free( buffer );
        }

        samplerate = mp4ff_get_sample_rate( mp4, audioTrack );
        if ( samplerate > 0 )
            lengthSecs = static_cast<int>(samples * f / samplerate + 0.5);
        bitrate = mp4ff_get_avg_bitrate( mp4, audioTrack );
        nchannels = mp4ff_get_channel_count( mp4, audioTrack );

        mp4ff_close( mp4 );
        NeAACDecClose( decoder );
        free( cb );
        fclose( fp );
    }
}


bool AAC_MP4_File::init()
{
    FILE* fp = NULL;

    bool success = commonSetup( m_decoder, m_mp4cb, fp, m_mp4File, m_mp4AudioTrack );
    if ( !success )
        return false;

    unsigned char* buffer = NULL;
    unsigned int buffer_size = 0;
    uint32_t samplerate;
    uint8_t channels;

    mp4ff_get_decoder_config( m_mp4File, m_mp4AudioTrack, &buffer, &buffer_size );

    if( NeAACDecInit2( m_decoder, buffer, buffer_size, &samplerate, &channels) < 0 )
    {
        // If some error initializing occured, skip the file
        if ( fp )
            fclose( fp );
        throw std::runtime_error( "Error: unable to initialize AAC decoder library!" );
        return false;
    }

    if ( buffer )
        free( buffer );

    return true;
}


void AAC_MP4_File::postDecode(unsigned long)
{
            free( m_inBuf );
            m_inBuf = NULL;
            m_mp4SampleId++;
}

void AAC_MP4_File::skip( const int mSecs )
{
    double dur = 0.0;
    int f = 1;
    unsigned char *buff = NULL;
    unsigned int buff_size = 0;
    uint32_t totalSamples = mp4ff_num_samples( m_mp4File, m_mp4AudioTrack );
    mp4AudioSpecificConfig mp4ASC;

    mp4ff_get_decoder_config( m_mp4File, m_mp4AudioTrack, &buff, &buff_size );

    if ( buff )
    {
        int8_t rc = NeAACDecAudioSpecificConfig( buff, buff_size, &mp4ASC );
        free( buff );
        if ( rc >= 0 && mp4ASC.sbr_present_flag == 1 )
            f = 2;

        // I think the f multiplier is needed here.
        while ( dur * 1000.0 * f / static_cast<double>(mp4ASC.samplingFrequency) < mSecs && m_mp4SampleId < totalSamples )
        {
            dur += mp4ff_get_sample_duration( m_mp4File, m_mp4AudioTrack, m_mp4SampleId );
            m_mp4SampleId++;
        }
    }
    else
        std::cerr << "Error: could not skip " << mSecs << " milliseconds" << std::endl;
}


AAC_MP4_File::~AAC_MP4_File()
{
    if ( m_mp4File )
        mp4ff_close( m_mp4File );
    if ( m_mp4cb )
    {
        free( m_mp4cb );
    }
}


////////////////////////////////////////////////////////////////////////
//
// AacSource
//
////////////////////////////////////////////////////////////////////////

AacSource::AacSource()
    : m_eof( false )
    , m_aacFile( NULL )
{}


AacSource::~AacSource()
{
    delete m_aacFile;
}


int AacSource::checkHeader()
{
    FILE *fp = NULL;
    unsigned char header[10];

    // check for mp4 file
    fp = fopen(QFile::encodeName(m_fileName), "rb");
    if ( !fp )
    {
        std::cerr << "Error: failed to open " << strerror( errno ) << std::endl;
        return AAC_File::AAC_UNKNOWN;
    }

    fread( header, 1, 10, fp );

    // MP4 headers
    if ( !memcmp( &header[4], "ftyp", 4 ) )
    {
        fclose( fp );
        return AAC_File::AAC_MP4;
    }

    // Skip id3 tags
    int tagsize = 0;
    if ( !memcmp( header, "ID3", 3 ) )
    {
        /* high bit is not used */
        tagsize = (header[6] << 21) | (header[7] << 14) |
            (header[8] <<  7) | (header[9] <<  0);

        tagsize += 10;
        fseek( fp, tagsize, SEEK_SET );
        fread( header, 1, 10, fp );
    }

    // Check for ADTS OR ADIF headers
    if ( (header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0) )
    {
        fclose( fp );
        return AAC_File::AAC_ADTS;
    }
    else if (memcmp(header, "ADIF", 4) == 0)
    {
        fclose( fp );
        return AAC_File::AAC_ADIF;
    }

    fclose( fp );
    return AAC_File::AAC_UNKNOWN;
}


void AacSource::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels )
{
    // get the header plus some other stuff..

    m_aacFile->getInfo( lengthSecs, samplerate, bitrate, nchannels );
}


void AacSource::init(const QString& fileName)
{
    m_fileName = fileName;
    
    int headerType = checkHeader();
    if ( headerType != AAC_File::AAC_UNKNOWN )
    {
        if ( headerType == AAC_File::AAC_MP4 )
            m_aacFile = new AAC_MP4_File(m_fileName, headerType);
        else
            m_aacFile = new AAC_ADTS_File( m_fileName, headerType );
    }    
    
    if ( m_aacFile )
        m_aacFile->init();
    else
        throw std::runtime_error( "ERROR: No suitable AAC decoder found!" );
}


/*QString AacSource::getMbid()
{
    QString mbid = m_aacFile->getMbid();
    return mbid;
}*/


void AacSource::skip( const int mSecs )
{
    if ( mSecs < 0 || !m_aacFile->m_decoder )
        return;

        m_aacFile->skip( mSecs );
}


void AacSource::skipSilence(double silenceThreshold /* = 0.0001 */)
{
    if ( !m_aacFile->m_decoder )
        return;

    silenceThreshold *= static_cast<double>( std::numeric_limits<short>::max() );

    for (;;)
    {
        if ( m_aacFile->m_header == AAC_File::AAC_MP4 )
        {
            if ( !static_cast<AAC_MP4_File*>(m_aacFile)->readSample() )
                break;
        }
        NeAACDecFrameInfo frameInfo;

        void* sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast<uint32_t>(m_aacFile->m_inBufSize) );

        m_aacFile->postDecode( frameInfo.bytesconsumed );

        if ( frameInfo.error > 0 )
        {
            break;
        }
        else if ( frameInfo.samples > 0 )
        {
            double sum = 0;
            int16_t *buf = static_cast<int16_t*>(sampleBuffer);
            switch ( frameInfo.channels )
            {
                case 1:
                    for (size_t j = 0; j < frameInfo.samples; ++j)
                        sum += abs( buf[j] );
                    break;
                case 2:
                    for (size_t j = 0; j < frameInfo.samples; j+=2)
                        sum += abs( (buf[j] >> 1) + (buf[j+1] >> 1) );
                    break;
            }
            if ( (sum >= silenceThreshold * static_cast<short>(frameInfo.samples/frameInfo.channels) ) )
                break;
        }
    }
}


int AacSource::updateBuffer( signed short *pBuffer, size_t bufferSize )
{
    size_t nwrit = 0; //number of samples written to the output buffer

    if ( m_aacFile->m_overflowSize > 0 )
    {
        size_t samples_to_use = bufferSize < m_aacFile->m_overflowSize ? bufferSize : m_aacFile->m_overflowSize;
        memcpy( pBuffer, m_aacFile->m_overflow, samples_to_use * sizeof(signed short) );
        nwrit += samples_to_use;
        m_aacFile->m_overflowSize -= samples_to_use;
        memmove( (void*)(m_aacFile->m_overflow), (void*)(m_aacFile->m_overflow + samples_to_use*sizeof(signed short)), samples_to_use*sizeof(signed short) );
    }

    if ( !m_aacFile->m_decoder )
        return 0;

    for (;;)
    {
        signed short* pBufferIt = pBuffer + nwrit;
        void* sampleBuffer;

        assert( nwrit <= bufferSize );

        if ( m_aacFile->m_header == AAC_File::AAC_MP4 )
        {
            if ( !static_cast<AAC_MP4_File*>(m_aacFile)->readSample() )
            {
                m_eof = true;
                return static_cast<int>(nwrit);
            }
        }
        NeAACDecFrameInfo frameInfo;

        sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast<uint32_t>(m_aacFile->m_inBufSize) );
        size_t samples_to_use = (bufferSize - nwrit) < frameInfo.samples ? bufferSize-nwrit : frameInfo.samples;

        if ( samples_to_use > 0 && sampleBuffer != NULL )
        {
            memcpy( pBufferIt, sampleBuffer, samples_to_use * sizeof(signed short) );
            nwrit += samples_to_use;
        }

        if ( samples_to_use < frameInfo.samples )
        {
            m_aacFile->m_overflow = static_cast<unsigned char*>(realloc( m_aacFile->m_overflow, (frameInfo.samples - samples_to_use) * sizeof(signed short) ) );
            memcpy( m_aacFile->m_overflow, static_cast<signed short*>(sampleBuffer) + samples_to_use, (frameInfo.samples - samples_to_use) * sizeof(signed short) );
            m_aacFile->m_overflowSize = frameInfo.samples - samples_to_use;
        }

        m_aacFile->postDecode( frameInfo.bytesconsumed );

        if ( sampleBuffer == NULL )
        {
            m_eof = true;
            break;
        }

        if ( frameInfo.error > 0 )
        {
            std::cerr << "Error: " << NeAACDecGetErrorMessage(frameInfo.error) << std::endl;
            break;
        }

        if ( nwrit == bufferSize )
            break;
   }

   return static_cast<int>(nwrit);
}
0707010000004C000081A400000000000000000000000167B98EA80000059E000000000000000000000000000000000000005300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/AacSource.h/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __AAC_SOURCE_H__
#define __AAC_SOURCE_H__

#include <lastfm/FingerprintableSource>

class AacSource : public lastfm::FingerprintableSource
{
public:
    AacSource();
    ~AacSource();

    virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels);
    virtual void init(const QString& fileName);
    virtual int updateBuffer(signed short* pBuffer, size_t bufferSize);
    virtual void skip(const int mSecs);
    virtual void skipSilence(double silenceThreshold = 0.0001);
    virtual bool eof() const { return m_eof; }

private:
    int checkHeader();
    QString m_fileName;
    bool m_eof;
    class AAC_File *m_aacFile;
};

#endif

0707010000004D000081A400000000000000000000000167B98EA800000BBF000000000000000000000000000000000000005500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/AacSource_p.h/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <faad.h>
#include <mp4ff.h>

class AAC_File
{
public:
    AAC_File(const QString&, int headerType);
    virtual ~AAC_File();
    virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) = 0;
    virtual bool init() = 0;
    //virtual QString getMbid() = 0;
    virtual void skip( const int mSecs ) = 0;
    virtual void postDecode(unsigned long) = 0;

    enum HeaderType
    {
        AAC_UNKNOWN = 0,
        AAC_ADIF,
        AAC_ADTS,
        AAC_MP4
    };

    QString m_fileName;
    unsigned char *m_inBuf;
    size_t m_inBufSize;
    NeAACDecHandle m_decoder;
    unsigned char *m_overflow;
    size_t m_overflowSize;
    int m_header;
};


class AAC_MP4_File : public AAC_File
{
public:
    AAC_MP4_File(const QString&, int headerType = AAC_MP4 );
    ~AAC_MP4_File();
    virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels );
    virtual bool init();
    //virtual QString getMbid();
    virtual void skip( const int mSecs );
    virtual void postDecode(unsigned long);
    int32_t readSample();

private:
    bool commonSetup( NeAACDecHandle& handle, mp4ff_callback_t*& cb, FILE*& fp, mp4ff_t*& mp4, int32_t& audioTrack );
    virtual int32_t getTrack( const mp4ff_t* f );
    int m_mp4AudioTrack;
    uint32_t m_mp4SampleId;
    mp4ff_t *m_mp4File;
    mp4ff_callback_t *m_mp4cb;
};


class AAC_ADTS_File : public AAC_File
{
public:
    AAC_ADTS_File( const QString& fileName, int headerType );
    ~AAC_ADTS_File();
    virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels );
    virtual bool init();
    //virtual QString getMbid();
    virtual void skip( const int mSecs );
    virtual void postDecode(unsigned long bytesconsumed );

private:
    int32_t commonSetup( FILE*& fp, NeAACDecHandle& decoder, unsigned char*& buf, size_t& bufSize, uint32_t& samplerate, uint8_t& channels );
    void parse( FILE*& fp, unsigned char*& buf, size_t& bufSize, int &bitrate, double &length );
    void fillBuffer( FILE*& fp, unsigned char*& buf, size_t& bufSize, const size_t m_bytesConsumed );

    FILE* m_file;
    // These two only needed for skipping AAC ADIF files
    uint32_t m_adifSamplerate;
    int m_adifChannels;
};
0707010000004E000081A400000000000000000000000167B98EA800002B9B000000000000000000000000000000000000005600000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/FlacSource.cpp/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "FlacSource.h"
#include <algorithm>
#include <cassert>
#include <errno.h>
#include <iostream>
#include <limits>
#include <algorithm>
#include <stdexcept>

#include <QFile>


FLAC__StreamDecoderWriteStatus FlacSource::_write_callback(const FLAC__StreamDecoder *, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
{
    assert(client_data != NULL);
    FlacSource *instance = reinterpret_cast<FlacSource *>(client_data);
    assert(instance != NULL);
    return instance->write_callback(frame, buffer);
}

FLAC__StreamDecoderWriteStatus FlacSource::write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[])
{
    m_outBufLen = 0;

    if ( m_outBuf )
    {
        size_t i;
        for(i = 0; i < frame->header.blocksize; i++)
        {
            switch ( m_channels )
            {
                case 1:
                    m_outBuf[m_outBufLen] = (FLAC__int16)buffer[0][i]; // mono
                    m_outBufLen++;
                    break;
                case 2:
                    m_outBuf[m_outBufLen] = (FLAC__int16)buffer[0][i]; // left channel
                    m_outBuf[m_outBufLen+1] = (FLAC__int16)buffer[1][i]; // right channel
                    m_outBufLen += 2;
                    break;
            }
        }
        m_samplePos += frame->header.blocksize;
    }

    return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

// ---------------------------------------------------------------------

void FlacSource::_metadata_callback(const FLAC__StreamDecoder *, const FLAC__StreamMetadata *metadata, void *client_data)
{
    assert(client_data != NULL);
    FlacSource *instance = reinterpret_cast<FlacSource *>(client_data);
    assert(instance != NULL);
    instance->metadata_callback(metadata);
}

void FlacSource::metadata_callback( const FLAC__StreamMetadata *metadata )
{
    switch ( metadata->type )
    {
        case FLAC__METADATA_TYPE_STREAMINFO:
            m_channels = metadata->data.stream_info.channels;
            m_totalSamples = metadata->data.stream_info.total_samples;
            m_samplerate = metadata->data.stream_info.sample_rate;
            m_bps = metadata->data.stream_info.bits_per_sample;
            m_maxFrameSize = metadata->data.stream_info.max_framesize;
            break;
        case FLAC__METADATA_TYPE_VORBIS_COMMENT:
            m_commentData = FLAC__metadata_object_clone(metadata);
            break;
        default:
            break;
    }
}

// ---------------------------------------------------------------------

void FlacSource::_error_callback(const FLAC__StreamDecoder *, FLAC__StreamDecoderErrorStatus status, void *client_data)
{
    assert(client_data != NULL);
    FlacSource *instance = reinterpret_cast<FlacSource *>(client_data);
    assert(instance != NULL);
    instance->error_callback(status);
}

void FlacSource::error_callback(FLAC__StreamDecoderErrorStatus status)
{
    std::cerr << "Got FLAC error: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl;
}

// ---------------------------------------------------------------------

FlacSource::FlacSource()
    : m_decoder( 0 )
    , m_fileSize( 0 )
    , m_outBuf( 0 )
    , m_outBufLen( 0 )
    , m_outBufPos( 0 )
    , m_samplePos( 0 )
    , m_maxFrameSize( 0 )
    , m_commentData( 0 )
    , m_bps( 0 )
    , m_channels( 0 )
    , m_samplerate( 0 )
    , m_totalSamples( 0 )
    , m_eof( false )
{
}

// ---------------------------------------------------------------------

FlacSource::~FlacSource()
{
    if ( m_decoder )
    {
        FLAC__stream_decoder_finish( m_decoder );
        FLAC__stream_decoder_delete( m_decoder );
    }
    if ( m_commentData )
        FLAC__metadata_object_delete( m_commentData );
    if ( m_outBuf )
        free( m_outBuf );
}

// ---------------------------------------------------------------------

void FlacSource::init(const QString& fileName)
{
    m_fileName = fileName;
    
    if ( !m_decoder )
    {
        FILE *f = fopen(QFile::encodeName(m_fileName), "rb" );
        if ( f )
        {
            // Need to check which init call to use; flac doesn't do that for us
            unsigned char header[35];
            bool isOgg = false;
            fread( header, 1, 35, f );
            if ( memcmp(header, "OggS", 4) == 0 &&
                 memcmp(&header[29], "FLAC", 4) == 0 )
                isOgg = true;

            // getInfo() will need this to calculate bitrate
            fseek( f, 0, SEEK_END );
            m_fileSize = ftell(f);

            rewind( f );

            m_decoder = FLAC__stream_decoder_new();
            FLAC__stream_decoder_set_metadata_respond(m_decoder, FLAC__METADATA_TYPE_STREAMINFO);
            FLAC__stream_decoder_set_metadata_respond(m_decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT);

            int init_status;
            if ( FLAC_API_SUPPORTS_OGG_FLAC && isOgg )
                init_status = FLAC__stream_decoder_init_ogg_FILE( m_decoder, f, _write_callback, _metadata_callback, _error_callback, this );
            else
                init_status = FLAC__stream_decoder_init_FILE( m_decoder, f, _write_callback, _metadata_callback, _error_callback, this );

            if(init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
                return;

            FLAC__stream_decoder_process_until_end_of_metadata( m_decoder );
            m_outBuf = static_cast<signed short*>(malloc( sizeof(signed short)*m_maxFrameSize));

            if ( m_bps != 16 )
            {
                FLAC__stream_decoder_finish( m_decoder );
                FLAC__stream_decoder_delete( m_decoder );
                FLAC__metadata_object_delete( m_commentData );
                m_decoder = 0;
                m_commentData = 0;
                throw std::runtime_error( "ERROR: only 16 bit FLAC files are currently supported!" );
            }
        }
        else
            throw std::runtime_error( "ERROR: cannot load FLAC file!" );
    }
}

// ---------------------------------------------------------------------

/*QString FlacSource::getMbid()
{
    if ( m_commentData )
    {
        FLAC__StreamMetadata_VorbisComment *vc;
        vc = &m_commentData->data.vorbis_comment;
        for ( unsigned int i = 0; i < vc->num_comments; ++i )
        {
            QByteArray key( (char*)(vc->comments[i].entry), vc->comments[i].length );
            if ( key.left(20).toLower() == "musicbrainz_trackid=" )
            {
                QString val = key.mid(20);
                return val;
            }
        }
    }

    return QString();
}*/

// ---------------------------------------------------------------------

void FlacSource::getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels)
{
    lengthSecs = 0;
    samplerate = 0;
    bitrate = 0;
    nchannels = 0;

    if ( m_decoder )
    {
        samplerate = m_samplerate;
        nchannels = m_channels;
        if ( samplerate > 0 )
            lengthSecs = static_cast<int>( static_cast<double>(m_totalSamples)/m_samplerate + 0.5);

        // Calcuate bitrate
        if ( lengthSecs > 0 )
        {
            FLAC__Metadata_SimpleIterator *it = FLAC__metadata_simple_iterator_new();
            FLAC__metadata_simple_iterator_init( it, QFile::encodeName(m_fileName), true, true );
            while( !FLAC__metadata_simple_iterator_is_last( it ) )
            {
                FLAC__metadata_simple_iterator_next( it );
            }
            off_t audioOffset = FLAC__metadata_simple_iterator_get_block_offset( it ) +
                FLAC__metadata_simple_iterator_get_block_length( it );
            FLAC__metadata_simple_iterator_delete( it );
            bitrate = static_cast<int>( static_cast<double>(m_fileSize - audioOffset) * 8 / lengthSecs + 0.5 );
        }
    }
}

// ---------------------------------------------------------------------

void FlacSource::skip( const int mSecs )
{
    FLAC__uint64 absSample = mSecs * m_samplerate / 1000 + m_samplePos;
    if ( !FLAC__stream_decoder_seek_absolute(m_decoder, absSample) )
        FLAC__stream_decoder_reset( m_decoder );
    m_outBufLen = 0;
}

// ---------------------------------------------------------------------

void FlacSource::skipSilence(double silenceThreshold /* = 0.0001 */)
{
    silenceThreshold *= static_cast<double>( std::numeric_limits<short>::max() );
    for ( ;; )
    {
        double sum = 0;
        bool result = FLAC__stream_decoder_process_single( m_decoder );
        // there was a fatal read
        if ( !result )
            break;

        switch ( m_channels )
        {
            case 1:
                for (size_t j = 0; j < m_outBufLen; ++j)
                    sum += abs( m_outBuf[j] );
                break;
            case 2:
                for ( size_t j = 0; j < m_outBufLen; j+=2 )
                    sum += abs( (m_outBuf[j] >> 1)
                                + (m_outBuf[j+1] >> 1) );
                break;
        }
        if ( (sum >= silenceThreshold * static_cast<double>(m_outBufLen) ) )
            break;
    }
    m_outBufLen = 0;
}

// ---------------------------------------------------------------------

int FlacSource::updateBuffer( signed short *pBuffer, size_t bufferSize )
{
    size_t nwrit = 0;

    for ( ;; )
    {
        size_t samples_to_use = std::min (bufferSize - nwrit, m_outBufLen - m_outBufPos);
        signed short* pBufferIt = pBuffer + nwrit;

        nwrit += samples_to_use;
        assert( nwrit <= bufferSize );
        memcpy( pBufferIt, m_outBuf + m_outBufPos, sizeof(signed short)*samples_to_use );

        if ( samples_to_use < m_outBufLen - m_outBufPos )
            m_outBufPos = samples_to_use;
        else
        {
            m_outBufPos = 0;
            bool result = FLAC__stream_decoder_process_single( m_decoder );
            // there was a fatal read
            if ( !result )
            {
                std::cerr << "Fatal error decoding FLAC" << std::endl;
                return 0;
            }
            else if ( FLAC__stream_decoder_get_state( m_decoder ) == FLAC__STREAM_DECODER_END_OF_STREAM )
            {
                m_eof = true;
                break;
            }
        }

        if ( nwrit == bufferSize )
            return static_cast<int>(nwrit);
    }
    return static_cast<int>(nwrit);
}

// -----------------------------------------------------------------------------

0707010000004F000081A400000000000000000000000167B98EA8000009F5000000000000000000000000000000000000005400000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/FlacSource.h/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __FLAC_SOURCE_H__
#define __FLAC_SOURCE_H__

#include <lastfm/FingerprintableSource>
#include <FLAC/stream_decoder.h>
#include <FLAC/metadata.h>


class FlacSource : public lastfm::FingerprintableSource
{
public:
    FlacSource();
    virtual ~FlacSource();

    virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels);
    virtual void init(const QString& fileName);

    // return a chunk of PCM data from the FLAC file
    virtual int updateBuffer(signed short* pBuffer, size_t bufferSize);

    virtual void skip(const int mSecs);
    virtual void skipSilence(double silenceThreshold = 0.0001);

    //QString getMbid();

    bool eof() const { return m_eof; }

private:
    static FLAC__StreamDecoderWriteStatus _write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data);
    static void _metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data);
    static void _error_callback(const ::FLAC__StreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *client_data);

    FLAC__StreamDecoderWriteStatus write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[]);
    void metadata_callback( const FLAC__StreamMetadata *metadata );
    void error_callback(FLAC__StreamDecoderErrorStatus status);

    FLAC__StreamDecoder *m_decoder;
    QString m_fileName;
    size_t m_fileSize;
    short *m_outBuf;
    size_t m_outBufLen;
    size_t m_outBufPos;
    FLAC__uint64 m_samplePos;
    unsigned m_maxFrameSize;
    FLAC__StreamMetadata* m_commentData;
    unsigned m_bps;
    unsigned m_channels;
    unsigned m_samplerate;
    FLAC__uint64 m_totalSamples;

    bool m_eof;
};

#endif

07070100000050000081A400000000000000000000000167B98EA8000038CC000000000000000000000000000000000000005500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/MadSource.cpp/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <fstream>
#include <limits>
#include <climits>
#include <cstdlib>
#include <sstream>
#include <cassert>
#include <stdexcept>
#include "MadSource.h"

#undef max // was definded in mad

using namespace std;


// -----------------------------------------------------------

MadSource::MadSource()
          : m_pMP3_Buffer ( new unsigned char[m_MP3_BufferSize+MAD_BUFFER_GUARD] )
{}

// -----------------------------------------------------------

MadSource::~MadSource()
{
   if ( m_inputFile.isOpen() )
   {
      m_inputFile.close();
      mad_synth_finish(&m_mad_synth);
      mad_frame_finish(&m_mad_frame);
      mad_stream_finish(&m_mad_stream);
   }
   if (m_pMP3_Buffer) delete[] m_pMP3_Buffer;
}

// ---------------------------------------------------------------------

inline short f2s(mad_fixed_t f)
{
   /* A fixed point number is formed of the following bit pattern:
   *
   * SWWWFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   * MSB                          LSB
   * S ==> Sign (0 is positive, 1 is negative)
   * W ==> Whole part bits
   * F ==> Fractional part bits
   *
   * This pattern contains MAD_F_FRACBITS fractional bits, one
   * should alway use this macro when working on the bits of a fixed
   * point number. It is not guaranteed to be constant over the
   * different platforms supported by libmad.
   *
   * The signed short value is formed, after clipping, by the least
   * significant whole part bit, followed by the 15 most significant
   * fractional part bits. Warning: this is a quick and dirty way to
   * compute the 16-bit number, madplay includes much better
   * algorithms.
   */

   /* Clipping */
   if(f >= MAD_F_ONE)
      return(SHRT_MAX);
   if(f <= -MAD_F_ONE)
      return(-SHRT_MAX);

   /* Conversion. */
   f = f >> (MAD_F_FRACBITS-15);
   return (signed short)f;
}

// ---------------------------------------------------------------------

string MadSource::MadErrorString(const mad_error& error)
{
   switch(error)
   {
      /* Generic unrecoverable errors. */
   case MAD_ERROR_BUFLEN:
      return("input buffer too small (or EOF)");
   case MAD_ERROR_BUFPTR:
      return("invalid (null) buffer pointer");
   case MAD_ERROR_NOMEM:
      return("not enough memory");

      /* Frame header related unrecoverable errors. */
   case MAD_ERROR_LOSTSYNC:
      return("lost synchronization");
   case MAD_ERROR_BADLAYER:
      return("reserved header layer value");
   case MAD_ERROR_BADBITRATE:
      return("forbidden bitrate value");
   case MAD_ERROR_BADSAMPLERATE:
      return("reserved sample frequency value");
   case MAD_ERROR_BADEMPHASIS:
      return("reserved emphasis value");

      /* Recoverable errors */
   case MAD_ERROR_BADCRC:
      return("CRC check failed");
   case MAD_ERROR_BADBITALLOC:
      return("forbidden bit allocation value");
   case MAD_ERROR_BADSCALEFACTOR:
      return("bad scalefactor index");
   case MAD_ERROR_BADFRAMELEN:
      return("bad frame length");
   case MAD_ERROR_BADBIGVALUES:
      return("bad big_values count");
   case MAD_ERROR_BADBLOCKTYPE:
      return("reserved block_type");
   case MAD_ERROR_BADSCFSI:
      return("bad scalefactor selection info");
   case MAD_ERROR_BADDATAPTR:
      return("bad main_data_begin pointer");
   case MAD_ERROR_BADPART3LEN:
      return("bad audio data length");
   case MAD_ERROR_BADHUFFTABLE:
      return("bad Huffman table select");
   case MAD_ERROR_BADHUFFDATA:
      return("Huffman data overrun");
   case MAD_ERROR_BADSTEREO:
      return("incompatible block_type for JS");

      /* Unknown error. This switch may be out of sync with libmad's
      * defined error codes.
      */
   default:
      return("Unknown error code");
   }
}


// -----------------------------------------------------------------------------

bool MadSource::isRecoverable(const mad_error& error, bool log)
{
   if (MAD_RECOVERABLE (error))
   {
      /* Do not print a message if the error is a loss of
      * synchronization and this loss is due to the end of
      * stream guard bytes. (See the comments marked {3}
      * supra for more informations about guard bytes.)
      */
      if (error != MAD_ERROR_LOSTSYNC   /*|| mad_stream.this_frame != pGuard */ && log)
      {
         cerr << "Recoverable frame level error: " 
              << MadErrorString(error) << endl;
      }

      return true;
   }
   else
   {
      if (error == MAD_ERROR_BUFLEN)
         return true;
      else
      {
         stringstream ss;

         ss << "Unrecoverable frame level error: " 
            << MadErrorString (error) << endl;
         throw ss.str();
      }
   }

   return false;
}

// -----------------------------------------------------------

void MadSource::init(const QString& fileName)
{
   m_inputFile.setFileName( m_fileName = fileName );
   bool fine = m_inputFile.open( QIODevice::ReadOnly );

   if ( !fine )
   {
      throw std::runtime_error ("Cannot load mp3 file!");
   }

   mad_stream_init(&m_mad_stream);
   mad_frame_init (&m_mad_frame);
   mad_synth_init (&m_mad_synth);
   mad_timer_reset(&m_mad_timer);

   m_pcmpos = m_mad_synth.pcm.length;
}

// -----------------------------------------------------------------------------

/*QString MadSource::getMbid()
{
    char out[MBID_BUFFER_SIZE];
    int const r = getMP3_MBID( QFile::encodeName( m_fileName ), out );
    if (r == 0)
        return QString::fromLatin1( out );
    return QString();
}*/

void MadSource::getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels )
{
   // get the header plus some other stuff..
   QFile inputFile(m_fileName);
   bool fine = inputFile.open( QIODevice::ReadOnly );

   if ( !fine )
   {
      throw std::runtime_error ("ERROR: Cannot load file for getInfo!");
      return;
   }

   unsigned char* pMP3_Buffer  = new unsigned char[m_MP3_BufferSize+MAD_BUFFER_GUARD];

   mad_stream   madStream;
   mad_header  madHeader;
   mad_timer_t  madTimer;

   mad_stream_init(&madStream);
   mad_timer_reset(&madTimer);

   double avgSamplerate = 0;
   double avgBitrate = 0;
   double avgNChannels = 0;
   int nFrames = 0;

   while ( fetchData( inputFile, pMP3_Buffer, m_MP3_BufferSize, madStream) )
   {
      if ( mad_header_decode(&madHeader, &madStream) != 0 )
      {
         if ( isRecoverable(madStream.error) )
            continue;
         else
            break;
      }

      mad_timer_add(&madTimer, madHeader.duration);

      avgSamplerate += madHeader.samplerate;
      avgBitrate += madHeader.bitrate;

      if ( madHeader.mode == MAD_MODE_SINGLE_CHANNEL )
         ++avgNChannels;
      else
         avgNChannels += 2;

      ++nFrames;
   }

   inputFile.close();
   mad_stream_finish(&madStream);
   mad_header_finish(&madHeader);
   delete[] pMP3_Buffer;


   lengthSecs = static_cast<int>(madTimer.seconds);
   samplerate = static_cast<int>( (avgSamplerate/nFrames) + 0.5 );
   bitrate = static_cast<int>( (avgBitrate/nFrames) + 0.5 );
   nchannels = static_cast<int>( (avgNChannels/nFrames) + 0.5 );
}

// -----------------------------------------------------------


bool MadSource::fetchData( QFile& mp3File,
                            unsigned char* pMP3_Buffer,
                            const int MP3_BufferSize,
                            mad_stream& madStream )
{
   unsigned char *pReadStart = NULL;
   unsigned char *pGuard = NULL;

   if ( madStream.buffer == NULL || 
        madStream.error == MAD_ERROR_BUFLEN )
   {

      size_t readSize;
      size_t remaining;

      /* {2} libmad may not consume all bytes of the input
      * buffer. If the last frame in the buffer is not wholly
      * contained by it, then that frame's start is pointed by
      * the next_frame member of the Stream structure. This
      * common situation occurs when mad_frame_decode() fails,
      * sets the stream error code to MAD_ERROR_BUFLEN, and
      * sets the next_frame pointer to a non NULL value. (See
      * also the comment marked {4} bellow.)
      *
      * When this occurs, the remaining unused bytes must be
      * put back at the beginning of the buffer and taken in
      * account before refilling the buffer. This means that
      * the input buffer must be large enough to hold a whole
      * frame at the highest observable bit-rate (currently 448
      * kb/s). XXX=XXX Is 2016 bytes the size of the largest
      * frame? (448000*(1152/32000))/8
      */
      if (madStream.next_frame != NULL)
      {
         remaining = madStream.bufend - madStream.next_frame;
         memmove (pMP3_Buffer, madStream.next_frame, remaining);

         pReadStart = pMP3_Buffer + remaining;
         readSize = MP3_BufferSize - remaining;
      }
      else
      {
         readSize = MP3_BufferSize;
         pReadStart = pMP3_Buffer;
         remaining = 0;
      }

      readSize = mp3File.read( reinterpret_cast<char*>(pReadStart), readSize );

      // nothing else to read!
      if (readSize <= 0) 
         return false;

      if ( mp3File.atEnd() )
      {
         pGuard = pReadStart + readSize;

         memset (pGuard, 0, MAD_BUFFER_GUARD);
         readSize += MAD_BUFFER_GUARD;
      }

      // Pipe the new buffer content to libmad's stream decoder facility.
      mad_stream_buffer( &madStream, pMP3_Buffer,
                         static_cast<unsigned int>(readSize + remaining));

      madStream.error = MAD_ERROR_NONE;
   }

   return true;
}

// -----------------------------------------------------------------------------

void MadSource::skipSilence(double silenceThreshold /* = 0.0001 */)
{
   mad_frame  madFrame;
   mad_synth    madSynth;

   mad_frame_init(&madFrame);
   mad_synth_init (&madSynth);

   silenceThreshold *= static_cast<double>( numeric_limits<short>::max() );

   for (;;)
   {
      if ( !fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream) )
         break;

      if ( mad_frame_decode(&madFrame, &m_mad_stream) != 0 )
      {
         if ( isRecoverable(m_mad_stream.error) )
            continue;
         else
            break;
      }

      mad_synth_frame (&madSynth, &madFrame);

      double sum = 0;

      switch (madSynth.pcm.channels)
      {
      case 1:
         for (size_t j = 0; j < madSynth.pcm.length; ++j)
            sum += abs(f2s(madSynth.pcm.samples[0][j]));
         break;
      case 2:
         for (size_t j = 0; j < madSynth.pcm.length; ++j)
            sum += abs(f2s(
                     (madSynth.pcm.samples[0][j] >> 1)
                   + (madSynth.pcm.samples[1][j] >> 1)));
         break;
      }

      if ( (sum >= silenceThreshold * madSynth.pcm.length) )
         break;
   }

   mad_frame_finish(&madFrame);
}

// -----------------------------------------------------------------------------

void MadSource::skip(const int mSecs)
{
   if ( mSecs <= 0 )
      return;

   mad_header  madHeader;
   mad_header_init(&madHeader);

   for (;;)
   {
      if (!fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream))
         break;

      if ( mad_header_decode(&madHeader, &m_mad_stream) != 0 )
      {
         if ( isRecoverable(m_mad_stream.error) )
            continue;
         else
            break;
      }
 
      mad_timer_add(&m_mad_timer, madHeader.duration);

      if ( mad_timer_count(m_mad_timer, MAD_UNITS_MILLISECONDS) >= mSecs )
         break;
   }

   mad_header_finish(&madHeader);
}

// -----------------------------------------------------------

int MadSource::updateBuffer(signed short* pBuffer, size_t bufferSize)
{
   size_t nwrit = 0; //number of samples written to the output buffer

   for (;;)
   {
      // get a (valid) frame
      // m_pcmpos == 0 could mean two things
      // - we have completely decoded a frame, but the output buffer is still
      //   not full (it would make more sense for pcmpos == pcm.length(), but
      //   the loop assigns pcmpos = 0 at the end and does it this way!
      // - we are starting a stream
      if ( m_pcmpos == m_mad_synth.pcm.length )
      {
         if ( !fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream) )
         {
            break; // nothing else to read
         }

         // decode the frame
         if (mad_frame_decode (&m_mad_frame, &m_mad_stream))
         {
            if ( isRecoverable(m_mad_stream.error) )
               continue;
            else
               break;
         } // if (mad_frame_decode (&madFrame, &madStream))

         mad_timer_add (&m_mad_timer, m_mad_frame.header.duration);
         mad_synth_frame (&m_mad_synth, &m_mad_frame);

         m_pcmpos = 0;
      }

      size_t samples_for_mp3 = m_mad_synth.pcm.length - m_pcmpos;
      size_t samples_for_buf = bufferSize - nwrit;
      signed short* pBufferIt = pBuffer + nwrit;
      size_t i = 0, j = 0;

      switch( m_mad_synth.pcm.channels )
      {
      case 1:
         {
            size_t samples_to_use = min (samples_for_mp3, samples_for_buf);
            for (i = 0; i < samples_to_use; ++i )
               pBufferIt[i] = f2s( m_mad_synth.pcm.samples[0][i+m_pcmpos] );
         }
         j = i;
         break;

      case 2:
         for (; i < samples_for_mp3 && j < samples_for_buf ; ++i, j+=2 )
         {
            pBufferIt[j]   = f2s( m_mad_synth.pcm.samples[0][i+m_pcmpos] );
            pBufferIt[j+1] = f2s( m_mad_synth.pcm.samples[1][i+m_pcmpos] );
         }
         break;

      default:
         cerr << "wtf kind of mp3 has " << m_mad_synth.pcm.channels << " channels??\n";
         break;
      }

      m_pcmpos += i;
      nwrit += j;

      assert( nwrit <= bufferSize );

      if (nwrit == bufferSize) 
         return static_cast<int>(nwrit);
   }

   return static_cast<int>(nwrit);
}

// -----------------------------------------------------------------------------

07070100000051000081A400000000000000000000000167B98EA800000866000000000000000000000000000000000000005300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/MadSource.h/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef __MP3_SOURCE_H__
#define __MP3_SOURCE_H__

#include <lastfm/FingerprintableSource>
#include <QFile>
#include <string>
#include <vector>
#include <fstream>
#include <mad.h>


class MadSource : public lastfm::FingerprintableSource
{
public:
    MadSource();
    ~MadSource();

    virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels);
    virtual void init(const QString& fileName);
    virtual int updateBuffer(signed short* pBuffer, size_t bufferSize);
    virtual void skip(const int mSecs);
    virtual void skipSilence(double silenceThreshold = 0.0001);
    virtual bool eof() const { return m_inputFile.atEnd(); }

private:
    static bool fetchData( QFile& mp3File,
                           unsigned char* pMP3_Buffer,
                           const int MP3_BufferSize,
                           mad_stream& madStream );

    static bool isRecoverable(const mad_error& error, bool log = false);

    static std::string MadErrorString(const mad_error& error);

    struct mad_stream    m_mad_stream;
    struct mad_frame     m_mad_frame;
    mad_timer_t          m_mad_timer;
    struct mad_synth     m_mad_synth;

    QFile                m_inputFile;

    unsigned char*       m_pMP3_Buffer;
    static const int     m_MP3_BufferSize = (5*8192);
    QString              m_fileName;

    size_t               m_pcmpos;
};

#endif
07070100000052000081A400000000000000000000000167B98EA800001817000000000000000000000000000000000000005800000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/VorbisSource.cpp/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "VorbisSource.h"
#include <QFile>
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <errno.h>

// These specify the output format
static const int wordSize = 2; // 16 bit output
static const int isSigned = 1;
#if __BIG_ENDIAN__
static const int isBigEndian = 1;
#else
static const int isBigEndian = 0;
#endif


VorbisSource::VorbisSource()
    : m_channels( 0 )
    , m_samplerate( 0 )
    , m_eof( false )
{
    memset( &m_vf, 0, sizeof(m_vf) );
}

// ---------------------------------------------------------------------

VorbisSource::~VorbisSource()
{
    // ov_clear() also closes the file
    ov_clear( &m_vf );
}

// ---------------------------------------------------------------------

void VorbisSource::init(const QString& fileName)
{
    m_fileName = fileName;
    
    if ( m_vf.datasource )
    {
        std::cerr << "Warning: file already appears to be open";
        return;
    }

    FILE *fp = fopen(QFile::encodeName(m_fileName), "rb" );
    if( !fp )
        throw std::runtime_error( "ERROR: Cannot open ogg file!" );

    // See the warning about calling ov_open on Windows
    if ( ov_test_callbacks( fp, &m_vf, NULL, 0, OV_CALLBACKS_DEFAULT ) < 0 )
    {
        fclose( fp );
        throw std::runtime_error( "ERROR: This is not an ogg vorbis file!" );
    }

    ov_test_open( &m_vf );

    // Don't fingerprint files with more than one logical bitstream
    // They most likely contain more than one track
    if ( ov_streams( &m_vf ) != 1 )
        throw std::runtime_error( "ERROR: ogg file contains multiple bitstreams" );

    m_channels = ov_info( &m_vf, 0 )->channels;
    m_samplerate = static_cast<int>(ov_info( &m_vf, 0 )->rate);
    m_eof = false;
}

void VorbisSource::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels)
{
    // stream info
    nchannels = ov_info( &m_vf, -1 )->channels;
    samplerate = static_cast<int>(ov_info( &m_vf, -1 )->rate);
    lengthSecs = static_cast<int>(ov_time_total( &m_vf, -1 ) + 0.5);
    bitrate = static_cast<int>(ov_bitrate( &m_vf, -1 ));
}

// ---------------------------------------------------------------------

void VorbisSource::skip( const int mSecs )
{
    if ( mSecs < 0 )
        return;

    double ts = mSecs / 1000.0 + ov_time_tell( &m_vf );
    ov_time_seek( &m_vf, ts );
}

// ---------------------------------------------------------------------

void VorbisSource::skipSilence(double silenceThreshold /* = 0.0001 */)
{
    silenceThreshold *= static_cast<double>( std::numeric_limits<short>::max() );

    char sampleBuffer[4096];
    int bs = 0;
    for (;;)
    {
        long charReadBytes = ov_read( &m_vf, sampleBuffer, 4096, isBigEndian, wordSize, isSigned, &bs );

        // eof
        if ( !charReadBytes )
        {
            m_eof = true;
            break;
        }
        if ( charReadBytes < 0 )
        {
            // a bad bit of data: OV_HOLE || OV_EBADLINK
            continue;
        }
        else if ( charReadBytes > 0 )
        {
            double sum = 0;
            int16_t *buf = reinterpret_cast<int16_t*>(sampleBuffer);
            switch ( m_channels )
            {
                case 1:
                    for (long j = 0; j < charReadBytes/wordSize; j++)
                        sum += abs( buf[j] );
                    break;
                case 2:
                    for (long j = 0; j < charReadBytes/wordSize; j+=2)
                        sum += abs( (buf[j] >> 1) + (buf[j+1] >> 1) );
                    break;
            }
            if ( sum >= silenceThreshold * static_cast<double>(charReadBytes/wordSize/m_channels) )
                break;
        }
    }
}

// ---------------------------------------------------------------------

int VorbisSource::updateBuffer( signed short *pBuffer, size_t bufferSize )
{
    char buf[ bufferSize * wordSize ];
    int bs = 0;
    size_t charwrit = 0; //number of samples written to the output buffer

    for (;;)
    {
        long charReadBytes = ov_read( &m_vf, buf, static_cast<int>(bufferSize * wordSize - charwrit),
                                      isBigEndian, wordSize, isSigned, &bs );
        if ( !charReadBytes )
        {
            m_eof = true;
            break; // nothing else to read
        }

        // Don't really need this though since we're excluding files that have
        // more than one logical bitstream
        if ( bs != 0 )
        {
            vorbis_info *vi = ov_info( &m_vf, -1 );
            if ( m_channels != vi->channels || m_samplerate != vi->rate )
            {
                std::cerr << "Files that change channel parameters or samplerate are currently not supported" << std::endl;
                return 0;
            }
        }

        if( charReadBytes < 0 )
        {
            std::cerr << "Warning: corrupt section of data, attempting to continue..." << std::endl;
            continue;
        }

        char* pBufferIt = reinterpret_cast<char*>(pBuffer) + charwrit;
        charwrit += charReadBytes;

        assert( charwrit <= bufferSize * wordSize );
        memcpy( pBufferIt, buf, charReadBytes );

      if (charwrit == bufferSize * wordSize)
         return static_cast<int>(charwrit/wordSize);
   }

   return static_cast<int>(charwrit/wordSize);
}

// -----------------------------------------------------------------------------

07070100000053000081A400000000000000000000000167B98EA8000005D3000000000000000000000000000000000000005600000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/VorbisSource.h/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __VORBIS_SOURCE_H__
#define __VORBIS_SOURCE_H__

#include <FingerprintableSource.h>
#include <vorbis/vorbisfile.h>


class VorbisSource : public lastfm::FingerprintableSource
{
public:
    VorbisSource();
    ~VorbisSource();
    virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels);
    virtual void init(const QString& fileName);
    virtual int updateBuffer(signed short* pBuffer, size_t bufferSize);
    virtual void skip(const int mSecs);
    virtual void skipSilence(double silenceThreshold = 0.0001);
    virtual bool eof() const { return m_eof; }

private:
    OggVorbis_File m_vf;
    QString m_fileName;
    int m_channels;
    int m_samplerate;
    bool m_eof;
};

#endif
07070100000054000081A400000000000000000000000167B98EA8000012CA000000000000000000000000000000000000005000000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/contrib/main.cpp/*
   Copyright 2009 Last.fm Ltd. 
   Copyright 2009 John Stamp <jstamp@users.sourceforge.net>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

// ubuntu 9.04: sudo apt-get install libmad0-dev libvorbis-dev libflac-dev libfaac-dev
// macports: sudo port install libmad libvorbis libflac
// Windows: lol

#include "MadSource.h"
#include "VorbisSource.h"
#include "FlacSource.h"
#include "AacSource.h"
#include <lastfm.h>
#include <QCoreApplication>
#include <QFile>
#include <QStringList>
#include <iostream>
int typeOf(const QString& path);
lastfm::FingerprintableSource* factory(int type);
enum { MP3, OGG, FLAC, AAC, UNKNOWN };
namespace lastfm { Track taglib(const QString& path); }


int main(int argc, char** argv) try
{
    if (argc < 2) {
        std::cerr << "usage: " << argv[0] << " path" << std::endl;
        return 1;
    }
 
    QCoreApplication app(argc, argv);
    QEventLoop loop;
    
    QString const path = QFile::decodeName(argv[1]);
    
    lastfm::Track t = lastfm::taglib(path); //see contrib //TODO mbid
    lastfm::Fingerprint fp(t);
    if (fp.id().isNull()) {
        lastfm::FingerprintableSource* src = factory(typeOf(path));
        fp.generate(src);
        QNetworkReply* reply = fp.submit();
        loop.connect(reply, SIGNAL(finished()), SLOT(quit()));
        fp.decode(reply);
    }
    
    QNetworkReply* reply = fp.id().getSuggestions();
    loop.connect(reply, SIGNAL(finished()), SLOT(quit()));
    
    std::cout << reply->readAll().data() << std::endl; //returns XML
    return 0;
}
catch (std::exception& e)
{
    std::cerr << e.what() << std::endl;
}

lastfm::FingerprintableSource* factory(int type)
{
    switch (type) {
        case MP3: return new MadSource;
        case OGG: return new VorbisSource;
        case FLAC: return new FlacSource;
    #ifndef MACPORTS_SUCKS
        case AAC: return new AacSource;
    #endif
        default: throw std::runtime_error("Cannot handle filetype");
    }
}

int typeOf(const QString& fileName)
{
    QStringList parts = fileName.split( "." );
    QString extension;
    if ( parts.size() > 1 )
        extension = parts.last();

    // Let's be trusting about extensions
    if ( extension.toLower() == "mp3" )
        return MP3;
    else if ( extension.toLower() == "ogg" )
        return OGG;
    else if ( extension.toLower() == "oga" )
        return FLAC;
    else if ( extension.toLower() == "flac" )
        return FLAC;
    else if ( extension.toLower() == "aac" )
        return AAC;
    else if ( extension.toLower() == "m4a" )
        return AAC;

    // So much for relying on extensions.  Let's try file magic instead.
    FILE *fp = NULL;
    unsigned char header[35];

    fp = fopen(QFile::encodeName(fileName), "rb");
    if ( !fp )
    {
        return UNKNOWN;
    }
    int fType = UNKNOWN;
    fread( header, 1, 35, fp );

    // Some formats can have ID3 tags (or not), so let's just
    // get them out of the way first before we check what we have.
    if ( memcmp( header, "ID3", 3) == 0 )
    {
        int tagsize = 0;
        /* high bit is not used */
        tagsize = (header[6] << 21) | (header[7] << 14) |
            (header[8] <<  7) | (header[9] <<  0);

        tagsize += 10;
        fseek( fp, tagsize, SEEK_SET );
        fread( header, 1, 35, fp );
    }

    if ( (header[0] == 0xFF) && ((header[1] & 0xFE) == 0xFA ) )
    {
        fType = MP3;
    }
    else if ( memcmp(header, "OggS", 4) == 0 )
    {
        if ( memcmp(&header[29], "vorbis", 6) == 0 )
        {
            // ogg vorbis (.ogg)
            fType = OGG;
        }
        else if ( memcmp(&header[29], "FLAC", 4) == 0 )
        {
            // ogg flac (.oga)
            fType = FLAC;
        }
    }
    else if ( memcmp(header, "fLaC", 4 ) == 0 )
    {
        // flac file
        fType = FLAC;
    }
    else if ( (header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0) )
    {
        // aac adts
        fType = AAC;
    }
    else if (memcmp(header, "ADIF", 4) == 0)
    {
        // aac adif
        fType = AAC;
    }
    else if ( memcmp( &header[4], "ftyp", 4 ) == 0 )
    {
        // mp4 header: aac
        fType = AAC;
    }

    fclose(fp);
    return fType;
}
07070100000055000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000004500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib07070100000056000081A400000000000000000000000167B98EA800001A2B000000000000000000000000000000000000005500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib/CircularArray.h/*
   Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __CIRCULAR_ARRAY_H
#define __CIRCULAR_ARRAY_H

#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
#include <cstdlib> // for memset
#include <algorithm> // for max

#ifndef NULL
#define NULL 0
#endif

template< typename T >
class CircularArray 
{

public:

   typedef size_t size_type;

   /////////////////////////////////////////////////////////////

   // IMPORTANT: The distance must be redefined!!
   // See declaration of iterator from stl_iterator_base_types.h:
   //   template<typename _Category, typename _Tp, typename _Distance = ptrdiff_t,
   //            typename _Pointer = _Tp*, typename _Reference = _Tp&>
   //   struct iterator { ...

   // ---------- Forward declarations

   class iterator : 
      public std::iterator<std::random_access_iterator_tag, T>
   {
      // it should be by default because is an inner class, but I put it just to be sure..
      friend class CircularArray;

   private:
      iterator( size_type idx, T* pData, size_type size ) : _idx(idx), _pData(pData), _size(size) {}

   public:

      //typedef random_access_iterator_tag iterator_category;

      iterator() : _idx(0), _pData(NULL) {}

      iterator& operator++()
      { // preincrement
         _idx = (_idx + 1) % _size;
         return (*this);
      }

      iterator operator++(int)
      { // postincrement
         iterator _Tmp = *this;
         _idx = (_idx + 1) % _size;
         return (_Tmp);
      }

      void operator+=(size_type offs)
      {
         this->_idx = (_idx + offs) % _size;
      }

      iterator operator+(size_type offs) const 
      {
         size_type newIdx = (_idx + offs) % _size;
         iterator _Tmp(newIdx, _pData, _size);
         return _Tmp;
      }

      // return the distance between this iterator and it
      size_t operator-(const iterator& it) const
      {
         if ( this->_idx > it._idx )
            return this->_idx - it._idx;
         else
            return this->_idx + (_size - it._idx);
      }

      iterator operator-(size_type offs) const
      {
         size_type newIdx;

         if ( offs <= _idx )
            newIdx = _idx - offs;
         else
            newIdx = _size - ((_idx - offs) % _size); // note: should be ok, but to be checked better

         iterator _Tmp(newIdx, _pData, _size);
         return _Tmp;
      }

      iterator& operator--()
      { // predecrement
         if (_idx == 0)
            _idx = _size - 1;
         else
            --_idx;
         return (*this);
      }

      iterator operator--(int)
      { // postdecrement
         iterator _Tmp = *this;
         if (_idx == 0)
            _idx = _size - 1;
         else
            --_idx;
         return (_Tmp);
      }

      T& operator*() const
      { // return designated object
         return _pData[_idx];
      }

      T* operator->() const
      { // return pointer to class object
         return &_pData[_idx];
      }    

      /*    T& operator=(const T& right)
      { // assign reference right to _val
      return ( this->_idx = right._idx );
      }*/    

      bool operator==(const iterator& right) const
      { // test for iterator equality
         return ( this->_idx == right._idx );
      }

      bool operator!=(const iterator& right) const
      { // test for iterator inequality
         return ( this->_idx != right._idx );
      }

   protected:
      size_type  _idx;
      T*         _pData;
      size_type  _size;
   };

   /////////////////////////////////////////////////////////////


   CircularArray()
      : _headIdx(0), _pData(NULL), _size(0)
   { }

   CircularArray( size_type size )
      : _headIdx(0), _pData(NULL)
   {
      this->resize(size);
   }

   CircularArray( size_type size, const T& init )
      : _headIdx(0), _pData(NULL)
   {
      this->resize(size, init);
   }

   ~CircularArray()
   {
      this->clear();
   }

   // remember: it is not working (yet!) with negative numbers!
   T& operator[](size_type offset)
   {    
      return _pData[ (_headIdx + offset) % _size ];
   }

   void resize( size_type size )
   {
      _headIdx = 0;
      if ( size == _size )
         return;

      this->clear();
      _pData = new T[size];
      _size = size;
   }

   void resize( size_type size, const T& init )
   {
      this->resize(size, false);
      this->fill(init);
   }

   void fill( const T& val )
   {
      for (size_type i=0; i<_size; ++i)
         _pData[i] = val;
   }

   void zero_fill()
   {
      memset( _pData, 0, _size * sizeof(T) );
   }

   bool empty() const
   {
      return ( _pData == NULL );
   }

   void clear()
   {
      if (_pData)
         delete [] _pData;
      _pData = NULL;
      _headIdx = 0;
      _size = 0;
   }

   iterator head() const
   {
      if (_pData == NULL)
         std::cerr << "WARNING: iterator in CircularArray points to an empty CircularArray" << std::endl;
      return iterator(_headIdx, _pData, _size);
   }

   void shift_head( int offset )
   {
      if ( offset < 0)
      {
         int mod = (-offset) % (int)_size;
         mod -= (int)_headIdx;
         _headIdx = _size - mod;
      }
      else
         _headIdx = (_headIdx + offset) % _size;
   }

   size_type size() const
   {
      return _size;
   }

   //// to be changed to an input forward iterator
   //template <typename TIterator>
   //void get_data( TIterator toFillIt, size_type size = 0 )
   //{
   //  if ( size == 0 )
   //    size = _size;
   //  iterator it = head();
   //  
   //  for (size_type i = 0; i < size; ++i)
   //    *(toFillIt++) = *(it++);
   //}

   // IMPORTANT! Destination buffer MUST be the same size!
   void copy_buffer( T* pDest )
   {
      memcpy( pDest, _pData, sizeof(T)*_size );
   }

   // returns the buffer
   T* get_buffer() const
   {
      return _pData;
   }


private:

   size_type  _headIdx; // index
   T*         _pData;   // array of data
   size_type  _size;    // size of data

};

#endif // __CIRCULAR_ARRAY_H
07070100000057000081A400000000000000000000000167B98EA800000E31000000000000000000000000000000000000005000000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib/Filter.cpp/*
   Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <algorithm> // for max
#include <vector>

#include "Filter.h"
#include "fp_helper_fun.h"

using namespace std;

namespace fingerprint
{

Filter::Filter(unsigned int id, float threshold, float weight) 
: id(id), threshold(threshold), weight(weight)
{
    float time_rate = 1.5;
        
    unsigned int t = 1;
    vector<unsigned int> time_lengths;

    while (t < KEYWIDTH) 
   {
        time_lengths.push_back(t);
      t = max( static_cast<unsigned int>( round__(time_rate*t) ) +
               static_cast<unsigned int>( round__(time_rate*t) % 2),
               t+1 );
    }

    unsigned int filter_count = 0;

    for (wt = 1; wt <= time_lengths.size(); wt++) 
   {
        for (wb = 1; wb <= NBANDS; wb++) 
      {
            for (first_band = 1; first_band <= NBANDS - wb + 1;
                 first_band++) 
         {
                unsigned int time = time_lengths[wt-1];
                filter_count++;

                if (filter_count == id) 
            {
                    wt = time_lengths[wt-1];
                    filter_type = 1;
                    return;
                }

                if (time > 1) 
            {
                    filter_count++;
                    if (filter_count == id) 
               {
                        wt = time_lengths[wt-1];
                        filter_type = 2;
                        return;
                    }
                }
                    
                if (wb > 1) 
            {
                    filter_count++;
                    if (filter_count == id) 
               {
                        wt = time_lengths[wt-1];
                        filter_type = 3;
                        return;
                    }
                }

                if (time > 1 && wb > 1) 
            {
                    filter_count++;
                    if (filter_count == id) 
               {
                        wt = time_lengths[wt-1];
                        filter_type = 4;
                        return;
                    }
                }
                
                if (time > 3) 
            {
                    filter_count++;
                    if (filter_count == id) 
               {
                        wt = time_lengths[wt-1];
                        filter_type = 5;
                        return;
                    }
                }
                
                if (wb > 3) 
            {
                    filter_count++;
                    if (filter_count == id) 
               {
                        wt = time_lengths[wt-1];
                        filter_type = 6;
                        return;
                    }
                }

            } // for first_band
        } // for wb
    } // for wt
}

} // end of namespace fingerprint

// -----------------------------------------------------------------------------
07070100000058000081A400000000000000000000000167B98EA800000574000000000000000000000000000000000000004E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib/Filter.h/*
   Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __FILTER_H
#define __FILTER_H

namespace fingerprint
{

struct Filter 
{
    /// Constructs a new filter with id.
    Filter(unsigned int id, float threshold, float weight);
   
   unsigned int id; //< filter id
    unsigned int wt; //< time width
    unsigned int first_band; //< first band
    unsigned int wb; //< band width
    unsigned int filter_type; //< filter type
    
    float threshold; //< threshold for filter
    float weight; //< filter weight

   // number of frames in time
   static const unsigned int KEYWIDTH = 100;
   // number of bands to divide the signal (log step)
   static const unsigned int NBANDS = 33;
};

}

#endif // __FILTER_H
07070100000059000081A400000000000000000000000167B98EA80000755D000000000000000000000000000000000000005E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib/FingerprintExtractor.cpp/*
   Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <limits>
#include <bitset>
#include <deque>
#include <vector>
#include <stdexcept>
#include <cmath>
#include <cstring>

#include <samplerate.h> // libsamplerate

#include "FingerprintExtractor.h"
#include "fp_helper_fun.h" // for GroupData
#include "Filter.h"
#include "FloatingAverage.h"
#include "OptFFT.h"

//////////////////////////////////////////////////////////////////////////

namespace fingerprint
{

using namespace std;
static const int NUM_FRAMES_CLIENT = 32; // ~= 10 secs.

enum eProcessType
{
   PT_UNKNOWN,
   PT_FOR_QUERY,
   PT_FOR_FULLSUBMIT
};

//////////////////////////////////////////////////////////////////////////

class PimplData
{

public:

   PimplData()
   : m_pDownsampledPCM(NULL), m_pDownsampledCurrIt(NULL),
     m_normalizedWindowMs(static_cast<unsigned int>(NORMALIZATION_SKIP_SECS * 1000 * 2)),
     m_compensateBufferSize(FRAMESIZE-OVERLAPSAMPLES + Filter::KEYWIDTH * OVERLAPSAMPLES),
     m_downsampledProcessSize(NUM_FRAMES_CLIENT*FRAMESIZE),
     // notice that the buffer has extra space on either side for the normalization window  
     m_fullDownsampledBufferSize( m_downsampledProcessSize + // the actual processed part
                                  m_compensateBufferSize +  // a compensation buffer for the fft
                                ((m_normalizedWindowMs * DFREQ / 1000) / 2) ), // a compensation buffer for the normalization
     m_normWindow(m_normalizedWindowMs * DFREQ / 1000),
     m_pFFT(NULL), m_pDownsampleState(NULL), m_processType(PT_UNKNOWN)
   {
      m_pFFT            = new OptFFT(m_downsampledProcessSize + m_compensateBufferSize);
      m_pDownsampledPCM = new float[m_fullDownsampledBufferSize];

      // the end of ||-------m_bufferSize-------|-cb-|---norm/2---|| 
      //                                                           ^-- pEndDownsampledBuf
      m_pEndDownsampledBuf = m_pDownsampledPCM + m_fullDownsampledBufferSize;

      // loading filters
      size_t numFilters = sizeof(rFilters) / sizeof(RawFilter) ;
      for (size_t i = 0; i < numFilters; ++i)
         m_filters.push_back( Filter( rFilters[i].ftid, rFilters[i].thresh, rFilters[i].weight ) );

   }

   ~PimplData()
   {
      if ( m_pFFT )
         delete m_pFFT;
      m_pFFT = NULL;
      if ( m_pDownsampledPCM )
         delete [] m_pDownsampledPCM;
      m_pDownsampledPCM = NULL;

      if ( m_pDownsampleState )
         src_delete(m_pDownsampleState) ;

   }

   float*                 m_pDownsampledPCM;
   float*                 m_pDownsampledCurrIt;

   const unsigned int     m_normalizedWindowMs;
   const size_t           m_compensateBufferSize;
   const size_t           m_downsampledProcessSize;
   const size_t           m_fullDownsampledBufferSize;

   FloatingAverage<double> m_normWindow;
   OptFFT*                 m_pFFT;

   //////////////////////////////////////////////////////////////////////////
   
   // libsamplerate
   SRC_STATE*              m_pDownsampleState;
   SRC_DATA                m_downsampleData;

   vector<float>           m_floatInData;

   //////////////////////////////////////////////////////////////////////////


   bool                   m_groupsReady;
   bool                   m_preBufferPassed;

   eProcessType           m_processType;

   size_t                 m_toSkipSize;
   size_t                 m_toSkipMs;

   size_t                 m_skippedSoFar;
   bool                   m_skipPassed;

   float*                 m_pEndDownsampledBuf;

   int m_freq;
   int m_nchannels;

   unsigned int m_lengthMs;
   int          m_minUniqueKeys;
   unsigned int m_uniqueKeyWindowMs;

   unsigned int m_toProcessKeys;
   unsigned int m_totalWindowKeys;
   
   vector<Filter>     m_filters;

   deque<GroupData>   m_groupWindow;
   vector<GroupData>  m_groups;
   unsigned int       m_processedKeys;

   vector<unsigned int>   m_partialBits; // here just to avoid reallocation

#if __BIG_ENDIAN__

#define reorderbits(X)  ((((unsigned int)(X) & 0xff000000) >> 24) | \
                        (((unsigned int)(X) & 0x00ff0000) >> 8)  | \
                        (((unsigned int)(X) & 0x0000ff00) << 8)  | \
                        (((unsigned int)(X) & 0x000000ff) << 24))

   vector<GroupData>  m_bigEndianGroups;
#endif
};

//////////////////////////////////////////////////////////////////////////

void initCustom( PimplData& pd,
                 int freq, int nchannels,
                 unsigned int lengthMs, unsigned int skipMs,
                 int minUniqueKeys, unsigned int uniqueKeyWindowMs, int duration );

inline float getRMS( const FloatingAverage<double>& signal );
unsigned int processKeys( deque<GroupData>& groups, size_t size, PimplData& pd );
void         integralImage( float** ppFrames, unsigned int nFrames );
void         computeBits( vector<unsigned int>& bits,
                          const vector<Filter>& f, 
                          float ** frames, unsigned int nframes );


void src_short_to_float_and_mono_array(const short *in, float *out, int srclen, int nchannels);

//////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------

FingerprintExtractor::FingerprintExtractor()
: m_pPimplData(NULL)
{
   m_pPimplData = new PimplData();
}

// -----------------------------------------------------------------------------

FingerprintExtractor::~FingerprintExtractor()
{
   if ( m_pPimplData )
      delete m_pPimplData;
}

// -----------------------------------------------------------------------------

size_t FingerprintExtractor::getToSkipMs() 
{ return m_pPimplData->m_toSkipMs; }

// -----------------------------------------------------------------------------

size_t FingerprintExtractor::getMinimumDurationMs()
{
   return static_cast<size_t>( (QUERY_SIZE_SECS + NORMALIZATION_SKIP_SECS * 2 + GUARD_SIZE_SECS) * 1000 );
}

// -----------------------------------------------------------------------------

size_t FingerprintExtractor::getVersion()
{ return FINGERPRINT_LIB_VERSION; }

// -----------------------------------------------------------------------------

void FingerprintExtractor::initForQuery(int freq, int nchannels, int duration )
{
   m_pPimplData->m_skipPassed = false;
   m_pPimplData->m_processType = PT_FOR_QUERY;

   if ( !m_pPimplData )
      throw std::runtime_error("Not enough RAM to allocate the fingerprinter!");

   initCustom( *m_pPimplData,
               freq, nchannels,
               static_cast<unsigned int>(QUERY_SIZE_SECS * 1000),
               static_cast<unsigned int>(QUERY_START_SECS * 1000), 
               MIN_UNIQUE_KEYS, 
               static_cast<unsigned int>(UPDATE_SIZE_SECS * 1000), duration );
}

// -----------------------------------------------------------------------------

void FingerprintExtractor::initForFullSubmit(int freq, int nchannels )
{
   m_pPimplData->m_skipPassed = true;
   m_pPimplData->m_processType = PT_FOR_FULLSUBMIT;

   if ( !m_pPimplData )
      throw std::runtime_error("Not enough RAM to allocate the fingerprinter!");

   initCustom( *m_pPimplData, 
               freq, nchannels, 
               numeric_limits<unsigned int>::max(), 
               0, MIN_UNIQUE_KEYS, 0, -1 );
}

// -----------------------------------------------------------------------------

void initCustom( PimplData& pd, 
                 int freq, int nchannels,
                 unsigned int lengthMs, 
                 unsigned int skipMs, 
                 int minUniqueKeys, 
                 unsigned int uniqueKeyWindowMs, int duration )
{
   //////////////////////////////////////////////////////////////////////////
   pd.m_freq = freq;
   pd.m_nchannels = nchannels;
   pd.m_lengthMs = lengthMs;
   pd.m_minUniqueKeys = minUniqueKeys;
   pd.m_uniqueKeyWindowMs = uniqueKeyWindowMs;
   //////////////////////////////////////////////////////////////////////////

   // ***********************************************************************
   if ( pd.m_pDownsampleState )
      pd.m_pDownsampleState = src_delete(pd.m_pDownsampleState) ;
   pd.m_pDownsampleState = src_new (SRC_SINC_FASTEST, 1, NULL) ;
   pd.m_downsampleData.src_ratio = FDFREQ / freq;
   // ***********************************************************************

   //////////////////////////////////////////////////////////////////////////
   if ( pd.m_processType == PT_FOR_FULLSUBMIT ) 
      skipMs = 0; // make sure
   else if ( duration > 0 )
   {
      // skip + size + right normalization window + FFT guard
      // 
      int stdDurationMs = static_cast<int>((QUERY_START_SECS + QUERY_SIZE_SECS + NORMALIZATION_SKIP_SECS + GUARD_SIZE_SECS) * 1000);
      int actualDurationMs = duration * 1000;
      // compute the actual skipMs depending on the duration
      if ( actualDurationMs < stdDurationMs )
         skipMs -= max( stdDurationMs - actualDurationMs, 0 );
   }

   pd.m_toSkipMs = max( static_cast<int>(skipMs) - static_cast<int>((pd.m_normalizedWindowMs/2)), 0 );
   pd.m_toSkipSize = static_cast<size_t>( freq * nchannels * 
                                          (pd.m_toSkipMs / 1000.0) ); // half the norm window in secs;

   //if ( pd.m_processType == PT_FOR_QUERY && skipMs > pd.m_normalizedWindowMs/2 )
   //{
   //   pd.m_toSkipMs = skipMs - (pd.m_normalizedWindowMs/2);
   //   pd.m_toSkipSize = static_cast<size_t>( freq * nchannels * 
   //                                         (pd.m_toSkipMs / 1000.0) ); // half the norm window in secs
   //}
   //else
   //{
   //   pd.m_toSkipMs = 0;
   //   pd.m_toSkipSize = 0; // half of the normalization window will be skipped in ANY case
   //}

   pd.m_skippedSoFar = 0;
   pd.m_groupsReady = false;
   pd.m_preBufferPassed = false;

   // prepare the position for pre-buffering
   pd.m_pDownsampledCurrIt = pd.m_pDownsampledPCM + (pd.m_downsampledProcessSize - (pd.m_normWindow.size() / 2) ); 

   pd.m_toProcessKeys = fingerprint::getTotalKeys(pd.m_lengthMs);//  (m_lengthMs * DFREQ) / (1000 * OVERLAPSAMPLES) + 1;
   pd.m_totalWindowKeys = fingerprint::getTotalKeys(pd.m_uniqueKeyWindowMs); //(m_uniqueKeyWindowMs * DFREQ) / (1000 * OVERLAPSAMPLES) + 1;

   if (pd.m_toProcessKeys == 1)
      pd.m_toProcessKeys = 0;
   if (pd.m_totalWindowKeys == 1)
      pd.m_totalWindowKeys = 0;

   pd.m_processedKeys = 0;

   pd.m_groupWindow.clear();
   pd.m_processedKeys = 0;
}

// -----------------------------------------------------------------------------


// * cb = compensate buffer size
// * norm = floating normalization window size
//
// PREBUFFER:
//     (-------m_bufferSize-------)
//    ||    EMPTY    |---norm/2---|-cb-|---norm/2---||
// 1.                {--------read frames-----------}
// 2.                {--read normalize window--}      
// 3.                             {----}   normalize
//
// 1. read [norm + cb] frames to m_bufferSize - norm/2
// 2. read [m_buffersize - norm/2...m_buffersize + norm/2] into normalize window
// 3. normalize [m_bufferSize..m_bufferSize+cb]
//
// PROCESS:
//
//     ||-------m_bufferSize-------|-cb-|---norm/2---||
// 1.   <--------------------------{------copy-------}
// 2.                    {--------read frames-------}
// 3.        {---------normalize--------}
// 4.   {------fft/process/whatevs------}
//
// 1. copy [m_bufferSize..m_bufferSize + cb + norm/2] to beginning
// 2. read m_bufferSize frames to cb + norm/2
// 3. normalize [cb..m_bufferSize+cb]
// 4. fft/process/whatevs [0...m_bufferSize+cb]
//
// repeat until enough blocks processed and enough groups!
//
bool FingerprintExtractor::process( const short* pPCM, size_t num_samples, bool end_of_stream )
{
   if ( num_samples == 0 )
      return false;

   // easier read
   PimplData& pd = *m_pPimplData;

   if ( pd.m_processType == PT_UNKNOWN )
      throw std::runtime_error("Please call initForQuery() or initForFullSubmit() before process()!");

   const short* pSourcePCMIt = pPCM;
   const short* pSourcePCMIt_end = pPCM + num_samples;

   if ( !pd.m_skipPassed )
   {
      // needs to skip data? (reminder: the query needs to skip QUERY_START_SECS (- half of the normalization window)
      if ( pd.m_skippedSoFar + num_samples > pd.m_toSkipSize )
      {
         pSourcePCMIt = pPCM + (pd.m_toSkipSize - pd.m_skippedSoFar);
         pd.m_skipPassed = true;
      }
      else
      {
         // need more data
         pd.m_skippedSoFar += num_samples;
         return false;
      }
   }

   pair<size_t, size_t> readData(0,0);
   pd.m_downsampleData.end_of_input = end_of_stream ? 1 : 0;

   //////////////////////////////////////////////////////////////////////////
   // PREBUFFER:
   if ( !pd.m_preBufferPassed )
   {
      // 1. downsample [norm + cb] frames to m_bufferSize - norm/2
      pd.m_floatInData.resize( (pSourcePCMIt_end - pSourcePCMIt) / pd.m_nchannels);
      src_short_to_float_and_mono_array( pSourcePCMIt, 
                                         &(pd.m_floatInData[0]), static_cast<int>(pSourcePCMIt_end - pSourcePCMIt), 
                                         pd.m_nchannels);

      pd.m_downsampleData.data_in = &(pd.m_floatInData[0]);
      pd.m_downsampleData.input_frames = static_cast<long>(pd.m_floatInData.size());

      pd.m_downsampleData.data_out = pd.m_pDownsampledCurrIt;
      pd.m_downsampleData.output_frames = static_cast<long>(pd.m_pEndDownsampledBuf - pd.m_pDownsampledCurrIt);

      int err = src_process(pd.m_pDownsampleState, &(pd.m_downsampleData));
      if ( err )
         throw std::runtime_error( src_strerror(err) );

      pd.m_pDownsampledCurrIt += pd.m_downsampleData.output_frames_gen;

      if ( pd.m_pDownsampledCurrIt != pd.m_pEndDownsampledBuf )
         return false; // NEED MORE DATA

      pSourcePCMIt += pd.m_downsampleData.input_frames_used * pd.m_nchannels;

      size_t pos = pd.m_downsampledProcessSize;
      size_t window_pos = pd.m_downsampledProcessSize - pd.m_normWindow.size() / 2;
      const size_t end_window_pos = window_pos + pd.m_normWindow.size();

      // 2. read [m_buffersize - norm/2...m_buffersize + norm/2] into normalize window
      for (; window_pos < end_window_pos ; ++window_pos)
         pd.m_normWindow.add(pd.m_pDownsampledPCM[window_pos] * pd.m_pDownsampledPCM[window_pos]);

      // 3. normalize [m_bufferSize..m_bufferSize+cb]
      for (; pos < pd.m_downsampledProcessSize + pd.m_compensateBufferSize; ++pos, ++window_pos)
      {
         pd.m_pDownsampledPCM[pos] /= getRMS(pd.m_normWindow);
         pd.m_normWindow.add(pd.m_pDownsampledPCM[window_pos] * pd.m_pDownsampledPCM[window_pos]);
      }

      pd.m_preBufferPassed = true;
   }

   //////////////////////////////////////////////////////////////////////////
   // PROCESS:

   bool found_enough_unique_keys = false;
   while (pd.m_toProcessKeys == 0 || pd.m_processedKeys < pd.m_toProcessKeys || !found_enough_unique_keys)
   {

      // 1. copy [m_bufferSize..m_bufferSize + cb + norm/2] to beginning
      if ( pd.m_pDownsampledCurrIt == pd.m_pEndDownsampledBuf )
      {
         memcpy( pd.m_pDownsampledPCM, pd.m_pDownsampledPCM + pd.m_downsampledProcessSize,
                (pd.m_compensateBufferSize + (pd.m_normWindow.size() / 2)) * sizeof(float));
         pd.m_pDownsampledCurrIt = pd.m_pDownsampledPCM + (pd.m_compensateBufferSize + (pd.m_normWindow.size() / 2));
      }

      // 2. read m_bufferSize frames to cb + norm/2
      pd.m_floatInData.resize( (pSourcePCMIt_end - pSourcePCMIt) / pd.m_nchannels);

      if ( pd.m_floatInData.empty() )
         return false;

      src_short_to_float_and_mono_array( pSourcePCMIt, 
                                         &(pd.m_floatInData[0]), static_cast<int>(pSourcePCMIt_end - pSourcePCMIt), 
                                         pd.m_nchannels);

      pd.m_downsampleData.data_in = &(pd.m_floatInData[0]);
      pd.m_downsampleData.input_frames = static_cast<long>(pd.m_floatInData.size());

      pd.m_downsampleData.data_out = pd.m_pDownsampledCurrIt;
      pd.m_downsampleData.output_frames = static_cast<long>(pd.m_pEndDownsampledBuf - pd.m_pDownsampledCurrIt);

      int err = src_process(pd.m_pDownsampleState, &(pd.m_downsampleData));
      if ( err )
         throw std::runtime_error( src_strerror(err) );

      pd.m_pDownsampledCurrIt += pd.m_downsampleData.output_frames_gen;

      if ( pd.m_pDownsampledCurrIt != pd.m_pEndDownsampledBuf && !end_of_stream )
         return false; // NEED MORE DATA

      //pSourcePCMIt += readData.second;
      pSourcePCMIt += pd.m_downsampleData.input_frames_used * pd.m_nchannels;

      // ********************************************************************

      // 3. normalize [cb..m_bufferSize+cb]
      size_t pos = static_cast<unsigned int>(pd.m_compensateBufferSize);
      size_t window_pos = static_cast<unsigned int>(pd.m_compensateBufferSize + (pd.m_normWindow.size() / 2));

      for(; pos < pd.m_downsampledProcessSize + pd.m_compensateBufferSize /* m_fullDownsampledBufferSize*/; ++pos, ++window_pos)
      {
         pd.m_pDownsampledPCM[pos] /= getRMS(pd.m_normWindow);
         pd.m_normWindow.add(pd.m_pDownsampledPCM[window_pos] * pd.m_pDownsampledPCM[window_pos]);
      }

      // 4. fft/process/whatevs [0...m_bufferSize+cb]
      pd.m_processedKeys += processKeys(pd.m_groupWindow, pos, pd);

      // we have too many keys, now we have to chop either one end or the other
      if (pd.m_toProcessKeys != 0 && pd.m_processedKeys > pd.m_toProcessKeys)
      {
         // set up window begin and end
         deque<GroupData>::iterator itBeg = pd.m_groupWindow.begin(), itEnd = pd.m_groupWindow.end();
         unsigned int offset_left, offset_right;

         found_enough_unique_keys = 
            fingerprint::findSignificantGroups( itBeg, itEnd, offset_left, offset_right, pd.m_toProcessKeys,
                                                pd.m_totalWindowKeys, pd.m_minUniqueKeys);

         // if we're happy with this set, snip the beginning and end of the grouped keys
         if (found_enough_unique_keys)
         {
            itBeg->count -= offset_left;
            if (offset_right > 0 && itEnd != pd.m_groupWindow.end())
            {
               itEnd->count = offset_right;
               ++itEnd;
            }
         }

         // chop the deque
         copy(itBeg, itEnd, pd.m_groupWindow.begin());
         pd.m_groupWindow.resize(itEnd - itBeg);            

         // recalc keys
         pd.m_processedKeys = 0;
         for (deque<GroupData>::const_iterator it = pd.m_groupWindow.begin(); it != pd.m_groupWindow.end(); ++it)
            pd.m_processedKeys += it->count;
      }

      if ( end_of_stream )
         break;

   } // while (totalKeys == 0 || keys < totalKeys || !found_enough_unique_keys)


   if (pd.m_toProcessKeys != 0 && pd.m_processedKeys < pd.m_toProcessKeys)
      throw std::runtime_error("Couldn't deliver the requested number of keys (it's the file too short?)");

   if ((pd.m_toProcessKeys != 0 && !found_enough_unique_keys) || 
       (pd.m_toProcessKeys == 0 && !enoughUniqueGoodGroups(pd.m_groupWindow.begin(), pd.m_groupWindow.end(), pd.m_minUniqueKeys)))
   {
      throw std::runtime_error("Not enough unique keys (it's the file too short?)");
   }

   // copy to a vector so that they can be returned as contiguous data
   pd.m_groups.resize(pd.m_groupWindow.size());
   copy(pd.m_groupWindow.begin(), pd.m_groupWindow.end(), pd.m_groups.begin());

   pd.m_groupsReady = true;
   pd.m_processType = PT_UNKNOWN;
   return true;
}

// -----------------------------------------------------------------------------

pair<const char*, size_t> FingerprintExtractor::getFingerprint()
{
   // easier read
   PimplData& pd = *m_pPimplData;

   if ( pd.m_groupsReady )
   {
#if __BIG_ENDIAN__
      pd.m_bigEndianGroups.resize(pd.m_groups.size());
      for ( size_t i = 0; i < pd.m_groups.size(); ++i )
      {
         pd.m_bigEndianGroups[i].key = reorderbits(pd.m_groups[i].key);
         pd.m_bigEndianGroups[i].count = reorderbits(pd.m_groups[i].count);
      }

      return make_pair(reinterpret_cast<const char*>(&pd.m_bigEndianGroups[0]), pd.m_bigEndianGroups.size() * sizeof(GroupData) );

#else
      return make_pair(reinterpret_cast<const char*>(&pd.m_groups[0]), pd.m_groups.size() * sizeof(GroupData) );
#endif
   }
   else
      return make_pair(reinterpret_cast<const char*>(0), 0); // here's where null_ptr would become useful!
}

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

float getRMS(const FloatingAverage<double>& signal)
{
   // we don't want to normalize by the real rms, because excessive clipping will occur
   float rms = sqrtf(static_cast<float>(signal.getAverage())) * 10.0F;

   if (rms < 0.1F)
      rms = 0.1F;
   else if (rms > 3.0F)
      rms = 3.0F;

   return rms;
}

// -----------------------------------------------------------------------------

unsigned int processKeys( deque<GroupData>& groups, size_t size, PimplData& pd )
{
   size_t read_size = min(size, pd.m_downsampledProcessSize + pd.m_compensateBufferSize);

   unsigned int numFrames = pd.m_pFFT->process(pd.m_pDownsampledPCM, read_size);

   if ( numFrames <= Filter::KEYWIDTH )
      return 0; // skip it when the number of frames is too small

   float** ppFrames = pd.m_pFFT->getFrames();

   integralImage(ppFrames, numFrames);
   computeBits(pd.m_partialBits, pd.m_filters, ppFrames, numFrames);
   fingerprint::keys2GroupData(pd.m_partialBits, groups, false);

   return static_cast<unsigned int>(pd.m_partialBits.size());

}

// -----------------------------------------------------------------------------

void integralImage(float** ppFrames, unsigned int nFrames)
{
   for (unsigned int y = 1; y < nFrames; y++) 
   {
      ppFrames[y][0] += ppFrames[y-1][0];
   }

   for (unsigned int x = 1; x < Filter::NBANDS; x++) 
   {
      ppFrames[0][x] += ppFrames[0][x-1];
   }

   for (unsigned int y = 1; y < nFrames; y++) 
   {
      for (unsigned int x = 1; x < Filter::NBANDS; x++) 
      {
         ppFrames[y][x] += static_cast<float>( static_cast<double>(ppFrames[y-1][x]) + 
                                               static_cast<double>(ppFrames[y][x-1]) - 
                                               static_cast<double>(ppFrames[y-1][x-1]) );
      }
   }
}

// ---------------------------------------------------------------------
//
/// Convert bands to bits, using the supplied filters
void computeBits( vector<unsigned int>& bits,
                  const vector<Filter>& f, 
                  float ** frames, unsigned int nframes ) 
{
   unsigned int first_time = Filter::KEYWIDTH / 2 + 1;
   unsigned int last_time = nframes - Filter::KEYWIDTH / 2;

   unsigned int numBits = last_time - first_time + 1;
   bits.resize(numBits);

   const unsigned int fSize = static_cast<unsigned int>(f.size());
   std::bitset<32> bt;
   double X = 0;

   for (unsigned int t2 = first_time; t2 <= last_time; ++t2) 
   {

      for (unsigned int i = 0; i < fSize; ++i) 
      {
         // we subtract 1 from t1 and b1 because we use integral images
      
         unsigned int t1 = (unsigned int) ((float) t2 - f[i].wt / 2.0 - 1);
         unsigned int t3 = (unsigned int) ((float) t2 + f[i].wt / 2.0 - 1);
         unsigned int b1 = f[i].first_band;
         unsigned int b2 = (unsigned int) round__((float) b1 + f[i].wb / 2.0) - 1;
         unsigned int b3 = b1 + f[i].wb - 1;
         --b1;

         unsigned int t_1q = (t1 + t2) / 2; // one quarter time 
         unsigned int t_3q = t_1q + (t3 - t1 + 1) / 2; // three quarter time
         unsigned int b_1q = (b1 + b2) / 2; // one quarter band
         unsigned int b_3q = b_1q + (b3 - b1) / 2; // three quarter band
         
         X = 0;
         
         // we should check from t1 > 0, but in practice, this doesn't happen
         // we subtract 1 from everything because this came from matlab where indices start from 1
         switch (f[i].filter_type) {
         case 1: { // total energy
            if (b1 > 0)
               X = static_cast<double>(frames[t3-1][b3-1]) - static_cast<double>(frames[t3-1][b1-1])
                 - static_cast<double>(frames[t1-1][b3-1]) + static_cast<double>(frames[t1-1][b1-1]);
            else
               X = static_cast<double>(frames[t3-1][b3-1]) - static_cast<double>(frames[t1-1][b3-1]);
            break;
         }
         case 2: { // energy difference over time
            if (b1 > 0)
               X = static_cast<double>(frames[t1-1][b1-1]) - 2*static_cast<double>(frames[t2-2][b1-1])
                 + static_cast<double>(frames[t3-1][b1-1]) - static_cast<double>(frames[t1-1][b3-1])
                 + 2*static_cast<double>(frames[t2-2][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
            else
               X = - static_cast<double>(frames[t1-1][b3-1]) + 2*static_cast<double>(frames[t2-2][b3-1])
                   - static_cast<double>(frames[t3-1][b3-1]);
            break;
         
         }
         case 3: { // energy difference over bands
            if (b1 > 0)
               X = static_cast<double>(frames[t1-1][b1-1]) - static_cast<double>(frames[t3-1][b1-1])
                 - 2*static_cast<double>(frames[t1-1][b2-1]) + 2*static_cast<double>(frames[t3-1][b2-1])
                 + static_cast<double>(frames[t1-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
            else
               X = - 2*static_cast<double>(frames[t1-1][b2-1]) + 2*static_cast<double>(frames[t3-1][b2-1])
                   + static_cast<double>(frames[t1-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
            break;   
         }
         case 4: {
            // energy difference over time and bands
            if (b1 > 0)
               X = static_cast<double>(frames[t1-1][b1-1]) - 2*static_cast<double>(frames[t2-2][b1-1])
                 + static_cast<double>(frames[t3-1][b1-1]) - 2*static_cast<double>(frames[t1-1][b2-1])
                 + 4*static_cast<double>(frames[t2-2][b2-1]) - 2*static_cast<double>(frames[t3-1][b2-1])
                 + static_cast<double>(frames[t1-1][b3-1]) - 2*static_cast<double>(frames[t2-2][b3-1])
                 + static_cast<double>(frames[t3-1][b3-1]);
            else
               X = - 2*static_cast<double>(frames[t1-1][b2-1]) + 4*static_cast<double>(frames[t2-2][b2-1])
                   - 2*static_cast<double>(frames[t3-1][b2-1]) + static_cast<double>(frames[t1-1][b3-1])
                   - 2*static_cast<double>(frames[t2-2][b3-1]) + static_cast<double>(frames[t3-1][b3-1]);
            break;   
         }
         case 5: { // time peak
            if (b1 > 0)
               X = - static_cast<double>(frames[t1-1][b1-1]) + 2*static_cast<double>(frames[t_1q-1][b1-1])
                   - 2*static_cast<double>(frames[t_3q-1][b1-1]) + static_cast<double>(frames[t3-1][b1-1])
                   + static_cast<double>(frames[t1-1][b3-1]) - 2*static_cast<double>(frames[t_1q-1][b3-1])
                   + 2*static_cast<double>(frames[t_3q-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
            else
               X = static_cast<double>(frames[t1-1][b3-1]) - 2*static_cast<double>(frames[t_1q-1][b3-1])
                 + 2*static_cast<double>(frames[t_3q-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
                  
            break;
         }
         case 6: { // band beak
            if (b1 > 0)
               X = - static_cast<double>(frames[t1-1][b1-1]) + static_cast<double>(frames[t3-1][b1-1])
                   + 2*static_cast<double>(frames[t1-1][b_1q-1]) - 2*static_cast<double>(frames[t3-1][b_1q-1])
                   - 2*static_cast<double>(frames[t1-1][b_3q-1]) + 2*static_cast<double>(frames[t3-1][b_3q-1])
                   + static_cast<double>(frames[t1-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
            else
               X = + 2*static_cast<double>(frames[t1-1][b_1q-1]) - 2*static_cast<double>(frames[t3-1][b_1q-1])
                   - 2*static_cast<double>(frames[t1-1][b_3q-1]) + 2*static_cast<double>(frames[t3-1][b_3q-1])
                   + static_cast<double>(frames[t1-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);

            break;
         }
         }

         bt[i] = X > f[i].threshold;
      }

      bits[t2 - first_time] = bt.to_ulong();
   }
}

// -----------------------------------------------------------------------------

void src_short_to_float_and_mono_array( const short *in, float *out, int srclen, int nchannels )
{
   switch ( nchannels )
   {
   case 1:
      src_short_to_float_array(in, out, srclen);
      break;
   case 2:
      {
         // this can be optimized
         int j = 0;
         const double div = numeric_limits<short>::max() * nchannels;
         for ( int i = 0; i < srclen; i += 2, ++j )
         {
            out[j] = static_cast<float>( static_cast<double>(static_cast<int>(in[i]) + static_cast<int>(in[i+1])) / div );
         }
      }
      break;

   default:
      throw( std::runtime_error("Unsupported number of channels!") );
   }

}

// -----------------------------------------------------------------------------

} // end of namespace

// -----------------------------------------------------------------------------
0707010000005A000081A400000000000000000000000167B98EA800000A75000000000000000000000000000000000000005C00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib/FingerprintExtractor.h/*
   Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __FINGERPRINT_EXTRACTOR_H
#define __FINGERPRINT_EXTRACTOR_H

#include <utility> // for pair
#include <cstddef> // for size_t

namespace fingerprint {

// -----------------------------------------------------------------------------

class PimplData;

class FingerprintExtractor
{
public:

   FingerprintExtractor(); // ctor
   ~FingerprintExtractor(); // dtor

   // duration (in seconds!) is optional, but if you want to submit tracks <34 secs
   // it must be provided. 
   void initForQuery(int freq, int nchannels, int duration = -1);
   void initForFullSubmit(int freq, int nchannels);

   // return false if it needs more data, otherwise true
   // IMPORTANT: num_samples specify the size of the *short* array pPCM, that is
   //            the number of samples that are in the buffer. This includes
   //            the stereo samples, i.e.
   //            [L][R][L][R][L][R][L][R] would be num_samples=8
   bool process(const short* pPCM, size_t num_samples, bool end_of_stream = false);

   // returns pair<NULL, 0> if the data is not ready
   std::pair<const char*, size_t> getFingerprint();

   //////////////////////////////////////////////////////////////////////////

   // The FingerprintExtractor assumes that the file start from the beginning
   // but since the first SkipMs are ignored, it's possible to feed it with NULL.
   // In order to know how much must be skipped (in milliseconds) call this function.
   // Remark: this is only for "advanced" users!
   size_t getToSkipMs();

   // Return the minimum duration of the file (in ms)
   // Any file with a length smaller than this value will be discarded
   static size_t getMinimumDurationMs();

   // return the version of the fingerprint
   static size_t getVersion();

private:

   PimplData* m_pPimplData;
};

// -----------------------------------------------------------------------------

} // end of namespace fingerprint

#endif // __FINGERPRINT_EXTRACTOR_H
0707010000005B000081A400000000000000000000000167B98EA80000098B000000000000000000000000000000000000005700000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib/FloatingAverage.h/*
   Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __FLOAT_AVERAGE_H__
#define __FLOAT_AVERAGE_H__

//#include <deque>
#include <limits>
#include "CircularArray.h"

template <typename T>
class FloatingAverage
{
public:
   FloatingAverage(size_t size)
   {
      m_values.resize(size);
      m_valIt = m_values.head();
      m_sum = 0;
      m_bufferFilled = false;
   }

   void purge()
   {
      m_sum = 0;
      const T* pCircularBuffer = m_values.get_buffer();
      const int size = m_values.size();

      for ( int i = 0; i < size; ++i )
         m_sum += pCircularBuffer[i];
   }

   void add(const T& value)
   {
      m_sum += value;

      if ( m_bufferFilled )
      {
         m_sum -= *m_valIt;
         *m_valIt = value;
         ++m_valIt;
      }
      else
      {
         *m_valIt = value;
         ++m_valIt;
         if ( m_valIt == m_values.head() )
            m_bufferFilled = true;
      }
   }

   T getAverage() const
   {
      if ( !m_bufferFilled )
         return m_sum / (m_valIt - m_values.head());
      else
         return m_sum / m_values.size();
   }

   T getError() const
   {
      T real_sum = 0;
      const T* pCircularBuffer = m_values.get_buffer();
      const int size = m_values.size();

      for ( int i = 0; i < size; ++i )
         real_sum += pCircularBuffer[i];
      return abs(real_sum - m_sum) / this->size();
   }

   size_t size() const
   {
      return m_values.size();
   }

   void clear()
   {
      m_bufferFilled = false;
      m_values.zero_fill();
      m_valIt = m_values.head();
      m_sum = 0;
   }

private:
   //std::deque<T> m_values;
   CircularArray<T> m_values;
   typename CircularArray<T>::iterator m_valIt;

   bool m_bufferFilled;
   T m_sum;
};

#endif // __FLOAT_AVERAGE_H__
0707010000005C000081A400000000000000000000000167B98EA800007110000000000000000000000000000000000000005000000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib/OptFFT.cpp/*
   Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "OptFFT.h"
#include "fp_helper_fun.h"
#include "Filter.h" // for NBANDS

#include <cmath>
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <cstdlib> 
#include <stdexcept>
#include <cstring>

using namespace std;
// ----------------------------------------------------------------------

namespace fingerprint
{

   static const float hann[] = {
      0.000000f,0.000002f,0.000009f,0.000021f,0.000038f,0.000059f,0.000085f,0.000115f,0.000151f,0.000191f,0.000236f,
      0.000285f,0.000339f,0.000398f,0.000462f,0.000530f,0.000603f,0.000681f,0.000763f,0.000850f,0.000942f,0.001038f,
      0.001140f,0.001245f,0.001356f,0.001471f,0.001591f,0.001716f,0.001845f,0.001980f,0.002118f,0.002262f,0.002410f,
      0.002563f,0.002720f,0.002883f,0.003049f,0.003221f,0.003397f,0.003578f,0.003764f,0.003954f,0.004149f,0.004349f,
      0.004553f,0.004762f,0.004976f,0.005194f,0.005417f,0.005645f,0.005877f,0.006114f,0.006355f,0.006602f,0.006853f,
      0.007108f,0.007368f,0.007633f,0.007903f,0.008177f,0.008455f,0.008739f,0.009027f,0.009319f,0.009617f,0.009919f,
      0.010225f,0.010536f,0.010852f,0.011172f,0.011497f,0.011827f,0.012161f,0.012499f,0.012843f,0.013191f,0.013543f,
      0.013900f,0.014262f,0.014628f,0.014999f,0.015374f,0.015754f,0.016139f,0.016528f,0.016921f,0.017320f,0.017722f,
      0.018130f,0.018541f,0.018958f,0.019379f,0.019804f,0.020234f,0.020668f,0.021107f,0.021551f,0.021999f,0.022451f,
      0.022908f,0.023370f,0.023836f,0.024306f,0.024781f,0.025260f,0.025744f,0.026233f,0.026725f,0.027223f,0.027724f,
      0.028231f,0.028741f,0.029256f,0.029776f,0.030300f,0.030828f,0.031361f,0.031898f,0.032440f,0.032986f,0.033536f,
      0.034091f,0.034650f,0.035214f,0.035781f,0.036354f,0.036930f,0.037512f,0.038097f,0.038687f,0.039281f,0.039879f,
      0.040482f,0.041089f,0.041701f,0.042316f,0.042936f,0.043561f,0.044189f,0.044822f,0.045460f,0.046101f,0.046747f,
      0.047397f,0.048052f,0.048710f,0.049373f,0.050040f,0.050711f,0.051387f,0.052067f,0.052751f,0.053439f,0.054132f,
      0.054828f,0.055529f,0.056234f,0.056943f,0.057657f,0.058374f,0.059096f,0.059822f,0.060552f,0.061286f,0.062024f,
      0.062767f,0.063513f,0.064264f,0.065019f,0.065777f,0.066540f,0.067307f,0.068078f,0.068854f,0.069633f,0.070416f,
      0.071204f,0.071995f,0.072790f,0.073590f,0.074393f,0.075201f,0.076012f,0.076828f,0.077647f,0.078470f,0.079298f,
      0.080129f,0.080964f,0.081804f,0.082647f,0.083494f,0.084345f,0.085200f,0.086059f,0.086922f,0.087788f,0.088659f,
      0.089533f,0.090412f,0.091294f,0.092180f,0.093070f,0.093963f,0.094861f,0.095762f,0.096667f,0.097576f,0.098489f,
      0.099406f,0.100326f,0.101250f,0.102178f,0.103109f,0.104045f,0.104984f,0.105926f,0.106873f,0.107823f,0.108777f,
      0.109734f,0.110696f,0.111661f,0.112629f,0.113601f,0.114577f,0.115557f,0.116540f,0.117526f,0.118517f,0.119511f,
      0.120508f,0.121509f,0.122514f,0.123522f,0.124534f,0.125549f,0.126568f,0.127590f,0.128616f,0.129645f,0.130678f,
      0.131714f,0.132754f,0.133797f,0.134844f,0.135894f,0.136948f,0.138005f,0.139065f,0.140129f,0.141196f,0.142266f,
      0.143340f,0.144418f,0.145498f,0.146582f,0.147670f,0.148760f,0.149854f,0.150951f,0.152052f,0.153156f,0.154263f,
      0.155373f,0.156487f,0.157603f,0.158723f,0.159847f,0.160973f,0.162103f,0.163236f,0.164372f,0.165511f,0.166653f,
      0.167799f,0.168947f,0.170099f,0.171254f,0.172411f,0.173572f,0.174737f,0.175904f,0.177074f,0.178247f,0.179423f,
      0.180603f,0.181785f,0.182970f,0.184158f,0.185350f,0.186544f,0.187741f,0.188941f,0.190144f,0.191350f,0.192559f,
      0.193771f,0.194986f,0.196203f,0.197423f,0.198647f,0.199873f,0.201102f,0.202333f,0.203568f,0.204805f,0.206045f,
      0.207288f,0.208534f,0.209782f,0.211033f,0.212287f,0.213544f,0.214803f,0.216065f,0.217329f,0.218597f,0.219867f,
      0.221139f,0.222414f,0.223692f,0.224972f,0.226255f,0.227541f,0.228829f,0.230120f,0.231413f,0.232709f,0.234007f,
      0.235308f,0.236611f,0.237917f,0.239225f,0.240536f,0.241849f,0.243165f,0.244483f,0.245803f,0.247126f,0.248451f,
      0.249779f,0.251108f,0.252441f,0.253775f,0.255112f,0.256451f,0.257793f,0.259137f,0.260483f,0.261831f,0.263182f,
      0.264534f,0.265889f,0.267247f,0.268606f,0.269967f,0.271331f,0.272697f,0.274065f,0.275435f,0.276808f,0.278182f,
      0.279558f,0.280937f,0.282318f,0.283700f,0.285085f,0.286472f,0.287861f,0.289251f,0.290644f,0.292039f,0.293435f,
      0.294834f,0.296235f,0.297637f,0.299041f,0.300448f,0.301856f,0.303266f,0.304678f,0.306091f,0.307507f,0.308924f,
      0.310343f,0.311764f,0.313187f,0.314611f,0.316038f,0.317466f,0.318895f,0.320327f,0.321760f,0.323194f,0.324631f,
      0.326069f,0.327509f,0.328950f,0.330393f,0.331837f,0.333283f,0.334731f,0.336180f,0.337631f,0.339083f,0.340537f,
      0.341993f,0.343449f,0.344908f,0.346368f,0.347829f,0.349291f,0.350755f,0.352221f,0.353688f,0.355156f,0.356626f,
      0.358097f,0.359569f,0.361042f,0.362517f,0.363994f,0.365471f,0.366950f,0.368430f,0.369911f,0.371394f,0.372877f,
      0.374362f,0.375848f,0.377336f,0.378824f,0.380314f,0.381804f,0.383296f,0.384789f,0.386283f,0.387778f,0.389274f,
      0.390771f,0.392269f,0.393768f,0.395269f,0.396770f,0.398272f,0.399775f,0.401279f,0.402784f,0.404290f,0.405797f,
      0.407305f,0.408813f,0.410322f,0.411833f,0.413344f,0.414856f,0.416368f,0.417882f,0.419396f,0.420911f,0.422427f,
      0.423944f,0.425461f,0.426979f,0.428497f,0.430017f,0.431537f,0.433057f,0.434578f,0.436100f,0.437623f,0.439146f,
      0.440669f,0.442193f,0.443718f,0.445243f,0.446769f,0.448295f,0.449822f,0.451349f,0.452877f,0.454405f,0.455934f,
      0.457463f,0.458992f,0.460522f,0.462052f,0.463582f,0.465113f,0.466644f,0.468176f,0.469708f,0.471240f,0.472772f,
      0.474305f,0.475837f,0.477370f,0.478904f,0.480437f,0.481971f,0.483505f,0.485039f,0.486573f,0.488107f,0.489641f,
      0.491176f,0.492710f,0.494245f,0.495780f,0.497314f,0.498849f,0.500384f,0.501918f,0.503453f,0.504988f,0.506522f,
      0.508057f,0.509591f,0.511126f,0.512660f,0.514194f,0.515728f,0.517262f,0.518796f,0.520330f,0.521863f,0.523396f,
      0.524929f,0.526462f,0.527994f,0.529526f,0.531058f,0.532590f,0.534121f,0.535652f,0.537183f,0.538713f,0.540243f,
      0.541773f,0.543302f,0.544831f,0.546359f,0.547887f,0.549414f,0.550941f,0.552468f,0.553994f,0.555519f,0.557044f,
      0.558569f,0.560093f,0.561616f,0.563139f,0.564661f,0.566182f,0.567703f,0.569223f,0.570743f,0.572262f,0.573780f,
      0.575298f,0.576815f,0.578331f,0.579846f,0.581361f,0.582875f,0.584388f,0.585900f,0.587412f,0.588922f,0.590432f,
      0.591941f,0.593449f,0.594957f,0.596463f,0.597968f,0.599473f,0.600977f,0.602479f,0.603981f,0.605482f,0.606981f,
      0.608480f,0.609978f,0.611474f,0.612970f,0.614464f,0.615958f,0.617450f,0.618941f,0.620431f,0.621920f,0.623408f,
      0.624895f,0.626380f,0.627865f,0.629348f,0.630830f,0.632310f,0.633790f,0.635268f,0.636745f,0.638220f,0.639695f,
      0.641167f,0.642639f,0.644109f,0.645578f,0.647046f,0.648512f,0.649977f,0.651440f,0.652902f,0.654363f,0.655822f,
      0.657279f,0.658735f,0.660190f,0.661643f,0.663094f,0.664544f,0.665993f,0.667440f,0.668885f,0.670329f,0.671771f,
      0.673212f,0.674650f,0.676088f,0.677523f,0.678957f,0.680389f,0.681820f,0.683249f,0.684676f,0.686101f,0.687525f,
      0.688946f,0.690366f,0.691785f,0.693201f,0.694616f,0.696029f,0.697439f,0.698849f,0.700256f,0.701661f,0.703065f,
      0.704466f,0.705866f,0.707263f,0.708659f,0.710053f,0.711444f,0.712834f,0.714222f,0.715608f,0.716991f,0.718373f,
      0.719752f,0.721130f,0.722505f,0.723879f,0.725250f,0.726619f,0.727986f,0.729351f,0.730714f,0.732074f,0.733432f,
      0.734788f,0.736142f,0.737494f,0.738843f,0.740191f,0.741536f,0.742878f,0.744219f,0.745557f,0.746892f,0.748226f,
      0.749557f,0.750886f,0.752212f,0.753536f,0.754857f,0.756177f,0.757493f,0.758808f,0.760120f,0.761429f,0.762736f,
      0.764041f,0.765343f,0.766642f,0.767939f,0.769234f,0.770526f,0.771815f,0.773102f,0.774386f,0.775668f,0.776947f,
      0.778224f,0.779497f,0.780769f,0.782037f,0.783303f,0.784567f,0.785827f,0.787085f,0.788340f,0.789593f,0.790842f,
      0.792089f,0.793334f,0.794575f,0.795814f,0.797050f,0.798283f,0.799513f,0.800741f,0.801965f,0.803187f,0.804406f,
      0.805622f,0.806835f,0.808046f,0.809253f,0.810458f,0.811659f,0.812858f,0.814054f,0.815246f,0.816436f,0.817623f,
      0.818807f,0.819987f,0.821165f,0.822340f,0.823512f,0.824680f,0.825846f,0.827008f,0.828168f,0.829324f,0.830477f,
      0.831628f,0.832775f,0.833918f,0.835059f,0.836197f,0.837331f,0.838462f,0.839591f,0.840715f,0.841837f,0.842955f,
      0.844071f,0.845183f,0.846291f,0.847397f,0.848499f,0.849598f,0.850693f,0.851786f,0.852874f,0.853960f,0.855042f,
      0.856121f,0.857197f,0.858269f,0.859338f,0.860404f,0.861466f,0.862524f,0.863580f,0.864631f,0.865680f,0.866725f,
      0.867766f,0.868804f,0.869839f,0.870870f,0.871897f,0.872922f,0.873942f,0.874959f,0.875973f,0.876983f,0.877989f,
      0.878992f,0.879991f,0.880987f,0.881979f,0.882967f,0.883952f,0.884934f,0.885911f,0.886885f,0.887856f,0.888822f,
      0.889785f,0.890745f,0.891701f,0.892653f,0.893601f,0.894545f,0.895486f,0.896423f,0.897357f,0.898287f,0.899213f,
      0.900135f,0.901053f,0.901968f,0.902879f,0.903786f,0.904689f,0.905588f,0.906484f,0.907376f,0.908264f,0.909148f,
      0.910028f,0.910904f,0.911777f,0.912645f,0.913510f,0.914371f,0.915228f,0.916081f,0.916930f,0.917775f,0.918616f,
      0.919454f,0.920287f,0.921116f,0.921942f,0.922763f,0.923581f,0.924394f,0.925204f,0.926009f,0.926810f,0.927608f,
      0.928401f,0.929191f,0.929976f,0.930757f,0.931534f,0.932308f,0.933077f,0.933842f,0.934603f,0.935359f,0.936112f,
      0.936861f,0.937605f,0.938345f,0.939082f,0.939814f,0.940542f,0.941265f,0.941985f,0.942701f,0.943412f,0.944119f,
      0.944822f,0.945521f,0.946215f,0.946906f,0.947592f,0.948274f,0.948951f,0.949625f,0.950294f,0.950959f,0.951620f,
      0.952276f,0.952928f,0.953576f,0.954220f,0.954859f,0.955495f,0.956125f,0.956752f,0.957374f,0.957992f,0.958606f,
      0.959215f,0.959820f,0.960420f,0.961017f,0.961609f,0.962196f,0.962780f,0.963358f,0.963933f,0.964503f,0.965069f,
      0.965630f,0.966187f,0.966740f,0.967288f,0.967832f,0.968371f,0.968906f,0.969437f,0.969963f,0.970485f,0.971002f,
      0.971515f,0.972023f,0.972527f,0.973027f,0.973522f,0.974012f,0.974498f,0.974980f,0.975457f,0.975930f,0.976398f,
      0.976862f,0.977321f,0.977776f,0.978226f,0.978672f,0.979113f,0.979549f,0.979982f,0.980409f,0.980832f,0.981251f,
      0.981665f,0.982075f,0.982480f,0.982880f,0.983276f,0.983667f,0.984054f,0.984436f,0.984814f,0.985187f,0.985556f,
      0.985919f,0.986279f,0.986634f,0.986984f,0.987329f,0.987670f,0.988007f,0.988339f,0.988666f,0.988989f,0.989307f,
      0.989620f,0.989929f,0.990233f,0.990532f,0.990827f,0.991118f,0.991403f,0.991684f,0.991961f,0.992233f,0.992500f,
      0.992762f,0.993020f,0.993273f,0.993522f,0.993766f,0.994005f,0.994240f,0.994470f,0.994695f,0.994916f,0.995132f,
      0.995343f,0.995550f,0.995752f,0.995949f,0.996142f,0.996329f,0.996513f,0.996691f,0.996865f,0.997035f,0.997199f,
      0.997359f,0.997514f,0.997665f,0.997810f,0.997952f,0.998088f,0.998220f,0.998347f,0.998469f,0.998587f,0.998700f,
      0.998808f,0.998912f,0.999010f,0.999105f,0.999194f,0.999279f,0.999359f,0.999434f,0.999505f,0.999571f,0.999632f,
      0.999689f,0.999740f,0.999787f,0.999830f,0.999868f,0.999900f,0.999929f,0.999952f,0.999971f,0.999985f,0.999995f,
      0.999999f,0.999999f,0.999995f,0.999985f,0.999971f,0.999952f,0.999929f,0.999900f,0.999868f,0.999830f,0.999787f,
      0.999740f,0.999689f,0.999632f,0.999571f,0.999505f,0.999434f,0.999359f,0.999279f,0.999194f,0.999105f,0.999010f,
      0.998912f,0.998808f,0.998700f,0.998587f,0.998469f,0.998347f,0.998220f,0.998088f,0.997952f,0.997810f,0.997665f,
      0.997514f,0.997359f,0.997199f,0.997035f,0.996865f,0.996691f,0.996513f,0.996329f,0.996142f,0.995949f,0.995752f,
      0.995550f,0.995343f,0.995132f,0.994916f,0.994695f,0.994470f,0.994240f,0.994005f,0.993766f,0.993522f,0.993273f,
      0.993020f,0.992762f,0.992500f,0.992233f,0.991961f,0.991684f,0.991403f,0.991118f,0.990827f,0.990532f,0.990233f,
      0.989929f,0.989620f,0.989307f,0.988989f,0.988666f,0.988339f,0.988007f,0.987670f,0.987329f,0.986984f,0.986634f,
      0.986279f,0.985919f,0.985556f,0.985187f,0.984814f,0.984436f,0.984054f,0.983667f,0.983276f,0.982880f,0.982480f,
      0.982075f,0.981665f,0.981251f,0.980832f,0.980409f,0.979982f,0.979549f,0.979113f,0.978672f,0.978226f,0.977776f,
      0.977321f,0.976862f,0.976398f,0.975930f,0.975457f,0.974980f,0.974498f,0.974012f,0.973522f,0.973027f,0.972527f,
      0.972023f,0.971515f,0.971002f,0.970485f,0.969963f,0.969437f,0.968906f,0.968371f,0.967832f,0.967288f,0.966740f,
      0.966187f,0.965630f,0.965069f,0.964503f,0.963933f,0.963358f,0.962780f,0.962196f,0.961609f,0.961017f,0.960420f,
      0.959820f,0.959215f,0.958606f,0.957992f,0.957374f,0.956752f,0.956125f,0.955495f,0.954859f,0.954220f,0.953576f,
      0.952928f,0.952276f,0.951620f,0.950959f,0.950294f,0.949625f,0.948951f,0.948274f,0.947592f,0.946906f,0.946215f,
      0.945521f,0.944822f,0.944119f,0.943412f,0.942701f,0.941985f,0.941265f,0.940542f,0.939814f,0.939082f,0.938345f,
      0.937605f,0.936861f,0.936112f,0.935359f,0.934603f,0.933842f,0.933077f,0.932308f,0.931534f,0.930757f,0.929976f,
      0.929191f,0.928401f,0.927608f,0.926810f,0.926009f,0.925204f,0.924394f,0.923581f,0.922763f,0.921942f,0.921116f,
      0.920287f,0.919454f,0.918616f,0.917775f,0.916930f,0.916081f,0.915228f,0.914371f,0.913510f,0.912645f,0.911777f,
      0.910904f,0.910028f,0.909148f,0.908264f,0.907376f,0.906484f,0.905588f,0.904689f,0.903786f,0.902879f,0.901968f,
      0.901053f,0.900135f,0.899213f,0.898287f,0.897357f,0.896423f,0.895486f,0.894545f,0.893601f,0.892653f,0.891701f,
      0.890745f,0.889785f,0.888822f,0.887856f,0.886885f,0.885911f,0.884934f,0.883952f,0.882967f,0.881979f,0.880987f,
      0.879991f,0.878992f,0.877989f,0.876983f,0.875973f,0.874959f,0.873942f,0.872922f,0.871897f,0.870870f,0.869839f,
      0.868804f,0.867766f,0.866725f,0.865680f,0.864631f,0.863580f,0.862524f,0.861466f,0.860404f,0.859338f,0.858269f,
      0.857197f,0.856121f,0.855042f,0.853960f,0.852874f,0.851786f,0.850693f,0.849598f,0.848499f,0.847397f,0.846291f,
      0.845183f,0.844071f,0.842955f,0.841837f,0.840715f,0.839591f,0.838462f,0.837331f,0.836197f,0.835059f,0.833918f,
      0.832775f,0.831628f,0.830477f,0.829324f,0.828168f,0.827008f,0.825846f,0.824680f,0.823512f,0.822340f,0.821165f,
      0.819987f,0.818807f,0.817623f,0.816436f,0.815246f,0.814054f,0.812858f,0.811659f,0.810458f,0.809253f,0.808046f,
      0.806835f,0.805622f,0.804406f,0.803187f,0.801965f,0.800741f,0.799513f,0.798283f,0.797050f,0.795814f,0.794575f,
      0.793334f,0.792089f,0.790842f,0.789593f,0.788340f,0.787085f,0.785827f,0.784567f,0.783303f,0.782037f,0.780769f,
      0.779497f,0.778224f,0.776947f,0.775668f,0.774386f,0.773102f,0.771815f,0.770526f,0.769234f,0.767939f,0.766642f,
      0.765343f,0.764041f,0.762736f,0.761429f,0.760120f,0.758808f,0.757493f,0.756177f,0.754857f,0.753536f,0.752212f,
      0.750886f,0.749557f,0.748226f,0.746892f,0.745557f,0.744219f,0.742878f,0.741536f,0.740191f,0.738843f,0.737494f,
      0.736142f,0.734788f,0.733432f,0.732074f,0.730714f,0.729351f,0.727986f,0.726619f,0.725250f,0.723879f,0.722505f,
      0.721130f,0.719752f,0.718373f,0.716991f,0.715608f,0.714222f,0.712834f,0.711444f,0.710053f,0.708659f,0.707263f,
      0.705866f,0.704466f,0.703065f,0.701661f,0.700256f,0.698849f,0.697439f,0.696029f,0.694616f,0.693201f,0.691785f,
      0.690366f,0.688946f,0.687525f,0.686101f,0.684676f,0.683249f,0.681820f,0.680389f,0.678957f,0.677523f,0.676088f,
      0.674650f,0.673212f,0.671771f,0.670329f,0.668885f,0.667440f,0.665993f,0.664544f,0.663094f,0.661643f,0.660190f,
      0.658735f,0.657279f,0.655822f,0.654363f,0.652902f,0.651440f,0.649977f,0.648512f,0.647046f,0.645578f,0.644109f,
      0.642639f,0.641167f,0.639695f,0.638220f,0.636745f,0.635268f,0.633790f,0.632310f,0.630830f,0.629348f,0.627865f,
      0.626380f,0.624895f,0.623408f,0.621920f,0.620431f,0.618941f,0.617450f,0.615958f,0.614464f,0.612970f,0.611474f,
      0.609978f,0.608480f,0.606981f,0.605482f,0.603981f,0.602479f,0.600977f,0.599473f,0.597968f,0.596463f,0.594957f,
      0.593449f,0.591941f,0.590432f,0.588922f,0.587412f,0.585900f,0.584388f,0.582875f,0.581361f,0.579846f,0.578331f,
      0.576815f,0.575298f,0.573780f,0.572262f,0.570743f,0.569223f,0.567703f,0.566182f,0.564661f,0.563139f,0.561616f,
      0.560093f,0.558569f,0.557044f,0.555519f,0.553994f,0.552468f,0.550941f,0.549414f,0.547887f,0.546359f,0.544831f,
      0.543302f,0.541773f,0.540243f,0.538713f,0.537183f,0.535652f,0.534121f,0.532590f,0.531058f,0.529526f,0.527994f,
      0.526462f,0.524929f,0.523396f,0.521863f,0.520330f,0.518796f,0.517262f,0.515728f,0.514194f,0.512660f,0.511126f,
      0.509591f,0.508057f,0.506522f,0.504988f,0.503453f,0.501918f,0.500384f,0.498849f,0.497314f,0.495780f,0.494245f,
      0.492710f,0.491176f,0.489641f,0.488107f,0.486573f,0.485039f,0.483505f,0.481971f,0.480437f,0.478904f,0.477370f,
      0.475837f,0.474305f,0.472772f,0.471240f,0.469708f,0.468176f,0.466644f,0.465113f,0.463582f,0.462052f,0.460522f,
      0.458992f,0.457463f,0.455934f,0.454405f,0.452877f,0.451349f,0.449822f,0.448295f,0.446769f,0.445243f,0.443718f,
      0.442193f,0.440669f,0.439146f,0.437623f,0.436100f,0.434578f,0.433057f,0.431537f,0.430017f,0.428497f,0.426979f,
      0.425461f,0.423944f,0.422427f,0.420911f,0.419396f,0.417882f,0.416368f,0.414856f,0.413344f,0.411833f,0.410322f,
      0.408813f,0.407305f,0.405797f,0.404290f,0.402784f,0.401279f,0.399775f,0.398272f,0.396770f,0.395269f,0.393768f,
      0.392269f,0.390771f,0.389274f,0.387778f,0.386283f,0.384789f,0.383296f,0.381804f,0.380314f,0.378824f,0.377336f,
      0.375848f,0.374362f,0.372877f,0.371394f,0.369911f,0.368430f,0.366950f,0.365471f,0.363994f,0.362517f,0.361042f,
      0.359569f,0.358097f,0.356626f,0.355156f,0.353688f,0.352221f,0.350755f,0.349291f,0.347829f,0.346368f,0.344908f,
      0.343449f,0.341993f,0.340537f,0.339083f,0.337631f,0.336180f,0.334731f,0.333283f,0.331837f,0.330393f,0.328950f,
      0.327509f,0.326069f,0.324631f,0.323194f,0.321760f,0.320327f,0.318895f,0.317466f,0.316038f,0.314611f,0.313187f,
      0.311764f,0.310343f,0.308924f,0.307507f,0.306091f,0.304678f,0.303266f,0.301856f,0.300448f,0.299041f,0.297637f,
      0.296235f,0.294834f,0.293435f,0.292039f,0.290644f,0.289251f,0.287861f,0.286472f,0.285085f,0.283700f,0.282318f,
      0.280937f,0.279558f,0.278182f,0.276808f,0.275435f,0.274065f,0.272697f,0.271331f,0.269967f,0.268606f,0.267247f,
      0.265889f,0.264534f,0.263182f,0.261831f,0.260483f,0.259137f,0.257793f,0.256451f,0.255112f,0.253775f,0.252441f,
      0.251108f,0.249779f,0.248451f,0.247126f,0.245803f,0.244483f,0.243165f,0.241849f,0.240536f,0.239225f,0.237917f,
      0.236611f,0.235308f,0.234007f,0.232709f,0.231413f,0.230120f,0.228829f,0.227541f,0.226255f,0.224972f,0.223692f,
      0.222414f,0.221139f,0.219867f,0.218597f,0.217329f,0.216065f,0.214803f,0.213544f,0.212287f,0.211033f,0.209782f,
      0.208534f,0.207288f,0.206045f,0.204805f,0.203568f,0.202333f,0.201102f,0.199873f,0.198647f,0.197423f,0.196203f,
      0.194986f,0.193771f,0.192559f,0.191350f,0.190144f,0.188941f,0.187741f,0.186544f,0.185350f,0.184158f,0.182970f,
      0.181785f,0.180603f,0.179423f,0.178247f,0.177074f,0.175904f,0.174737f,0.173572f,0.172411f,0.171254f,0.170099f,
      0.168947f,0.167799f,0.166653f,0.165511f,0.164372f,0.163236f,0.162103f,0.160973f,0.159847f,0.158723f,0.157603f,
      0.156487f,0.155373f,0.154263f,0.153156f,0.152052f,0.150951f,0.149854f,0.148760f,0.147670f,0.146582f,0.145498f,
      0.144418f,0.143340f,0.142266f,0.141196f,0.140129f,0.139065f,0.138005f,0.136948f,0.135894f,0.134844f,0.133797f,
      0.132754f,0.131714f,0.130678f,0.129645f,0.128616f,0.127590f,0.126568f,0.125549f,0.124534f,0.123522f,0.122514f,
      0.121509f,0.120508f,0.119511f,0.118517f,0.117526f,0.116540f,0.115557f,0.114577f,0.113601f,0.112629f,0.111661f,
      0.110696f,0.109734f,0.108777f,0.107823f,0.106873f,0.105926f,0.104984f,0.104045f,0.103109f,0.102178f,0.101250f,
      0.100326f,0.099406f,0.098489f,0.097576f,0.096667f,0.095762f,0.094861f,0.093963f,0.093070f,0.092180f,0.091294f,
      0.090412f,0.089533f,0.088659f,0.087788f,0.086922f,0.086059f,0.085200f,0.084345f,0.083494f,0.082647f,0.081804f,
      0.080964f,0.080129f,0.079298f,0.078470f,0.077647f,0.076828f,0.076012f,0.075201f,0.074393f,0.073590f,0.072790f,
      0.071995f,0.071204f,0.070416f,0.069633f,0.068854f,0.068078f,0.067307f,0.066540f,0.065777f,0.065019f,0.064264f,
      0.063513f,0.062767f,0.062024f,0.061286f,0.060552f,0.059822f,0.059096f,0.058374f,0.057657f,0.056943f,0.056234f,
      0.055529f,0.054828f,0.054132f,0.053439f,0.052751f,0.052067f,0.051387f,0.050711f,0.050040f,0.049373f,0.048710f,
      0.048052f,0.047397f,0.046747f,0.046101f,0.045460f,0.044822f,0.044189f,0.043561f,0.042936f,0.042316f,0.041701f,
      0.041089f,0.040482f,0.039879f,0.039281f,0.038687f,0.038097f,0.037512f,0.036930f,0.036354f,0.035781f,0.035214f,
      0.034650f,0.034091f,0.033536f,0.032986f,0.032440f,0.031898f,0.031361f,0.030828f,0.030300f,0.029776f,0.029256f,
      0.028741f,0.028231f,0.027724f,0.027223f,0.026725f,0.026233f,0.025744f,0.025260f,0.024781f,0.024306f,0.023836f,
      0.023370f,0.022908f,0.022451f,0.021999f,0.021551f,0.021107f,0.020668f,0.020234f,0.019804f,0.019379f,0.018958f,
      0.018541f,0.018130f,0.017722f,0.017320f,0.016921f,0.016528f,0.016139f,0.015754f,0.015374f,0.014999f,0.014628f,
      0.014262f,0.013900f,0.013543f,0.013191f,0.012843f,0.012499f,0.012161f,0.011827f,0.011497f,0.011172f,0.010852f,
      0.010536f,0.010225f,0.009919f,0.009617f,0.009319f,0.009027f,0.008739f,0.008455f,0.008177f,0.007903f,0.007633f,
      0.007368f,0.007108f,0.006853f,0.006602f,0.006355f,0.006114f,0.005877f,0.005645f,0.005417f,0.005194f,0.004976f,
      0.004762f,0.004553f,0.004349f,0.004149f,0.003954f,0.003764f,0.003578f,0.003397f,0.003221f,0.003049f,0.002883f,
      0.002720f,0.002563f,0.002410f,0.002262f,0.002118f,0.001980f,0.001845f,0.001716f,0.001591f,0.001471f,0.001356f,
      0.001245f,0.001140f,0.001038f,0.000942f,0.000850f,0.000763f,0.000681f,0.000603f,0.000530f,0.000462f,0.000398f,
      0.000339f,0.000285f,0.000236f,0.000191f,0.000151f,0.000115f,0.000085f,0.000059f,0.000038f,0.000021f,0.000009f,
      0.000002f,0.000000f };

// -----------------------------------------------------------------------------

OptFFT::OptFFT(const size_t maxDataSize)
{
   assert( maxDataSize % OVERLAPSAMPLES == 0 );

   // DOUBLE
   //m_pIn = static_cast<double*>( fftw_malloc(sizeof(double) * FRAMESIZE) );
   //m_pOut = static_cast<fftw_complex*>( fftw_malloc(sizeof(fftw_complex) * (FRAMESIZE/2 + 1)) );
    //m_p = fftw_plan_dft_r2c_1f(FRAMESIZE, m_pIn, m_pOut, FFTW_ESTIMATE); // FFTW_ESTIMATE or FFTW_MEASURE

   // FLOAT
 //  m_pIn = static_cast<float*>( fftwf_malloc(sizeof(float) * FRAMESIZE) );
 //  m_pOut = static_cast<fftwf_complex*>( fftwf_malloc(sizeof(fftwf_complex) * (FRAMESIZE/2 + 1)) );

    //// in destroyed when line executed
    //m_p = fftwf_plan_dft_r2c_1d(FRAMESIZE, m_pIn, m_pOut, FFTW_ESTIMATE); // FFTW_ESTIMATE or FFTW_MEASURE

   //-----------------------------------------------------------------

   int numSamplesPerFrame    = FRAMESIZE;
   int numSamplesPerFrameOut = FRAMESIZE/2+1;

    m_maxFrames = static_cast<int> ( (maxDataSize - FRAMESIZE) / OVERLAPSAMPLES + 1 );

   m_pIn  = static_cast<float*> ( fftwf_malloc(sizeof(float) * (numSamplesPerFrame * m_maxFrames) ) );
   if ( !m_pIn )
   {
      ostringstream oss;
      oss << "fftwf_malloc failed on m_pIn. Trying to allocate <" 
          << sizeof(float) * (numSamplesPerFrame * m_maxFrames)
          << "> bytes";
      throw std::runtime_error(oss.str());
   }

   m_pOut = static_cast<fftwf_complex*>( fftwf_malloc(sizeof(fftwf_complex) * (numSamplesPerFrameOut* m_maxFrames) ) );
   if ( !m_pOut )
   {
      ostringstream oss;
      oss << "fftwf_malloc failed on m_pOut. Trying to allocate <" 
          << sizeof(fftwf_complex) * (numSamplesPerFrameOut* m_maxFrames)
          << "> bytes";

      throw std::runtime_error(oss.str());
   }

    // in destroyed when line executed
   m_p = fftwf_plan_many_dft_r2c(1, &numSamplesPerFrame, m_maxFrames,
                                 m_pIn, &numSamplesPerFrame, 1, numSamplesPerFrame,
                                 m_pOut, &numSamplesPerFrameOut,
                                 1, numSamplesPerFrameOut,
                                 FFTW_ESTIMATE | FFTW_DESTROY_INPUT);

   if ( !m_p )
      throw std::runtime_error ("fftwf_plan_many_dft_r2c failed");

    double base = exp( log( static_cast<double>(MAXFREQ) / static_cast<double>(MINFREQ) ) / 
                      static_cast<double>(Filter::NBANDS) 
                     );

   m_powTable.resize( Filter::NBANDS+1 );
   for ( unsigned int i = 0; i < Filter::NBANDS + 1; ++i )
      m_powTable[i] = static_cast<unsigned int>( (pow(base, static_cast<double>(i)) - 1.0) * MINCOEF );

   m_pFrames = new float*[m_maxFrames];

   if ( !m_pFrames )
   {
      ostringstream oss;
      oss << "Allocation failed on m_pFrames. Trying to allocate <" 
         << sizeof(float*) * m_maxFrames
         << "> bytes";

      throw std::runtime_error(oss.str());
   }


   for (int i = 0; i < m_maxFrames; ++i) 
   {
      m_pFrames[i] = new float[Filter::NBANDS];
      if ( !m_pFrames[i] )
         throw std::runtime_error("Allocation failed on m_pFrames");
   }

}

// ----------------------------------------------------------------------

OptFFT::~OptFFT()
{
    fftwf_destroy_plan(m_p);
    
    fftwf_free(m_pIn);
    fftwf_free(m_pOut);

   for (int i = 0; i < m_maxFrames; ++i)
      delete [] m_pFrames[i];

   delete [] m_pFrames;
}

// ----------------------------------------------------------------------

int OptFFT::process(float* pInData, const size_t dataSize)
{
   // generally is the same of the one we used in the constructor (m_maxFrames) but
   // might be less at the end of the stream
   int nFrames = static_cast<int>( (dataSize - FRAMESIZE) / OVERLAPSAMPLES + 1 );

   float* pIn_It = m_pIn;

   for (int i = 0; i < nFrames; ++i)
   {
      memcpy( pIn_It, &pInData[i*OVERLAPSAMPLES], sizeof(float) * FRAMESIZE);
      // apply hanning window
      applyHann(pIn_It, FRAMESIZE);

      pIn_It += FRAMESIZE;
   }

   // fill the rest with zeroes
   if ( nFrames < m_maxFrames )
      memset( pIn_It, 0, sizeof(float) * (m_maxFrames-nFrames) * FRAMESIZE );

   fftwf_execute(m_p);

   int totSamples = (FRAMESIZE/2+1) * // numSamplesPerFrameOut
                    nFrames; // the frames actually in the input

   // scaling (?)
   float scalingFactor = static_cast<float>(FRAMESIZE) / 2.0f;
   for (int k = 0; k < totSamples; ++k)
   {
      m_pOut[k][0] /= scalingFactor;
      m_pOut[k][1] /= scalingFactor;
   }

   int frameStart;
   unsigned int outBlocStart;
   unsigned int outBlocEnd;

    for (int i = 0; i < nFrames; ++i) 
   {
      frameStart = i * (FRAMESIZE/2+1);

       // compute bands
       for (unsigned int j = 0; j < Filter::NBANDS; j++) 
      {
         outBlocStart = m_powTable[j] + frameStart;
         outBlocEnd   = m_powTable[j+1] + frameStart;

           m_pFrames[i][j] = 0;

           // WARNING: We're double counting the last one here.
           // this bug is to match matlab's implementation bug in power2band.m
         unsigned int end_k = outBlocEnd + static_cast<unsigned int>(MINCOEF);
           for (unsigned int k = outBlocStart + static_cast<unsigned int>(MINCOEF); k <= end_k; k++) 
         {
               m_pFrames[i][j] += m_pOut[k][0] * m_pOut[k][0] + 
                               m_pOut[k][1] * m_pOut[k][1];
           }

           // WARNING: if we change the k<=end to k<end above, we need to change the following line
           m_pFrames[i][j] /= static_cast<float>(outBlocEnd - outBlocStart + 1);        
       }   
   }

   return nFrames;
}

// -----------------------------------------------------------------------------

void OptFFT::applyHann( float* pInData, const size_t dataSize )
{
   assert (dataSize == 2048);

   for ( size_t i = 0; i < dataSize; ++i )
      pInData[i] *= hann[i];
}

// -----------------------------------------------------------------------------

} // end of namespace

// ----------------------------------------------------------------------
0707010000005D000081A400000000000000000000000167B98EA800000563000000000000000000000000000000000000004E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib/OptFFT.h/*
   Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __OPT_FFT_H
#define __OPT_FFT_H

#include <fftw3.h>
#include <vector>

namespace fingerprint
{

class OptFFT
{
public:

   OptFFT(const size_t maxDataSize);
   ~OptFFT();

   int 
   process(float* pInData, const size_t dataSize);
   
   float** 
   getFrames()    { return m_pFrames; }

private:

   void applyHann(float* pInData, const size_t dataSize);

    fftwf_plan        m_p;
    fftwf_complex *   m_pOut;
    float*            m_pIn;

   //float   m_base;

   int     m_numSamples;
   int     m_numOutSamples;

   float** m_pFrames;
   int     m_maxFrames;

   std::vector<int> m_powTable;

};

} // end of namespace

#endif // OPT_FFT
0707010000005E000081A400000000000000000000000167B98EA8000031F0000000000000000000000000000000000000005500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/fingerprint/fplib/fp_helper_fun.h/*
   Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __FINGERPRINT_HELPER_FUNCTIONS_H
#define __FINGERPRINT_HELPER_FUNCTIONS_H

#include <vector>
#include <deque>
#include <set>

namespace fingerprint
{

// -----------------------------------------------------------------------------

static const size_t FINGERPRINT_LIB_VERSION = 1;
static const float  QUERY_START_SECS = 20;
static const float  QUERY_SIZE_SECS = 14;
static const float  UPDATE_SIZE_SECS = 10;
//FFT needs also a buffer that depends on the input freq. 3 secs should be enough up to 48Khz 
static const float  GUARD_SIZE_SECS = 3; 
static const float  NORMALIZATION_SKIP_SECS = 2.5;
static const int    MIN_UNIQUE_KEYS = 75;
static const unsigned int MAX_GOOD_GROUP_SIZE = 200;
static const int    SHA_SIZE = 32;

/////////////////////////////////////////////////////
// For FFT. DO NOT TOUCH THEM!
// number of samples in a frame
static const int FRAMESIZE = 2048;
static const int OVERLAP = 32;
static const int OVERLAPSAMPLES = (FRAMESIZE/OVERLAP); // 64

// down-sampled frequency
static const int DFREQ = 5512;
static const float FDFREQ = 5512.5f;

// -----------------------------------------------------------------------------

struct GroupData
{
   unsigned int key;    // the key (or local descriptor)
   unsigned int count;  // the number of frames sharing this key
};

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

inline 
unsigned int getTotalKeys( 
   int mSecs )
{
   return static_cast<unsigned int>((static_cast<double>(mSecs) / (1000.0 * OVERLAPSAMPLES) ) * DFREQ ) + 1;
}

// -----------------------------------------------------------------------------

template <typename GroupDataIt>
void simpleSkip( 
   GroupDataIt& begIt, const GroupDataIt& endIt,
   unsigned int numSkipKeys )
{
   if ( numSkipKeys <= 0 )
      return;

   unsigned int nKeys;
   for ( nKeys = 0; nKeys < numSkipKeys && begIt != endIt; ++begIt )
      nKeys += begIt->count;

   // clear crop at the end
   if ( nKeys > numSkipKeys )
   {
      --begIt;
      begIt->count = nKeys - numSkipKeys;
   }

}

// -----------------------------------------------------------------------------

template <typename TGroupData>
void cutGroups( 
   std::vector<TGroupData>& groups, 
   const unsigned int startMS, 
   const unsigned int lengthMS )
{
   typename  std::vector<TGroupData>::iterator itBeg = groups.begin(), itEnd = groups.begin();

   unsigned int keys_begin, keys_end;

   for (keys_begin = getTotalKeys(startMS);
                  itBeg != groups.end() && keys_begin > itBeg->count; ++itBeg)
      keys_begin -= itBeg->count;

   for (keys_end = getTotalKeys(startMS + lengthMS);
        itEnd != groups.end() && keys_end > itEnd->count; ++itEnd)
      keys_end -= itEnd->count;

   if (itBeg == groups.end())  // in the umpossible scenario that you try to cut past the size of the groups
   {
      groups.clear();
      return;
   }

   itBeg->count -= keys_begin;
   if (keys_end > 0 && itEnd != groups.end())
   {
      itEnd->count = keys_end;
      ++itEnd;
   }

   copy(itBeg, itEnd, groups.begin());
   groups.resize(itEnd - itBeg);

   keys_begin = getTotalKeys(lengthMS);
   for (typename std::vector<TGroupData>::iterator it = groups.begin(); it != groups.end(); ++it)
      keys_begin -= it->count;
}

// -------------------------------------------------------------------------

template <typename TGroupData>
void keys2GroupData( 
   const std::vector<unsigned int>& keys, // in
   std::vector<TGroupData>& groupData,
   bool clearDst = true ) // out
{
   if (clearDst)
      groupData.clear();

   if (keys.empty())
      return;

   TGroupData tmpGroup;
   std::vector<unsigned int>::const_iterator it = keys.begin();

   if ( !groupData.empty() )
   {
      // get the last group 
      tmpGroup = groupData.back();
      groupData.pop_back();
   }
   else
   {
      // new group!
      tmpGroup.key = *it;
      tmpGroup.count = 1;
      ++it; // move to the next key
   }

   for (; it != keys.end(); ++it)
   {
      if ( *it != tmpGroup.key )
      {
         // new group ready!
         groupData.push_back( tmpGroup );
         tmpGroup.key = *it;
         tmpGroup.count = 0;
      }

      ++tmpGroup.count;
   }

   // last group
   groupData.push_back( tmpGroup );
}

// -------------------------------------------------------------------------

template <typename TGroupData>
void keys2GroupData( 
   const std::vector<unsigned int>& keys, // in
   std::deque<TGroupData>& groupData,
   bool clearDst = true ) // out
{
   if (clearDst)
      groupData.clear();

   if (keys.empty())
      return;

   TGroupData tmpGroup;
   std::vector<unsigned int>::const_iterator it = keys.begin();

   if ( !groupData.empty() )
   {
      // get the last group 
      tmpGroup = groupData.back();
      groupData.pop_back();
   }
   else
   {
      // new group!
      tmpGroup.key = *it;
      tmpGroup.count = 1;
      ++it; // move to the next key
   }

   for (; it != keys.end(); ++it)
   {
      if ( *it != tmpGroup.key )
      {
         // new group ready!
         groupData.push_back( tmpGroup );
         tmpGroup.key = *it;
         tmpGroup.count = 0;
      }

      ++tmpGroup.count;
   }

   // last group
   groupData.push_back( tmpGroup );
}

// -------------------------------------------------------------------------

template <typename TGroupData>
inline 
void groupData2Keys( 
   const std::vector<TGroupData>& groupData, // in
   std::vector<unsigned int>& keys ) // out
{
   keys.clear();

   typename std::vector<TGroupData>::const_iterator it;
   
   for (it = groupData.begin(); it != groupData.end(); ++it)
   {
      for (unsigned int j = 0; j < it->count; ++j)
         keys.push_back(it->key);
   }
}

// -------------------------------------------------------------------------

template <typename GroupDataIt>
bool findSignificantGroups(
   GroupDataIt& beg, GroupDataIt& end, unsigned int& offset_left, unsigned int& offset_right,
   unsigned int windowKeySize, unsigned int subWindowKeySize, unsigned int minUniqueKeys)
{
   GroupDataIt itBeg = beg, itEnd = beg, itWindowBeg = beg, itWindowEnd = beg;

   offset_left = 0;
   unsigned int window_offset_left;
   unsigned int window_offset_right;

   // this amounts to around a 500 ms hop for, say, a 20 second sub-window
   unsigned int key_hop_size = subWindowKeySize / 40;  

   // trail out itEnd
   for (offset_right = windowKeySize; itEnd != end && offset_right > itEnd->count; ++itEnd)
      offset_right -= itEnd->count;

   // dang man, we don't even have enough groups to span the window size
   if (itEnd == end && offset_right > 0)
      return false;

   // 0 window size means just scan the whole range
   if (windowKeySize == 0)
      itEnd = end;

   // trail out itWindowBeg
   for (window_offset_left = (windowKeySize - subWindowKeySize) / 2;
        window_offset_left > itWindowBeg->count; ++itWindowBeg)
      window_offset_left -= itWindowBeg->count;

   // trail out itWindowEnd
   for (window_offset_right = (windowKeySize + subWindowKeySize) / 2;
      window_offset_right > itWindowEnd->count; ++itWindowEnd)
      window_offset_right -= itWindowEnd->count;

   while (itEnd != end)
   {
      if (enoughUniqueGoodGroups(itWindowBeg, itWindowEnd, minUniqueKeys))
      {
         beg = itBeg;
         end = itEnd;
         return true;
      }

      // okay, jump key_hop_size on end iterator
      for (offset_right += key_hop_size; itEnd != end && offset_right > itEnd->count; ++itEnd)
         offset_right -= itEnd->count;

      // if we didn't hop the full hop size, modify the hop size to only hop as far as we hopped
      if (itEnd == end)
         key_hop_size -= offset_right;

      for (offset_left += key_hop_size; offset_left > itBeg->count; ++itBeg)
         offset_left -= itBeg->count;
      for (window_offset_right += key_hop_size; window_offset_right > itWindowEnd->count; ++itWindowEnd)
         window_offset_right -= itWindowEnd->count;
      for (window_offset_left += key_hop_size; window_offset_left > itWindowBeg->count; ++itWindowBeg)
         window_offset_left -= itWindowBeg->count;
   }

   beg = itBeg;
   end = itEnd;

   return enoughUniqueGoodGroups(itWindowBeg, itWindowEnd, minUniqueKeys);
}

// -----------------------------------------------------------------------------

template <typename TGroupData>
bool
reduceGroups( 
  std::vector<TGroupData>& groups, unsigned int startKeySize,
  unsigned int windowKeySize, unsigned int subWindowKeySize, unsigned int minUniqueKeys )
{
   unsigned int offset_left = 0;
   unsigned int offset_right = 0;

   typename std::vector<TGroupData>::iterator begIt = groups.begin();
   typename std::vector<TGroupData>::iterator endIt = groups.end();

   simpleSkip(begIt, endIt, startKeySize);
   bool result = findSignificantGroups( begIt, endIt, 
      offset_left, offset_right, 
      windowKeySize, subWindowKeySize, minUniqueKeys );

   if ( !result )
   {
      groups.clear();
      return false;
   }

   begIt->count -= offset_left;
   if (offset_right > 0 && endIt != groups.end())
   {
      endIt->count = offset_right;
      ++endIt;
   }

   std::vector<TGroupData> resGrups(begIt, endIt);
   groups.swap(resGrups);

   return true;
}


// -------------------------------------------------------------------------

template <typename GroupDataIt>
inline bool enoughUniqueGoodGroups(
   const GroupDataIt& beg, 
   const GroupDataIt& end, 
   unsigned int minUniqueKeys)
{
   std::set<unsigned int> groupKeys;

   for (GroupDataIt it = beg; it != end && static_cast<unsigned int>(groupKeys.size()) < minUniqueKeys; ++it)
   {
      if (it->count > MAX_GOOD_GROUP_SIZE)
         return false;

      groupKeys.insert(it->key);
   }
   
   return static_cast<unsigned int>(groupKeys.size()) >= minUniqueKeys;
}

// -----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Used by the fingerprint keys operation

// minimum and maximum frequency to consider
#define MINFREQ 300
#define MAXFREQ 2000

// amount of time in a frame
#define FRAME_TLEN ((float) FRAMESIZE / (float) DFREQ)
#define MINCOEF (FRAME_TLEN * MINFREQ)

#define round__(x)  ((int)(x + .5))

struct RawFilter
{
   unsigned int ftid;
   float thresh;
    float weight;
};

const RawFilter rFilters[] = {
   { 26752, -4.37515e-07f, 0.260836f }, // filterID, threshold, alpha (weight)
   { 23871, -2.44615e-05f, 0.263986f },
   { 26777, -3.69244e-08f, 0.267763f },
   { 4635,  -1.13672e-05f, 0.269428f },
   { 2937,   5.28804e-09f, 0.271896f },
   { 27405, -0.000126494f, 0.272362f },
   { 10782,  4.27478e-08f, 0.272609f },
   { 21033, -6.7912e-07f,  0.276099f },
   { 27117,  8.07178e-06f, 0.277762f },
   { 27072,  2.46044e-05f, 0.27883f  },
   { 24228,  4.11255e-07f, 0.281743f },
   { 23838,  0.000228396f, 0.284479f },
   { 17165, -1.19495e-07f, 0.286304f },
   { 25263,  0.000398279f, 0.287066f },
   { 20721,  7.15095e-07f, 0.288913f },
   { 8502,  -2.78361e-07f, 0.290424f },
   { 17175, -1.08429e-08f, 0.292219f },
   { 17811, -3.29527e-08f, 0.292554f },
   { 27495, -4.47575e-07f, 0.290119f },
   { 23538, -3.04273e-09f, 0.294539f },
   { 8205,   4.02691e-07f, 0.293525f },
   { 12177,  1.16873e-06f, 0.293832f },
   { 27051, -0.000902544f, 0.296453f },
   { 27111, -2.38425e-05f, 0.297428f },
   { 21779, -1.0669e-07f,  0.297302f },
   { 14817, -9.52849e-09f, 0.299f    },
   { 27087,  1.22163e-05f, 0.296502f },
   { 27081, -2.8758e-09f,  0.300112f },
   { 20394,  1.28237e-06f, 0.298693f },
   { 28209,  0.000624447f, 0.29812f  },
   { 23533, -2.19406e-06f, 0.299773f },
   { 23865, -1.28037e-08f, 0.300777f } // this is iteration 1
};

// -----------------------------------------------------------------------------

}

// -----------------------------------------------------------------------------

#endif // __FINGERPRINT_HELPER_FUNCTIONS_H

0707010000005F000081A400000000000000000000000167B98EA8000009A1000000000000000000000000000000000000003F00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/global.h.in/*
   Copyright 2009 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef LASTFM_GLOBAL_H
#define LASTFM_GLOBAL_H

#define LASTFM_VERSION @LASTFM_VERSION@
#define LASTFM_VERSION_STRING "@LASTFM_VERSION_STRING@"
#define LASTFM_MAJOR_VERSION @LASTFM_MAJOR_VERSION@
#define LASTFM_MINOR_VERSION @LASTFM_MINOR_VERSION@
#define LASTFM_PATCH_VERSION @LASTFM_PATCH_VERSION@


#ifndef LASTFM_LIB_STATIC
    #ifdef LASTFM_LIB
        #define LASTFM_DLLEXPORT Q_DECL_EXPORT
    #else
        #define LASTFM_DLLEXPORT Q_DECL_IMPORT
    #endif
    #ifdef LASTFM_FINGERPRINT_LIB
        #define LASTFM_FINGERPRINT_DLLEXPORT Q_DECL_EXPORT
    #else
        #define LASTFM_FINGERPRINT_DLLEXPORT Q_DECL_IMPORT
    #endif
#else // LASTFM_LIB_STATIC
    #define LASTFM_DLLEXPORT
    #define LASTFM_FINGERPRINT_DLLEXPORT
#endif // LASTFM_LIB_STATIC



#include <QMetaEnum>
#include <QString>

namespace lastfm
{
    /** http://labs.trolltech.com/blogs/2008/10/09/coding-tip-pretty-printing-enum-values
      * Tips for making this take a single parameter welcome! :)
      *
      * eg. lastfm::qMetaEnumString<QNetworkReply>( error, "NetworkError" );
      */
    template <typename T> static inline QString qMetaEnumString( int enum_value, const char* enum_name )
    {
        QMetaObject meta = T::staticMetaObject;
        for (int i=0; i < meta.enumeratorCount(); ++i)
        {
            QMetaEnum m = meta.enumerator(i);
            if (QLatin1String(m.name()) == QLatin1String(enum_name))
                return QLatin1String(m.valueToKey(enum_value));
        }
        return QString("Unknown enum value for \"%1\": %2").arg( enum_name ).arg( enum_value );
    }
}


#ifdef LASTFM_COLLAPSE_NAMESPACE
using namespace ::lastfm;
#endif


#endif //LASTFM_GLOBAL_H
07070100000060000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000003900000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/linux07070100000061000081A400000000000000000000000167B98EA800000680000000000000000000000000000000000000005500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/linux/LNetworkConnectionMonitor.h/*
   Copyright 2010 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/


#ifndef LNETWORK_CONNECTION_MONITOR_H
#define LNETWORK_CONNECTION_MONITOR_H

#include "../NetworkConnectionMonitor.h"
#include "global.h"

class QDBusConnection;
class QDBusInterface;

namespace lastfm
{

class LNetworkConnectionMonitor : public NetworkConnectionMonitor
{
    Q_OBJECT

    typedef enum {
        NM_STATE_UNKNOWN          = 0,
        NM_STATE_ASLEEP           = 10,
        NM_STATE_DISCONNECTED     = 20,
        NM_STATE_DISCONNECTING    = 30,
        NM_STATE_CONNECTING       = 40,
        NM_STATE_CONNECTED_LOCAL  = 50,
        NM_STATE_CONNECTED_SITE   = 60,
        NM_STATE_CONNECTED_GLOBAL = 70
    } NMState;

public:
    LNetworkConnectionMonitor( QObject* parent = 0 );
    ~LNetworkConnectionMonitor();
private Q_SLOTS:
    void onStateChange( uint newState );
private:
    QDBusInterface* m_nmInterface;
};

}

#endif // LNETWORK_CONNECTION_MONITOR_H

07070100000062000081A400000000000000000000000167B98EA800000BD6000000000000000000000000000000000000005D00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/linux/LNetworkConnectionMonitor_linux.cpp/*
   Copyright 2010 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/


#include "LNetworkConnectionMonitor.h"

#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusReply>
#include <QDebug>

#define NM_DBUS_SERVICE   "org.freedesktop.NetworkManager"
#define NM_DBUS_PATH      "/org/freedesktop/NetworkManager"
#define NM_DBUS_INTERFACE "org.freedesktop.NetworkManager"

lastfm::LNetworkConnectionMonitor::LNetworkConnectionMonitor( QObject* parent ) :
    NetworkConnectionMonitor( parent )
{
    m_nmInterface = new QDBusInterface( NM_DBUS_SERVICE,
                                        NM_DBUS_PATH,
                                        NM_DBUS_INTERFACE,
                                        QDBusConnection::systemBus(),
                                        this );
    if ( !m_nmInterface->isValid() )
    {
        qDebug() << "Unable to watch network state changes via D-Bus.";
        return;
    }

    //get current connection state
    QDBusReply<uint> reply = m_nmInterface->call( QDBus::AutoDetect, "state" );
    if ( reply.isValid() )
    {
        if ( reply.value() == NM_STATE_CONNECTED_GLOBAL )
        {
            setConnected( true );
        }
        else if ( reply.value() == NM_STATE_DISCONNECTED || reply.value() == NM_STATE_ASLEEP )
        {
            setConnected( false );
        }
    }
    else
    {
        qDebug() << "Error: " << reply.error();
    }

    //connect network manager signals
   m_nmInterface->connection().connect( NM_DBUS_SERVICE,
                                        NM_DBUS_PATH,
                                        NM_DBUS_INTERFACE,
                                        "StateChanged",
                                        this,
                                        SLOT( onStateChange( uint ) )
                                      );

}

lastfm::LNetworkConnectionMonitor::~LNetworkConnectionMonitor()
{
    delete m_nmInterface;
}


void
lastfm::LNetworkConnectionMonitor::onStateChange( uint newState )
{
    qDebug() << "Networkmanager state change!";

    if ( newState == NM_STATE_DISCONNECTED || newState == NM_STATE_ASLEEP )
    {
       setConnected( false );
    }
    else if ( newState == NM_STATE_CONNECTED_GLOBAL )
    {
       setConnected( true );
    }
}

#include "moc_LNetworkConnectionMonitor.cpp"
07070100000063000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000003700000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/mac07070100000064000081A400000000000000000000000167B98EA8000005E2000000000000000000000000000000000000005300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/mac/MNetworkConnectionMonitor.h/*
   Copyright 2009 Last.fm Ltd.
      - Primarily authored by Jono Cole and Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef MNETWORK_CONNECTION_MONITOR_H
#define MNETWORK_CONNECTION_MONITOR_H

#include "../NetworkConnectionMonitor.h"

#ifdef Q_OS_MAC
#include <SystemConfiguration/SCNetwork.h> //TODO remove
#include <SystemConfiguration/SCNetworkReachability.h>
#endif

class __SCNetworkReachability;

namespace lastfm
{

class MNetworkConnectionMonitor : public NetworkConnectionMonitor
{
    Q_OBJECT
public:
    MNetworkConnectionMonitor( QObject* parent = 0 );
    ~MNetworkConnectionMonitor();
private Q_SLOTS:

private:
#ifdef Q_OS_MAC
    static void callback( SCNetworkReachabilityRef target,
                          SCNetworkConnectionFlags flags,
                          void *info );
#endif
};

}

#endif // MNETWORK_CONNECTION_MONITOR_H

07070100000065000081A400000000000000000000000167B98EA8000008B0000000000000000000000000000000000000005900000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/mac/MNetworkConnectionMonitor_mac.cpp/*
   Copyright 2009 Last.fm Ltd.
      - Primarily authored by Jono Cole and Michael Coffey

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "MNetworkConnectionMonitor.h"
#include "moc_MNetworkConnectionMonitor.cpp"
#include "../ws.h"

#include <QPointer>
#include <SystemConfiguration/SCNetworkReachability.h>

lastfm::MNetworkConnectionMonitor* context = 0;

lastfm::MNetworkConnectionMonitor::MNetworkConnectionMonitor( QObject* parent ) :
    NetworkConnectionMonitor( parent )
{
    context = this;

    SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName( NULL, LASTFM_WS_HOSTNAME );
    SCNetworkReachabilityScheduleWithRunLoop( ref, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
    SCNetworkReachabilitySetCallback( ref, callback, NULL );
    CFRelease( ref );
}

lastfm::MNetworkConnectionMonitor::~MNetworkConnectionMonitor()
{
}


void
lastfm::MNetworkConnectionMonitor::callback( SCNetworkReachabilityRef target,
                                     SCNetworkConnectionFlags flags,
                                     void * )
{
    static bool up = true;

    // I couldn't find any diffinitive usage examples for these flags
    // so I had to guess, since I can't test, eg. dial up :(

    bool b;
    if (flags & kSCNetworkFlagsConnectionRequired)
        b = false;
    else
        b = flags & (kSCNetworkFlagsReachable | kSCNetworkFlagsTransientConnection | kSCNetworkFlagsConnectionAutomatic);

    // basically, avoids telling everyone that we're up already on startup
    if (up == b)
        return;

    up = b;

    context->setConnected(b);
}

07070100000066000081A400000000000000000000000167B98EA800000A06000000000000000000000000000000000000004300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/mac/ProxyDict.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <SystemConfiguration/SystemConfiguration.h>

struct ProxyDict
{
    ProxyDict();

    int port;
    QString host;
    
    bool isProxyEnabled() const { return port > 0 && host.size(); }
};


inline ProxyDict::ProxyDict() : port( 0 )
{
    // Get the dictionary.
    CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies( NULL );
    bool result = (proxyDict != NULL);

    // Get the enable flag.  This isn't a CFBoolean, but a CFNumber.
    CFNumberRef enableNum;
    int enable;
    if (result) {
        enableNum = (CFNumberRef) CFDictionaryGetValue( proxyDict, kSCPropNetProxiesHTTPEnable );
        result = (enableNum != NULL) && (CFGetTypeID(enableNum) == CFNumberGetTypeID());
    }
    if (result)
        result = CFNumberGetValue( enableNum, kCFNumberIntType, &enable ) && (enable != 0);

    // Get the proxy host.  DNS names must be in ASCII.  If you 
    // put a non-ASCII character  in the "Secure Web Proxy"
    // field in the Network preferences panel, the CFStringGetCString
    // function will fail and this function will return false.
    CFStringRef hostStr;
    if (result) {
        hostStr = (CFStringRef) CFDictionaryGetValue( proxyDict, kSCPropNetProxiesHTTPProxy );
        result = (hostStr != NULL) && (CFGetTypeID(hostStr) == CFStringGetTypeID());
    }
    if (result)
        host = lastfm::CFStringToQString( hostStr );

    // get the proxy port
    CFNumberRef portNum;

    if (result) {
        portNum = (CFNumberRef) CFDictionaryGetValue( proxyDict, kSCPropNetProxiesHTTPPort );
        result = (portNum != NULL) && (CFGetTypeID(portNum) == CFNumberGetTypeID());
    }
    if (result)
        result = CFNumberGetValue( portNum, kCFNumberIntType, &port );

    // clean up.
    if (proxyDict != NULL)
        CFRelease( proxyDict );
}
07070100000067000081A400000000000000000000000167B98EA8000015CF000000000000000000000000000000000000003E00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/mbid_mp3.c/*
*  LICENSE
*  
*  Copyright (c) 2006, David Nicolson
*  All rights reserved.
*  
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*  
*    1. Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*    2. Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*    3. Neither the name of the author nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
*  
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
*  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT UNLESS REQUIRED BY
*  LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER OR CONTRIBUTOR
*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
*  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
*  OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, PROFITS; OR
*  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
*  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
*  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
*  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef __MBID_MP3_H
#define __MBID_MP3_H

#define MBID_BUFFER_SIZE 37

#include <stdio.h>

// -----------------------------------------------------------------------------

void mfile(size_t length, char ret[], FILE *fp, int *s) {
   size_t bytes = fread(ret,1,length,fp);

   if (bytes != length) {
      *s = 0;
   }
}

// -----------------------------------------------------------------------------

int to_synch_safe(char bytes[]) {
   return ((int)bytes[0] << 21) + ((int)bytes[1] << 14) + ((int)bytes[2] << 7) + (int)bytes[3];
}

int to_integer(char (&bytes)[4]) {
   size_t size = 0;
   uint i;
   for (i=0; i < sizeof(bytes); i++) {
      size = size * 256 + ((int)bytes[i] & 0x000000FF);
   }
   return static_cast<int>(size);
}

// -----------------------------------------------------------------------------

int getMP3_MBID(const char *path, char mbid[MBID_BUFFER_SIZE]) 
{
   FILE *fp;
   static int s = 1;
   char head[3];
   char version[2];
   char flag[1];
   char size[4];
   char size_extended[4];
   int tag_size = 0;
   int extended_size = 0;
   char frame[4];
   char frame_header[4];
   int frame_size;
   int version_major;//, version_minor;

   if (path == NULL) {
      //debug("Received null path\n");
      return -1;
   }

   fp = fopen(path,"rb");
   if (fp == NULL) {
      //debug("Failed to open music file: %s\n",path);
      return -1;
   }

   while (s) {
      mfile(3,head,fp,&s);
      if (!strncmp(head,"ID3",3) == 0) {
         //debug("No ID3v2 tag found: %s\n",path);
         break;
      }

      mfile(2,version,fp,&s);
      version_major = (int)version[0];
      //version_minor = (int)version[1];
      if (version_major == 2) {
         //debug("ID3v2.2.0 does not support MBIDs: %s\n",path);
         break;
      }
      if (version_major != 3 && version_major != 4) {
         //debug("Unsupported ID3 version: v2.%d.%d\n",version_major,version_minor);
         break;
      }

      mfile(1,flag,fp,&s);
      if ((unsigned int)flag[0] & 0x00000040) {
         //debug("Extended header found\n");
         if (version[0] == 4) {
            mfile(4,size_extended,fp,&s);
            extended_size = to_synch_safe(size_extended);
         } else {
            mfile(4,size_extended,fp,&s);
            extended_size = to_integer(size_extended);
         }
         //debug("Extended header size: %d\n",extended_size);
         fseek(fp,extended_size,SEEK_CUR);
      }

      mfile(4,size,fp,&s);
      tag_size = to_synch_safe(size);
      //debug("Tag size: %d\n",tag_size);

      while (s) {
         if (ftell(fp) > tag_size || ftell(fp) > 1048576) {
            break;
         }

         mfile(4,frame,fp,&s);
         if (frame[0] == 0x00) {
            break;
         }
         if (version_major == 4) {
            mfile(4,frame_header,fp,&s);
            frame_size = to_synch_safe(frame_header);
         } else {
            mfile(4,frame_header,fp,&s);
            frame_size = to_integer(frame_header);
         }

         fseek(fp,2,SEEK_CUR);
         //debug("Reading %d bytes from frame %s\n",frame_size,frame);

         if (strncmp(frame,"UFID",4) == 0) {
            //char frame_data[frame_size];
            char frame_data[59];
            mfile(59,frame_data,fp,&s);
            if (frame_size >= 59 && strncmp(frame_data,"http://musicbrainz.org",22) == 0) {
               char *tmbid = frame_data;
               tmbid = frame_data + 23;
               strncpy(mbid,tmbid,MBID_BUFFER_SIZE-1);
               mbid[MBID_BUFFER_SIZE-1] = 0x00;
               fclose(fp);
               return 0;
            }
         } else {
            fseek(fp,frame_size,SEEK_CUR);
         }
      }
      break;
   }

   if (fp) {
      fclose(fp);
   }
   //if (!s) {
   //   debug("Failed to read music file: %s\n",path);
   //}                
   return -1;

}

#endif

// -----------------------------------------------------------------------------
07070100000068000081A400000000000000000000000167B98EA8000011C2000000000000000000000000000000000000003C00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/misc.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "misc.h"

#include <QCryptographicHash>
#include <QDir>
#include <QDebug>
#ifdef WIN32
    #include <shlobj.h>
#endif


#ifdef Q_OS_MAC
#include <CoreFoundation/CoreFoundation.h>

QDir
lastfm::dir::bundle()
{
    // Trolltech provided example
    CFURLRef appUrlRef = CFBundleCopyBundleURL( CFBundleGetMainBundle() );
    CFStringRef macPath = CFURLCopyFileSystemPath( appUrlRef, kCFURLPOSIXPathStyle );
    QString path = CFStringToQString( macPath );
    CFRelease(appUrlRef);
    CFRelease(macPath);
    return QDir( path );
}
#endif


QDir ensurePathExists( QDir dir )
{
    if ( !dir.exists() )
        dir.mkpath( QString( "." ) );

    return dir;
}


static QDir dataDotDot()
{
#ifdef WIN32
    // Use this for non-DOS-based Windowses
    char path[MAX_PATH];
    HRESULT h = SHGetFolderPathA( NULL, 
                                  CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE,
                                  NULL, 
                                  0, 
                                  path );
    if (h == S_OK)
        return QString::fromLocal8Bit( path );
    else
        return QDir::home();
#elif defined(Q_OS_MAC)
    return ensurePathExists( QDir::home().filePath( "Library/Application Support" ) );
#elif defined(Q_OS_LINUX) || defined(Q_OS_UNIX)
    return ensurePathExists( QDir::home().filePath( ".local/share" ) );
#else
    return QDir::home();
#endif
}


QDir
lastfm::dir::runtimeData()
{
    return ensurePathExists( dataDotDot().filePath( "Last.fm" ) );
}


QDir
lastfm::dir::logs()
{
#ifdef Q_OS_MAC
    return ensurePathExists( QDir::home().filePath( "Library/Logs/Last.fm" ) );
#else
    return runtimeData();    
#endif
}


QDir
lastfm::dir::cache()
{
#ifdef Q_OS_MAC
    return ensurePathExists( QDir::home().filePath( "Library/Caches/Last.fm" ) );
#else
    return ensurePathExists( runtimeData().filePath( "cache" ) );
#endif
}


#ifdef WIN32
QDir
lastfm::dir::programFiles()
{
    char path[MAX_PATH];

    // TODO: this call is dependant on a specific version of shell32.dll.
    // Need to degrade gracefully. Need to bundle SHFolder.exe with installer
    // and execute it on install for this to work on Win98.
    HRESULT h = SHGetFolderPathA( NULL,
                                 CSIDL_PROGRAM_FILES, 
                                 NULL,
                                 0, // current path
                                 path );

    if (h != S_OK)
    {
        qCritical() << "Couldn't get Program Files dir. Possibly Win9x?";
        return QDir();
    }

    return QString::fromLocal8Bit( path );
}
#endif

#ifdef Q_OS_MAC
CFStringRef
lastfm::QStringToCFString( const QString &s )
{
    return CFStringCreateWithCharacters( 0, (UniChar*)s.unicode(), s.length() );
}

QByteArray
lastfm::CFStringToUtf8( CFStringRef s )
{
    QByteArray result;

    if (s != NULL) 
    {
        CFIndex length;
        length = CFStringGetLength( s );
        length = CFStringGetMaximumSizeForEncoding( length, kCFStringEncodingUTF8 ) + 1;
        char* buffer = new char[length];

        if (CFStringGetCString( s, buffer, length, kCFStringEncodingUTF8 ))
            result = QByteArray( buffer );
        else
            qWarning() << "CFString conversion failed.";

        delete[] buffer;
    }

    return result;
}
#endif


const char*
lastfm::platform()
{
    static QString platform = QSysInfo::prettyProductName();
    return qPrintable(platform);
}

QString lastfm::
md5( const QByteArray& src )
{
    QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 );
    return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' ).toLower();
}

#ifdef Q_OS_MAC
QString
lastfm::CFStringToQString( CFStringRef s )
{
    return QString::fromUtf8( CFStringToUtf8( s ) );
}
#endif

07070100000069000081A400000000000000000000000167B98EA800000643000000000000000000000000000000000000003A00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/misc.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_MISC_H
#define LASTFM_MISC_H

#include "global.h"
#include <QDir>

#ifdef Q_OS_MAC
typedef const struct __CFString* CFStringRef;
#endif

namespace lastfm
{
    namespace dir
    {
    #ifdef Q_OS_WIN
        LASTFM_DLLEXPORT QDir programFiles();
    #endif
    #ifdef Q_OS_MAC
        LASTFM_DLLEXPORT QDir bundle();
    #endif
        LASTFM_DLLEXPORT QDir runtimeData();
        LASTFM_DLLEXPORT QDir cache();
        LASTFM_DLLEXPORT QDir logs();
    }
   
#ifdef Q_OS_MAC 
    LASTFM_DLLEXPORT QByteArray CFStringToUtf8( CFStringRef );
    LASTFM_DLLEXPORT CFStringRef QStringToCFString( const QString& );
    LASTFM_DLLEXPORT QString CFStringToQString( CFStringRef s );
#endif
    LASTFM_DLLEXPORT const char* platform();
    LASTFM_DLLEXPORT QString md5( const QByteArray& src );
}
#endif //LASTFM_MISC_H
0707010000006A000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000003700000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/win0707010000006B000081A400000000000000000000000167B98EA800000723000000000000000000000000000000000000004200000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/win/ComSetup.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _WIN32_WINNT
// This means we are targetting Windows XP onwards
// bring in CoInitializeSecurity from objbase.h
#define _WIN32_WINNT 0x0501
#endif

#include <objbase.h>

#ifndef __MINGW32__
    #include <atlbase.h>
    #include <atlcom.h>
#endif


/** @brief WsConnectionMonitor needs Com to work as early as possible so we do this
  * @author <doug@last.fm> 
  */
class ComSetup
{
public:
    ComSetup()
    {
        HRESULT hr = CoInitialize(0);
        m_bComInitialised = SUCCEEDED(hr);
        _ASSERT(m_bComInitialised);
        if (m_bComInitialised) {
            setupSecurity();
        }
    }
    
    void setupSecurity()
    {
        CSecurityDescriptor sd;
        sd.InitializeFromThreadToken();
        HRESULT hr = CoInitializeSecurity(sd, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); 
        _ASSERT(SUCCEEDED(hr));
    }
    
    ~ComSetup()
    {
        if (m_bComInitialised) {
            CoUninitialize();
        }
    }
    
private:
    bool m_bComInitialised;
};
0707010000006C000081A400000000000000000000000167B98EA80000056D000000000000000000000000000000000000004400000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/win/IeSettings.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <windows.h>
#include <winhttp.h>


/** @brief memory managing wrapper for WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
  * @author <doug@last.fm>
  */
struct IeSettings : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
{
    IeSettings()
    {
        if (!WinHttpGetIEProxyConfigForCurrentUser(this)) {
            fAutoDetect = FALSE;
            lpszAutoConfigUrl = lpszProxy = lpszProxyBypass = 0;
        }
    }
    
    ~IeSettings()
    {
        if (lpszAutoConfigUrl) GlobalFree(lpszAutoConfigUrl);
        if (lpszProxy) GlobalFree(lpszProxy);
        if (lpszProxyBypass) GlobalFree(lpszProxyBypass);
    }
};
0707010000006D000081A400000000000000000000000167B98EA800000BAE000000000000000000000000000000000000004600000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/win/NdisEvents.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "NdisEvents.h"
#include "WmiSink.h"

// see http://msdn.microsoft.com/en-us/magazine/cc301850.aspx for
// more about Ndis and wmi and getting these events

// Link to wbemuuid.lib to resolve IWbemObjectSink and IWbemClassObject
// interface definitions.

NdisEvents::NdisEvents()
    : m_pSink(0)
{}

NdisEvents::~NdisEvents()
{
    if (m_pSink)
        m_pSink->disconnect();
    if (m_pServices && m_pSink)
        m_pServices->CancelAsyncCall(m_pSink);
    // and reference counting will take care of the WmiSink object
}

HRESULT
NdisEvents::registerForNdisEvents()
{
    HRESULT hr = m_pLocator.CoCreateInstance(CLSID_WbemLocator);
    if (FAILED(hr))
        return hr;

    // Connect to the root\wmi namespace with the current user.
    hr = m_pLocator->ConnectServer(CComBSTR("ROOT\\WMI"),       // strNetworkResource
                                    NULL,               // strUser
                                    NULL,               // strPassword
                                    NULL,               // strLocale  
                                    0,                  // lSecurityFlags
                                    CComBSTR(""),       // strAuthority               
                                    NULL,               // pCtx
                                    &m_pServices
                                    );
    if (FAILED(hr))
        return hr;

    m_pSink = new WmiSink(this);

    //////////////////////////

    // other notifications we're not interested in right now include...
    // MSNdis_NotifyAdapterArrival  \DEVICE\<guid>
    // MSNdis_NotifyAdapterRemoval
    // MSNdis_StatusLinkSpeedChange
    // MSNdis_NotifyVcArrival
    // MSNdis_NotifyVcRemoval
    // MSNdis_StatusResetStart
    // MSNdis_StatusResetEnd
    // MSNdis_StatusProtocolBind
    // MSNdis_StatusProtocolUnbind
    // MSNdis_StatusMediaSpecificIndication

    CComBSTR wql("WQL");
    CComBSTR query("SELECT * FROM MSNdis_StatusMediaDisconnect");
    hr = m_pServices->ExecNotificationQueryAsync(wql, query, 0, 0, m_pSink);

    query = "SELECT * FROM MSNdis_StatusMediaConnect";
    hr = m_pServices->ExecNotificationQueryAsync(wql, query, 0, 0, m_pSink);

    return S_OK;
}

0707010000006E000081A400000000000000000000000167B98EA8000004DA000000000000000000000000000000000000004400000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/win/NdisEvents.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NDIS_EVENTS_H
#define NDIS_EVENTS_H

#include <windows.h>

#ifndef __MINGW32__
#include <atlbase.h>
#include <WbemCli.h>
#endif

class NdisEvents
{
public:
    NdisEvents();
    ~NdisEvents();
    HRESULT registerForNdisEvents();

    virtual void onConnectionUp(BSTR name) = 0;
    virtual void onConnectionDown(BSTR name) = 0;

private:
    CComPtr<IWbemLocator> m_pLocator;
    CComPtr<IWbemServices> m_pServices;
    class WmiSink *m_pSink;
};

#endif

0707010000006F000081A400000000000000000000000167B98EA8000004F8000000000000000000000000000000000000005300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/win/WNetworkConnectionMonitor.h/*
   Copyright 2010 Last.fm Ltd.
      - Primarily authored by Jono Cole, Michael Coffey, and William Viana

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef WNETWORK_CONNECTION_MONITOR_H
#define WNETWORK_CONNECTION_MONITOR_H

#include "../NetworkConnectionMonitor.h"

namespace lastfm {

class NdisEventsProxy;

class WNetworkConnectionMonitor : public NetworkConnectionMonitor
{
    Q_OBJECT
public:
    friend class lastfm::NdisEventsProxy;

    WNetworkConnectionMonitor( QObject* parent = 0 );
    ~WNetworkConnectionMonitor();

private:
    lastfm::NdisEventsProxy* m_ndisEventsProxy;
};

}

#endif // WNETWORK_CONNECTION_MONITOR_H

07070100000070000081A400000000000000000000000167B98EA8000007BF000000000000000000000000000000000000005900000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/win/WNetworkConnectionMonitor_win.cpp/*
   Copyright 2010 Last.fm Ltd.
      - Primarily authored by Jono Cole, Michael Coffey, and William Viana

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "WNetworkConnectionMonitor.h"
#include "moc_WNetworkConnectionMonitor.cpp"

// WsAccessManager needs special init (on Windows), and it needs to be done
// early, so be careful about moving this
#include "../win/ComSetup.h" //must be first header or compile fail results!
#include "../win/NdisEvents.h"
static ComSetup com_setup;

namespace lastfm {

// bounce NdisEvents signals through here so we don't have to expose the
// NdisEvents interface in InternetConnectionMonitor  :)
class NdisEventsProxy : public NdisEvents
{
public:
    NdisEventsProxy(WNetworkConnectionMonitor* icm)
        :m_icm(icm)
    {
    }

    // WmiSink callbacks:
    void onConnectionUp( BSTR /*name*/ )
    {
        m_icm->setConnected( true );
    }

    void onConnectionDown( BSTR /*name*/ )
    {
        m_icm->setConnected( false );
    }

    WNetworkConnectionMonitor* m_icm;
};


WNetworkConnectionMonitor::WNetworkConnectionMonitor( QObject* parent ) :
    NetworkConnectionMonitor( parent )
{
    m_ndisEventsProxy = new lastfm::NdisEventsProxy( this );
    m_ndisEventsProxy->registerForNdisEvents();
}

WNetworkConnectionMonitor::~WNetworkConnectionMonitor()
{
    delete m_ndisEventsProxy;
}

}
07070100000071000081A400000000000000000000000167B98EA800001B6B000000000000000000000000000000000000004300000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/win/WmiSink.cpp/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "WmiSink.h"
#include "NdisEvents.h"


WmiSink::WmiSink(NdisEvents *callback) 
    : m_cRef(1)
    , m_callback(callback)
{}

WmiSink::~WmiSink() 
{}

void
WmiSink::disconnect()
{
    m_callback = 0;
}

STDMETHODIMP
WmiSink::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = 0;

    if (IID_IUnknown==riid || IID_IWbemObjectSink == riid)
    {
        *ppv = (IWbemObjectSink *) this;
        AddRef();
        return NOERROR;
    }

    return E_NOINTERFACE;
}


ULONG
WmiSink::AddRef()
{
    return ++m_cRef;
}

ULONG
WmiSink::Release()
{
    if (0 != --m_cRef)
        return m_cRef;

    delete this;
    return 0;
}

// This method receives notification objects.
HRESULT 
WmiSink::Indicate(long lObjectCount, IWbemClassObject** ppObjArray)
{
    // For each object in the array, extract the object and display the
    // information in the object.
    for (long i=0; i<lObjectCount; i++)
    {
        CComVariant vt, vtClass;
        ppObjArray[i]->Get(L"InstanceName", 0, &vt, NULL, NULL);
        ppObjArray[i]->Get(L"__Class", 0, &vtClass, NULL, NULL);

        if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusMediaDisconnect"))
        {
            if (m_callback) m_callback->onConnectionDown(vt.bstrVal);
        }
        else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusMediaConnect"))
        {
            if (m_callback) m_callback->onConnectionUp(vt.bstrVal);
        }
        // notifications we aren't interested in right now:
        //
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyAdapterRemoval"))
        //{
        //    bstrLog = (_bstr_t) vt.bstrVal;
        //    VariantClear (&vt);
        //    ppObjArray[i]->Get (L"DeviceName", 0, &vt, NULL, NULL);
        //    bstrLog += (_bstr_t) _T(": ") + (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has been removed");
        //    displayDlg.LogEvent (bstrLog);
        //}
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyAdapterArrival"))
        //{
        //    bstrLog = (_bstr_t) vt.bstrVal;
        //    VariantClear (&vt);
        //    ppObjArray[i]->Get(L"DeviceName", 0, &vt, NULL, NULL);
        //    bstrLog += (_bstr_t) _T(": ") + (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has been added");
        //    displayDlg.LogEvent (bstrLog);
        //}
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusResetStart"))
        //{
        //    bstrLog = (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has begun a reset");
        //    displayDlg.LogEvent (bstrLog);
        //}
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusResetEnd"))
        //{
        //    bstrLog = (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has finished a reset");
        //    displayDlg.LogEvent (bstrLog);
        //}
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyVcArrival"))
        //{
        //    bstrLog = (_bstr_t) _T("VC arrival: ") + (_bstr_t) vt.bstrVal;
        //    displayDlg.LogEvent (bstrLog);
        //}
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyVcRemoval"))
        //{
        //    bstrLog = (_bstr_t) _T("VC removal: ") + (_bstr_t) vt.bstrVal;
        //    displayDlg.LogEvent (bstrLog);
        //}
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusMediaSpecificIndication"))
        //{
        //    ATLTRACE (_T("Media specific indication: %s\n"), (TCHAR *) (_bstr_t) vt.bstrVal);
        //    VariantClear (&vt);
        //    ppObjArray[i]->Get (L"NdisStatusMediaSpecificIndication", 0, &vt, NULL, NULL);
        //    LONG lLowerBound, lUpperBound, j;
        //    UCHAR ch;
        //    SafeArrayGetLBound (V_ARRAY (&vt), 1, &lLowerBound);
        //    SafeArrayGetUBound (V_ARRAY (&vt), 1, &lUpperBound);
        //    ATLTRACE (" ");
        //    for (j = lLowerBound; j<= lUpperBound; j++ )
        //    {
        //        SafeArrayGetElement (V_ARRAY (&vt), &j, &ch);
        //        ATLTRACE (_T("%4i"), ch);

        //        if (((j - lLowerBound) % 8 == 7) && (j <= lUpperBound))
        //            ATLTRACE (_T("\n"));         
        //    }                          
        //}
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusProtocolBind"))
        //{
        //    bstrLog = (_bstr_t) vt.bstrVal;
        //    VariantClear (&vt);
        //    ppObjArray[i]->Get (L"Transport", 0, &vt, NULL, NULL);
        //    bstrLog += (_bstr_t) _T(" is now bound to ") + (_bstr_t) vt.bstrVal;
        //    displayDlg.LogEvent (bstrLog);
        //}
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusProtocolBind"))
        //{
        //    bstrLog = (_bstr_t) vt.bstrVal;
        //    VariantClear (&vt);
        //    ppObjArray[i]->Get(L"Transport", 0, &vt, NULL, NULL);
        //    bstrLog += (_bstr_t) _T(" was unbound from ") + (_bstr_t) vt.bstrVal;
        //    displayDlg.LogEvent (bstrLog);
        //}
        //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusLinkSpeedChange"))
        //{
        //    IWbemClassObject* pWMIObj=NULL;
        //    bstrLog = (_bstr_t) _T("Link speed change ") + (_bstr_t) vt.bstrVal;
        //    VariantClear (&vt);
        //    ppObjArray[i]->Get (L"NdisStatusLinkSpeedChange", 0, &vt, NULL, NULL);
        //    if SUCCEEDED (vt.punkVal->QueryInterface (IID_IWbemClassObject, (void**)&pWMIObj))
        //    {
        //        TCHAR szNum[50];
        //        pWMIObj->Get (L"Inbound", 0, &vt2, NULL, NULL);
        //        _stprintf (szNum, _T(" Inbound = %u "), vt2.lVal);
        //        bstrLog += (_bstr_t) szNum;
        //        VariantClear (&vt2);
        //        pWMIObj->Get (L"Outbound", 0, &vt2, NULL, NULL);
        //        _stprintf (szNum, _T(" Outbound = %u "), vt2.lVal);
        //        bstrLog += (_bstr_t) szNum;
        //        VariantClear (&vt2);
        //        pWMIObj->Release ();
        //        pWMIObj = NULL;
        //    }
        //    displayDlg.LogEvent (bstrLog);
        //}

        VariantClear (&vtClass);
        VariantClear (&vt);
    }
    return WBEM_NO_ERROR;
}


// Misc. status codes sent by sink.
HRESULT 
WmiSink::SetStatus(long lFlags, HRESULT hResult, BSTR strParam, IWbemClassObject __RPC_FAR *pObjParam)
{
    lFlags;
    hResult;
    strParam;
    pObjParam;
    return WBEM_NO_ERROR;
}
07070100000072000081A400000000000000000000000167B98EA800000575000000000000000000000000000000000000004100000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/win/WmiSink.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WMISINK_WIN_H
#define WMISINK_WIN_H

#include "WbemCli.h"

// Sink object for WMI NDIS notifications
class WmiSink : public IWbemObjectSink
{
    UINT m_cRef;

public:
    WmiSink(class NdisEvents *callback);
    ~WmiSink();

    // IUnknown members
    STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IWbemObjectSink
    STDMETHODIMP Indicate(long, IWbemClassObject**);
    STDMETHODIMP SetStatus(long, HRESULT, BSTR, IWbemClassObject *);

    void disconnect();

private:
    class NdisEvents *m_callback;
};

#endif07070100000073000081A400000000000000000000000167B98EA800001D35000000000000000000000000000000000000003A00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/ws.cpp/*
   Copyright 2009 Last.fm Ltd.
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "ws.h"
#include "misc.h"
#include "NetworkAccessManager.h"
#include "Url.h"

#include <QCoreApplication>
#include <QDomDocument>
#include <QDomElement>
#include <QLocale>
#include <QStringList>
#include <QUrl>
#include <QThread>
#include <QMutex>
#include <QSslSocket>

static lastfm::ws::Scheme theScheme = lastfm::ws::Https;
static QString theHost = LASTFM_WS_HOSTNAME;
static QMap< QThread*, QNetworkAccessManager* > threadNamHash;
static QSet< QThread* > ourNamSet;
static QMutex namAccessMutex;

class lastfm::ws::ParseErrorPrivate
{
public:
    lastfm::ws::Error e;
    QString m_message;
};

lastfm::ws::ParseError::ParseError( lastfm::ws::Error e, QString message )
    :d( new ParseErrorPrivate )
{
     d->e = e;
     d->m_message = message;
}

lastfm::ws::ParseError::ParseError( const ParseError& that )
    : d( new ParseErrorPrivate( *that.d ) )
{
}

lastfm::ws::ParseError::~ParseError() throw()
{
    delete d;
}

lastfm::ws::Error
lastfm::ws::ParseError::enumValue() const
{
    return d->e;
}

QString
lastfm::ws::ParseError::message() const
{
    return d->m_message;
}

lastfm::ws::ParseError&
lastfm::ws::ParseError::operator=( const ParseError& that )
{
    d->e = that.d->e;
    d->m_message = that.d->m_message;
    return *this;
}

lastfm::ws::Scheme
lastfm::ws::scheme()
{
    return QSslSocket::supportsSsl() ? theScheme : Http;
}

void
lastfm::ws::setScheme( lastfm::ws::Scheme scheme )
{
    theScheme = scheme;
}

QString
lastfm::ws::host()
{
    return theHost;
}

void
lastfm::ws::setHost( QString host )
{
    theHost = host;
}

static QUrl baseUrl()
{
    QUrl url;
    url.setScheme( lastfm::ws::scheme() == lastfm::ws::Https ? "https" : "http" );
    url.setHost( lastfm::ws::host() );
    url.setPath( "/2.0/" );

    return url;
}

static QString iso639()
{
    return QLocale().name().left( 2 ).toLower();
}

void autograph( QMap<QString, QString>& params )
{
    params["api_key"] = lastfm::ws::ApiKey;
    params["lang"] = iso639();
}

void
lastfm::ws::sign( QMap<QString, QString>& params, bool sk )
{
    autograph( params );
    // it's allowed for sk to be null if we this is an auth call for instance
    if (sk && lastfm::ws::SessionKey.size())
        params["sk"] = lastfm::ws::SessionKey;

    QString s;
    QMapIterator<QString, QString> i( params );
    while (i.hasNext()) {
        i.next();
        s += i.key() + i.value();
    }
    s += lastfm::ws::SharedSecret;

    params["api_sig"] = lastfm::md5( s.toUtf8() );
}


QUrl
lastfm::ws::url( QMap<QString, QString> params, bool sk )
{
    lastfm::ws::sign( params, sk );
    lastfm::Url url = lastfm::Url( ::baseUrl() );
    // Qt setQueryItems doesn't encode a bunch of stuff, so we do it manually
    QMapIterator<QString, QString> i( params );
    while (i.hasNext()) {
        i.next();
        QString const key = i.key();
        QString const value = i.value();
        url.addQueryItem( key, value );
    }

    return url.url();
}


QNetworkReply*
lastfm::ws::get( QMap<QString, QString> params )
{
    return nam()->get( QNetworkRequest( url( params ) ) );
}


QNetworkReply*
lastfm::ws::post( QMap<QString, QString> params, bool sk )
{
    sign( params, sk );
    QByteArray query;
    QMapIterator<QString, QString> i( params );
    while (i.hasNext()) {
        i.next();
        query += QUrl::toPercentEncoding( i.key() )
               + '='
               + QUrl::toPercentEncoding( i.value() )
               + '&';
    }
    QNetworkRequest req( baseUrl() );
    req.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" );
    return nam()->post( req, query );
}


QNetworkAccessManager*
lastfm::nam()
{
    QMutexLocker l( &namAccessMutex );
    QThread* thread = QThread::currentThread();
    if ( !threadNamHash.contains( thread ) )
    {
        NetworkAccessManager* newNam = new NetworkAccessManager();
        threadNamHash[thread] = newNam;
        ourNamSet.insert( thread );
        return newNam;
    }
    return threadNamHash[thread];
}


void
lastfm::setNetworkAccessManager( QNetworkAccessManager* nam )
{
    if ( !nam )
        return;

    QMutexLocker l( &namAccessMutex );
    QThread* thread = QThread::currentThread();
    QNetworkAccessManager* oldNam = 0;
    if ( threadNamHash.contains( thread ) && ourNamSet.contains( thread ) )
        oldNam = threadNamHash[thread];

    if ( oldNam == nam )
    {
        // If we're being passed back our own NAM, assume they want to
        // ensure that we don't delete it out from under them
        ourNamSet.remove( thread );
        return;
    }

    threadNamHash[thread] = nam;
    ourNamSet.remove( thread );

    if ( oldNam )
        delete oldNam;
}


/** This useful function, fromHttpDate, comes from QNetworkHeadersPrivate
  * in qnetworkrequest.cpp.  Qt copyright and license apply. */
static QDateTime QByteArrayToHttpDate(const QByteArray &value)
{
    // HTTP dates have three possible formats:
    //  RFC 1123/822      -   ddd, dd MMM yyyy hh:mm:ss "GMT"
    //  RFC 850           -   dddd, dd-MMM-yy hh:mm:ss "GMT"
    //  ANSI C's asctime  -   ddd MMM d hh:mm:ss yyyy
    // We only handle them exactly. If they deviate, we bail out.

    int pos = value.indexOf(',');
    QDateTime dt;
    if (pos == -1) {
        // no comma -> asctime(3) format
        dt = QDateTime::fromString(QString::fromLatin1(value), Qt::TextDate);
    } else {
        // eat the weekday, the comma and the space following it
        QString sansWeekday = QString::fromLatin1(value.constData() + pos + 2);

        QLocale c = QLocale::c();
        if (pos == 3)
            // must be RFC 1123 date
            dt = c.toDateTime(sansWeekday, QLatin1String("dd MMM yyyy hh:mm:ss 'GMT"));
        else
            // must be RFC 850 date
            dt = c.toDateTime(sansWeekday, QLatin1String("dd-MMM-yy hh:mm:ss 'GMT'"));
    }

    if (dt.isValid())
        dt.setTimeSpec(Qt::UTC);
    return dt;
}


QDateTime
lastfm::ws::expires( QNetworkReply* reply )
{
    return QByteArrayToHttpDate( reply->rawHeader( "Expires" ) );
}


namespace lastfm
{
    namespace ws
    {
        QString SessionKey;
        QString Username;

        /** we leave these unset as you can't use the webservices without them
          * so lets make the programmer aware of it during testing by crashing */
        const char* SharedSecret;
        const char* ApiKey;

        /** if this is found set to "" we conjure ourselves a suitable one */
        const char* UserAgent = 0;
    }
}


QDebug operator<<( QDebug, lastfm::ws::Error );

QDebug operator<<( QDebug d, QNetworkReply::NetworkError e )
{
    return d << lastfm::qMetaEnumString<QNetworkReply>( e, "NetworkError" );
}
07070100000074000081A400000000000000000000000167B98EA80000165E000000000000000000000000000000000000003800000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/src/ws.h/*
   Copyright 2009 Last.fm Ltd. 
      - Primarily authored by Max Howell, Jono Cole and Doug Mansell

   This file is part of liblastfm.

   liblastfm is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   liblastfm is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFM_WS_H
#define LASTFM_WS_H

#include "global.h"
#include <QDateTime>
#include <QMap>
#include <QNetworkReply>
#include <stdexcept>

#ifdef Q_CC_MSVC
// ms admits its lousy compiler doesn't care about throw declarations
#pragma warning( disable : 4290 )
#endif


namespace lastfm
{
    /** if you don't set one, we create our own, our own is pretty good
      * for instance, it auto detects proxy settings on windows and mac
      * We take ownership of the NAM, do not delete it out from underneath us!
      * So don't keep any other pointers to this around in case you accidently
      * call delete on them :P */
    LASTFM_DLLEXPORT void setNetworkAccessManager( QNetworkAccessManager* nam );
    LASTFM_DLLEXPORT QNetworkAccessManager* nam();

    namespace ws
    {
        /** both of these are provided when you register at https://www.last.fm/api/ */
        LASTFM_DLLEXPORT extern const char* SharedSecret;
        LASTFM_DLLEXPORT extern const char* ApiKey;
    
        /** you need to set this for scrobbling to work (for now)
          * Also the User class uses it */
        LASTFM_DLLEXPORT extern QString Username;

        /** Some webservices require authentication. See the following
          * documentation:
          * https://www.last.fm/api/authentication
          * https://www.last.fm/api/desktopauth
          * You have to authenticate and then assign to SessionKey, liblastfm does
          * not do that for you. Also we do not store this. You should store this!
          * You only need to authenticate once, and that key lasts forever!
          */
        LASTFM_DLLEXPORT extern QString SessionKey;      
        
        enum Error
        {
            NoError = 1, // because last.fm error numbers start at 2

            /** numbers follow those at http://last.fm/api/ */
            InvalidService = 2,
            InvalidMethod,
            AuthenticationFailed,
            InvalidFormat,
            InvalidParameters,
            InvalidResourceSpecified,
            OperationFailed,
            InvalidSessionKey,
            InvalidApiKey,
            ServiceOffline,
            SubscribersOnly,

            Reserved13,
            TokenNotAuthorised,
            Reserved15,

            /** Last.fm sucks. 
              * There may be an error in networkError(), or this may just be some
              * internal error completing your request.
              * Advise the user to try again in a _few_minutes_.
              * For some cases, you may want to try again yourself, at this point
              * in the API you will have to. Eventually we will discourage this and
              * do it for you, as we don't want to strain Last.fm's servers
              */
            TryAgainLater = 16,

            Reserved17,
            Reserved18,
            Reserved19,
       
            NotEnoughContent = 20,
            NotEnoughMembers,
            NotEnoughFans,
            NotEnoughNeighbours,

            /** Last.fm fucked up, or something mangled the response on its way */
            MalformedResponse = 100,

            /** call QNetworkReply::error() as it's nothing to do with us */
            UnknownError
        };

        enum Scheme
        {
            Http,
            Https
        };

        /** Set the scheme for all web service calls. Note that it will only use
          * Https if SSL is supported on the user's machine otherwise it will
          * default to Http.
          */
        LASTFM_DLLEXPORT void setScheme( Scheme scheme );
        LASTFM_DLLEXPORT Scheme scheme();
        
        LASTFM_DLLEXPORT void setHost( QString host );
        LASTFM_DLLEXPORT QString host();

        /** the map needs a method entry, as per https://www.last.fm/api/ */
        LASTFM_DLLEXPORT QUrl url( QMap<QString, QString>, bool sessionKey = true);
        LASTFM_DLLEXPORT QNetworkReply* get( QMap<QString, QString> );
        /** generates api sig, includes api key, and posts, don't add the api
          * key yourself as well--it'll break */
        LASTFM_DLLEXPORT QNetworkReply* post( QMap<QString, QString>, bool sessionKey = true );

        LASTFM_DLLEXPORT void sign( QMap<QString, QString>&, bool sessionKey = true );


        class LASTFM_DLLEXPORT ParseError
        {
        public:
            explicit ParseError( Error e, QString message );
            ParseError( const ParseError& that );
            ~ParseError() throw();
            Error enumValue() const;
            QString message() const;
            ParseError& operator=( const ParseError& that );

        private:
            class ParseErrorPrivate * const d;
        };
        
        /** returns the expiry date of this HTTP response */
        LASTFM_DLLEXPORT QDateTime expires( QNetworkReply* );
    }
}


LASTFM_DLLEXPORT QDebug operator<<( QDebug d, QNetworkReply::NetworkError e );

#define LASTFM_WS_HOSTNAME "ws.audioscrobbler.com"

#endif
07070100000075000041ED00000000000000000000000267B98EA800000000000000000000000000000000000000000000003500000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/tests07070100000076000081A400000000000000000000000167B98EA80000008B000000000000000000000000000000000000004400000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/tests/CMakeLists.txtinclude(lastfm_add_test.cmake)
include_directories(${CMAKE_CURRENT_BINARY_DIR}/../src)

lastfm_add_test(UrlBuilder)
lastfm_add_test(Track)
07070100000077000081A400000000000000000000000167B98EA800000348000000000000000000000000000000000000004100000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/tests/TestTrack.h/*
   This software is in the public domain, furnished "as is", without technical 
   support, and with no warranty, express or implied, as to its usefulness for 
   any purpose.
*/

#ifndef LASTFM_TESTTRACK_H
#define LASTFM_TESTTRACK_H

#include "Track.h"

#include <QtTest>

using lastfm::Track;

class TestTrack : public QObject
{
    Q_OBJECT
    
    Track example()
    {
        lastfm::MutableTrack t;
        t.setTitle( "Test Title" );
        t.setArtist( "Test Artist" );
        t.setAlbum( "Test Album" );
        return t;
    }
    
private Q_SLOTS:
    void testClone()
    {
        Track original = example();
        Track copy = original;
        
        #define TEST( x ) QVERIFY( original.x == copy.x )
        TEST( title() );
        TEST( artist() );
        TEST( album() );
        #undef TEST
    }
};

#endif
07070100000078000081A400000000000000000000000167B98EA800000A64000000000000000000000000000000000000004600000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/tests/TestUrlBuilder.h/*
   This software is in the public domain, furnished "as is", without technical
   support, and with no warranty, express or implied, as to its usefulness for
   any purpose.
*/

#ifndef LASTFM_TESTURLBUILDER_H
#define LASTFM_TESTURLBUILDER_H

#include <UrlBuilder.h>
#include <global.h>

#include <QtTest>
#include <QtNetwork>
#include <QEventLoop>


static inline int getResponseCode( const QUrl& url )
{
    QNetworkAccessManager nam;
    QNetworkReply* reply = nam.head( QNetworkRequest(url) );

    QEventLoop loop;
    loop.connect( reply, SIGNAL(finished()), SLOT(quit()) );
    loop.exec();

    int const code = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();

    if (reply->error() != QNetworkReply::NoError)
        qDebug() << url << lastfm::qMetaEnumString<QNetworkReply>( reply->error(), "NetworkError" ) << code;

    return code;
}


class TestUrlBuilder : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void encode() /** @author <jono@last.fm> */
    {
        QFETCH( QString, input );
        QFETCH( QString, output );
        QCOMPARE( lastfm::UrlBuilder::encode( input ), output.toLatin1() );
    }

    void encode_data() /** @author <jono@last.fm> */
    {
        QTest::addColumn<QString>("input");
        QTest::addColumn<QString>("output");

        QTest::newRow( "ascii" ) << "Metallica" << "Metallica";
        QTest::newRow( "ascii alphanumeric" ) << "Apollo 440" << "Apollo+440";
        QTest::newRow( "ascii with symbols" ) << "Would?" << "Would%3F";
        QTest::newRow( "ascii with last.fm-special symbols" ) << "Survivalism [Revision #1]" << "Survivalism+%5BRevision+%231%5D";
    }

    void no404() /** @author <max@last.fm> */
    {
        QFETCH( QString, artist );
        QFETCH( QString, track );

        QUrl url = lastfm::UrlBuilder( "music" ).slash( artist ).slash( "_" ).slash( track ).url();

        QCOMPARE( getResponseCode( url ), 200 );
    }

    void no404_data() /** @author <max@last.fm> */
    {
        QTest::addColumn<QString>("artist");
        QTest::addColumn<QString>("track");

        #define NEW_ROW( x, y ) QTest::newRow( x " - " y ) << x << y;
        NEW_ROW( "Air", "Radio #1" );
        NEW_ROW( "Pink Floyd", "Speak to Me / Breathe" );
        NEW_ROW( "Radiohead", "2 + 2 = 5" );
        NEW_ROW( "Above & Beyond", "World On Fire (Maor Levi Remix)" );
        NEW_ROW( "Kreş", "Portret Nieświadomości" );
        NEW_ROW( "자우림", "스물다섯, 스물하나" );
        #undef NEW_ROW
    }

    void test404() /** @author <max@last.fm> */
    {
        QCOMPARE( getResponseCode( QUrl("https://www.last.fm/404") ), 404 );
    }
};

#endif
07070100000079000081A400000000000000000000000167B98EA800000249000000000000000000000000000000000000004B00000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/tests/lastfm_add_test.cmakemacro(lastfm_add_test test_class)
    include_directories("${PROJECT_SOURCE_DIR}/src" ${CMAKE_CURRENT_BINARY_DIR})

    set(LASTFM_TEST_CLASS ${test_class})
    configure_file(main.cpp.in Test${LASTFM_TEST_CLASS}.cpp)
    configure_file(Test${LASTFM_TEST_CLASS}.h Test${LASTFM_TEST_CLASS}.h)
    add_executable(${LASTFM_TEST_CLASS}Test Test${LASTFM_TEST_CLASS}.cpp)

    target_link_libraries(${LASTFM_TEST_CLASS}Test Qt::Core Qt::Test Qt::Xml Qt::Network
        ${LASTFM_LIB_TARGET_NAME}
    )

    add_test(NAME ${LASTFM_TEST_CLASS}Test COMMAND ${LASTFM_TEST_CLASS}Test)
endmacro()
0707010000007A000081A400000000000000000000000167B98EA800000220000000000000000000000000000000000000004100000000liblastfm-qt6-1.2.0git.20250222T104528~81e8f9d/tests/main.cpp.in/*
   This software is in the public domain, furnished "as is", without technical
   support, and with no warranty, express or implied, as to its usefulness for
   any purpose.
*/
#include <QtCore>
#include <QtTest>

#include "Test@LASTFM_TEST_CLASS@.h"
#include "moc_Test@LASTFM_TEST_CLASS@.cpp"

int main( int argc, char** argv)
{
    QCoreApplication app( argc, argv );

    #define TEST( Type ) { \
        Type o; \
        if (int r = QTest::qExec( &o, argc, argv ) != 0) return r; }

    TEST( Test@LASTFM_TEST_CLASS@ );
    return 0;
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1054 blocks
openSUSE Build Service is sponsored by