Showing preview only (2,085K chars total). Download the full file or copy to clipboard to get everything.
Repository: TheQwertiest/foo_spotify
Branch: master
Commit: ef3c0dd9fff1
Files: 226
Total size: 1.9 MB
Directory structure:
gitextract_nrm_lg86/
├── .appveyor.yml
├── .bandit
├── .codacy.yml
├── .gitattributes
├── .gitignore
├── .gitmodules
├── .license_index.txt
├── CHANGELOG.md
├── LICENSE
├── README.md
├── THIRD_PARTY_NOTICES.md
├── VERSION
├── foo_spotify/
│ ├── .clang-format
│ ├── .clang-tidy
│ ├── backend/
│ │ ├── audio_buffer.cpp
│ │ ├── audio_buffer.h
│ │ ├── libspotify_backend.cpp
│ │ ├── libspotify_backend.h
│ │ ├── libspotify_backend_user.h
│ │ ├── libspotify_key.h
│ │ ├── libspotify_wrapper.h
│ │ ├── spotify_instance.cpp
│ │ ├── spotify_instance.h
│ │ ├── spotify_object.cpp
│ │ ├── spotify_object.h
│ │ ├── webapi_auth.cpp
│ │ ├── webapi_auth.h
│ │ ├── webapi_auth_scopes.cpp
│ │ ├── webapi_auth_scopes.h
│ │ ├── webapi_backend.cpp
│ │ ├── webapi_backend.h
│ │ ├── webapi_cache.cpp
│ │ ├── webapi_cache.h
│ │ └── webapi_objects/
│ │ ├── webapi_album.cpp
│ │ ├── webapi_album.h
│ │ ├── webapi_artist.cpp
│ │ ├── webapi_artist.h
│ │ ├── webapi_image.cpp
│ │ ├── webapi_image.h
│ │ ├── webapi_media_objects.h
│ │ ├── webapi_paging_object.cpp
│ │ ├── webapi_paging_object.h
│ │ ├── webapi_playlist_track.cpp
│ │ ├── webapi_playlist_track.h
│ │ ├── webapi_restriction.cpp
│ │ ├── webapi_restriction.h
│ │ ├── webapi_track.cpp
│ │ ├── webapi_track.h
│ │ ├── webapi_track_link.cpp
│ │ ├── webapi_track_link.h
│ │ ├── webapi_user.cpp
│ │ └── webapi_user.h
│ ├── component_defines.h
│ ├── component_guids.h
│ ├── component_paths.cpp
│ ├── component_paths.h
│ ├── component_urls.h
│ ├── dllmain.cpp
│ ├── fb2k/
│ │ ├── acfu_integration.cpp
│ │ ├── advanced_config.cpp
│ │ ├── advanced_config.h
│ │ ├── album_art.cpp
│ │ ├── config.cpp
│ │ ├── config.h
│ │ ├── file_info_filler.cpp
│ │ ├── file_info_filler.h
│ │ ├── filesystem.cpp
│ │ ├── input.cpp
│ │ ├── playback.cpp
│ │ ├── playback.h
│ │ ├── playlist.cpp
│ │ └── playlist_loader.cpp
│ ├── foo_spotify.rc
│ ├── foo_spotify.rc2
│ ├── foo_spotify.vcxproj
│ ├── foo_spotify.vcxproj.filters
│ ├── packages.config
│ ├── resource.h
│ ├── stdafx.cpp
│ ├── stdafx.h
│ ├── ui/
│ │ ├── ui_ipref_tab.h
│ │ ├── ui_not_auth.cpp
│ │ ├── ui_not_auth.h
│ │ ├── ui_pref_tab_auth.cpp
│ │ ├── ui_pref_tab_auth.h
│ │ ├── ui_pref_tab_manager.cpp
│ │ ├── ui_pref_tab_manager.h
│ │ ├── ui_pref_tab_playback.cpp
│ │ └── ui_pref_tab_playback.h
│ └── utils/
│ ├── abort_manager.cpp
│ ├── abort_manager.h
│ ├── async_mutex.hpp
│ ├── cred_prompt.cpp
│ ├── cred_prompt.h
│ ├── json_macro_fix.h
│ ├── json_std_extenders.h
│ ├── rps_limiter.cpp
│ ├── rps_limiter.h
│ ├── secure_vector.h
│ ├── sleeper.cpp
│ └── sleeper.h
├── libspotify/
│ ├── ChangeLog
│ ├── LICENSE
│ ├── README
│ ├── docs/
│ │ ├── html/
│ │ │ ├── annotated.html
│ │ │ ├── api_8h.html
│ │ │ ├── browse_8c-example.html
│ │ │ ├── classes.html
│ │ │ ├── doxygen.css
│ │ │ ├── examples.html
│ │ │ ├── files.html
│ │ │ ├── functions.html
│ │ │ ├── functions_vars.html
│ │ │ ├── globals.html
│ │ │ ├── globals_0x69.html
│ │ │ ├── globals_0x73.html
│ │ │ ├── globals_0x74.html
│ │ │ ├── globals_defs.html
│ │ │ ├── globals_enum.html
│ │ │ ├── globals_eval.html
│ │ │ ├── globals_func.html
│ │ │ ├── globals_type.html
│ │ │ ├── group__album.html
│ │ │ ├── group__albumbrowse.html
│ │ │ ├── group__artist.html
│ │ │ ├── group__artistbrowse.html
│ │ │ ├── group__error.html
│ │ │ ├── group__image.html
│ │ │ ├── group__inbox.html
│ │ │ ├── group__link.html
│ │ │ ├── group__playlist.html
│ │ │ ├── group__search.html
│ │ │ ├── group__session.html
│ │ │ ├── group__toplist.html
│ │ │ ├── group__track.html
│ │ │ ├── group__types.html
│ │ │ ├── group__user.html
│ │ │ ├── index.html
│ │ │ ├── jukebox_8c-example.html
│ │ │ ├── modules.html
│ │ │ ├── search_8c-example.html
│ │ │ ├── structsp__audio__buffer__stats.html
│ │ │ ├── structsp__audioformat.html
│ │ │ ├── structsp__offline__sync__status.html
│ │ │ ├── structsp__playlist__callbacks.html
│ │ │ ├── structsp__playlistcontainer__callbacks.html
│ │ │ ├── structsp__session__callbacks.html
│ │ │ ├── structsp__session__config.html
│ │ │ ├── structsp__subscribers.html
│ │ │ ├── tabs.css
│ │ │ └── toplist_8c-example.html
│ │ └── images/
│ │ └── spotify-core.txt
│ ├── examples/
│ │ ├── Makefile
│ │ ├── Randomify/
│ │ │ ├── English.lproj/
│ │ │ │ ├── InfoPlist.strings
│ │ │ │ └── MainMenu.xib
│ │ │ ├── Randomify-Info.plist
│ │ │ ├── Randomify.xcodeproj/
│ │ │ │ └── project.pbxproj
│ │ │ ├── RandomifyAppDelegate.h
│ │ │ ├── RandomifyAppDelegate.m
│ │ │ ├── SpotifyPlaylist.h
│ │ │ ├── SpotifyPlaylist.m
│ │ │ ├── SpotifySession.h
│ │ │ ├── SpotifySession.m
│ │ │ ├── SpotifyTrack.h
│ │ │ ├── SpotifyTrack.m
│ │ │ └── main.m
│ │ ├── appkey.c
│ │ ├── common.mk
│ │ ├── jukebox/
│ │ │ ├── Makefile
│ │ │ ├── alsa-audio.c
│ │ │ ├── audio.c
│ │ │ ├── audio.h
│ │ │ ├── dummy-audio.c
│ │ │ ├── jukebox.c
│ │ │ ├── openal-audio.c
│ │ │ ├── osx/
│ │ │ │ └── jukebox.xcodeproj/
│ │ │ │ └── project.pbxproj
│ │ │ ├── osx-audio.c
│ │ │ ├── playtrack.c
│ │ │ └── queue.h
│ │ ├── localfiles/
│ │ │ ├── Makefile
│ │ │ ├── main.c
│ │ │ └── main.h
│ │ ├── spshell/
│ │ │ ├── Makefile
│ │ │ ├── browse.c
│ │ │ ├── cmd.c
│ │ │ ├── cmd.h
│ │ │ ├── inbox.c
│ │ │ ├── osx/
│ │ │ │ └── spshell.xcodeproj/
│ │ │ │ └── project.pbxproj
│ │ │ ├── playlist.c
│ │ │ ├── scrobbling.c
│ │ │ ├── search.c
│ │ │ ├── spshell.c
│ │ │ ├── spshell.h
│ │ │ ├── spshell_posix.c
│ │ │ ├── spshell_win32.c
│ │ │ ├── star.c
│ │ │ ├── test.c
│ │ │ ├── toplist.c
│ │ │ └── win32/
│ │ │ ├── spshell.vcproj
│ │ │ ├── spshell.vcxproj
│ │ │ └── spshell.vcxproj.filters
│ │ └── stub/
│ │ ├── Makefile
│ │ ├── main.c
│ │ └── main.h
│ ├── include/
│ │ └── libspotify/
│ │ └── api.h
│ ├── lib/
│ │ └── libspotify.lib
│ └── licenses.xhtml
├── licenses/
│ ├── JSON for Modern C++.txt
│ ├── LibSpotify.txt
│ ├── PFC.txt
│ ├── WTL.txt
│ ├── fmt.txt
│ ├── foobar2000 SDK.txt
│ ├── range-v3.txt
│ ├── ring-span-lite.txt
│ └── span-lite.txt
├── props/
│ ├── local_dependencies/
│ │ └── libspotify.props
│ └── submodules/
│ ├── fb2k_utils.props
│ └── submodules.props
├── scripts/
│ ├── README.md
│ ├── call_wrapper.py
│ ├── download_submodules.py
│ ├── pack_component.py
│ ├── setup.py
│ └── update_gh_pages.py
└── workspaces/
└── foo_spotify.sln
================================================
FILE CONTENTS
================================================
================================================
FILE: .appveyor.yml
================================================
#---------------------------------#
# general configuration #
#---------------------------------#
# version format
version: 1.0.{build}-{branch}
# branches to build
branches:
# blacklist
except:
- gh-pages
# Skipping commits affecting specific files (GitHub only). More details here: /docs/appveyor-yml
skip_commits:
# Default `skip` messages are applied even on tag builds with APPVEYOR_IGNORE_COMMIT_FILTERING_ON_TAG==true
message: /\[_skip ci_\]|\[_ci skip_\]|\[_skip_ci_\]|\[_ci_skip_\]/
files:
- README.md
- THIRD_PARTY_NOTICES.md
# - docs/*
# - '**/*.png'
# - '**/*.jpg'
# - '**/*.jpeg'
# - '**/*.bmp'
# - '**/*.gif'
# - '**/*.js'
# - '**/*.txt'
# - '**/*.md'
# Maximum number of concurrent jobs for the project
max_jobs: 1
#---------------------------------#
# environment configuration #
#---------------------------------#
# Build worker image (VM template)
image: Visual Studio 2019
# environment variables
environment:
APPVEYOR_IGNORE_COMMIT_FILTERING_ON_TAG: true
git_user_email: qwertiest@mail.ru
git_user_name: TheQwertiest
GITHUB_TOKEN:
secure: gBfHQnRcGcTS0yQXWOHhKfDkTZfIBwPIDjTsCQOGmOSPkRqs1QPYBX47BjCvJKim
# this is how to allow failing jobs in the matrix
matrix:
fast_finish: true # set this flag to immediately finish build once one of the jobs fails.
cache:
- workspaces\packages -> **\packages.config
- c:\tools\vcpkg\installed
# scripts that run after cloning repository
install:
# vcpkg
- cd C:\Tools\vcpkg
- git pull
- .\bootstrap-vcpkg.bat
- cd %APPVEYOR_BUILD_FOLDER%
- vcpkg remove --outdated --recurse
- cmd: set VCPKG_PLATFORM_TOOLSET=v142
- vcpkg install cpprestsdk:x86-windows-static
- vcpkg integrate install
# GitHub CLI
- choco install gh -y
- refreshenv
- gh config set -h github.com git_protocol https
# PIP
- py -3 -m pip install semver
# Component
- py -u scripts\setup.py
#---------------------------------#
# build configuration #
#---------------------------------#
# build platform, i.e. x86, x64, Any CPU. This setting is optional.
platform:
- Win32
# build Configuration, i.e. Debug, Release, etc.
configuration:
- Debug
- Release
# Build settings, not to be confused with "before_build" and "after_build".
# "project" is relative to the original build directory and not influenced by directory changes in "before_build".
build:
parallel: true # enable MSBuild parallel builds
project: workspaces\foo_spotify.sln # path to Visual Studio solution or project
# MSBuild verbosity level
verbosity: normal
# scripts to run before build
before_build:
- nuget restore workspaces\foo_spotify.sln
# to run your custom scripts instead of automatic MSBuild
build_script:
# scripts to run after build (working directory and environment changes are persisted from the previous steps)
after_build:
# scripts to run *after* solution is built and *before* automatic packaging occurs (web apps, NuGet packages, Azure Cloud Services)
before_package:
- if '%configuration%' == 'Debug' (
py -u scripts\pack_component.py --debug &&
ren "_result\%platform%_%configuration%\foo_spotify.fb2k-component" "foo_spotify.fb2k-component_debug"
)
else (
py -u scripts\pack_component.py
)
# to disable automatic builds
#build: off
#---------------------------------#
# artifacts configuration #
#---------------------------------#
artifacts:
# pushing a single file with environment variable in path and "Deployment name" specified
- path: _result\$(platform)_$(configuration)\foo_spotify.fb2k-component
name: SPTF package (Release)
- path: _result\$(platform)_$(configuration)\foo_spotify_pdb.zip
name: SPTF PDB package (Release)
- path: _result\$(platform)_$(configuration)\foo_spotify.fb2k-component_debug
name: SPTF package (Debug)
#---------------------------------#
# deployment configuration #
#---------------------------------#
# providers: Local, FTP, WebDeploy, AzureCS, AzureBlob, S3, NuGet, Environment
# provider names are case-sensitive!
deploy:
description: 'Dummy description'
provider: GitHub
auth_token: $(GITHUB_TOKEN)
artifact: /foo_spotify.*/ # upload all NuGet packages to release assets
prerelease: true
on:
branch: master # release from master branch only
appveyor_repo_tag: true # deploy on tag push only
# scripts to run before deployment
before_deploy:
# scripts to run after deployment
after_deploy:
# to run your custom scripts instead of provider deployments
deploy_script:
# to disable deployment
# deploy: off
#---------------------------------#
# global handlers #
#---------------------------------#
# on successful build
on_success:
# issues clean up
- ps: |
If ($env:APPVEYOR_REPO_TAG -eq $true -and $env:CONFIGURATION -eq "Release")
{
py -u submodules\fb2k_utils\scripts\close_gh_issues.py
}
# gh-pages
- ps: |
If ($env:APPVEYOR_REPO_TAG -eq $true -and $env:CONFIGURATION -eq "Release")
{
git config --global credential.helper store
Add-Content "$env:USERPROFILE\.git-credentials" "https://$($env:GITHUB_TOKEN):x-oauth-basic@github.com`n"
git config --global user.email $env:git_user_email
git config --global user.name $env:git_user_name
git clone -q --depth=1 -b gh-pages https://github.com/$($env:APPVEYOR_REPO_NAME).git gh-pages
py -u scripts\update_gh_pages.py
cd gh-pages
git add -A 2>&1
git commit -q -m "Updated documentation: $env:APPVEYOR_REPO_TAG_NAME ($env:APPVEYOR_REPO_COMMIT)"
git push -q origin gh-pages
cd $env:APPVEYOR_BUILD_FOLDER
}
# on build failure
on_failure:
# after build failure or success
on_finish:
#---------------------------------#
# notifications #
#---------------------------------#
notifications:
================================================
FILE: .bandit
================================================
skips: ['B101','B404','B602','B607']
================================================
FILE: .codacy.yml
================================================
engines:
prospector:
enabled: true
python_version: 3
pylint:
enabled: true
python_version: 3
exclude_paths:
- 'libspotify/**'
- 'submodules/**'
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.zip filter=lfs diff=lfs merge=lfs -text
libspotify/* linguist-detectable=false
scripts/* linguist-detectable=false
================================================
FILE: .gitignore
================================================
# Created by https://www.gitignore.io/api/visualstudio
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
/_*
*.fb2k-component
[Dd]ebug/
[Dd]ebugPublic/
[Dd]ebug FB2K/
[Rr]elease/
[Rr]eleases/
[Rr]elease FB2K/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
### VisualStudio Patch ###
build/
================================================
FILE: .gitmodules
================================================
[submodule "submodules/acfu-sdk"]
path = submodules/acfu-sdk
url = https://github.com/3dyd/acfu-sdk/
[submodule "submodules/fmt"]
path = submodules/fmt
url = https://github.com/fmtlib/fmt
branch = release
[submodule "submodules/foobar2000"]
path = submodules/foobar2000
url = https://github.com/TheQwertiest/foobar2000-sdk
[submodule "submodules/pfc"]
path = submodules/pfc
url = https://github.com/TheQwertiest/pfc
[submodule "submodules/range"]
path = submodules/range
url = https://github.com/ericniebler/range-v3
[submodule "submodules/span"]
path = submodules/span
url = https://github.com/martinmoene/span-lite
[submodule "submodules/json"]
path = submodules/json
url = https://github.com/nlohmann/json.git
branch = master
[submodule "submodules/wtl"]
path = submodules/wtl
url = https://git.code.sf.net/p/wtl/git
[submodule "submodules/fb2k_utils"]
path = submodules/fb2k_utils
url = https://github.com/TheQwertiest/fb2k_utils.git
[submodule "submodules/ring-span"]
path = submodules/ring-span
url = https://github.com/martinmoene/ring-span-lite.git
================================================
FILE: .license_index.txt
================================================
foobar2000 SDK: other
fmt: other
JSON for Modern C++: MIT
LibSpotify: other
PFC: zlib
range-v3: BSL-1.0
ring-span-lite: BSL-1.0
span-lite: BSL-1.0
WTL: MS-PL
================================================
FILE: CHANGELOG.md
================================================
# Changelog
#### Table of Contents
- [Unreleased](#unreleased)
- [1.1.3](#113---2021-02-18)
- [1.1.2](#112---2020-11-03)
- [1.1.1](#111---2020-10-27)
- [1.1.0](#110---2020-10-26)
- [1.0.2](#102---2020-10-04)
- [1.0.1](#101---2020-10-03)
- [1.0.0](#100---2020-10-02)
___
## [Unreleased][]
## [1.1.3][] - 2021-02-18
### Fixed
- Fixed broken import-by-URL handling ([#47](https://github.com/TheQwertiest/foo_spotify/issues/47)).
- Fixed some issues with keyboard navigation in component menu in Preferences.
## [1.1.2][] - 2020-11-03
### Changed
- Better Web API handling in attempt to avoid 429 errors:
- Pre-emptive batch artist update on album import ([#36](https://github.com/TheQwertiest/foo_spotify/issues/36)).
### Fixed
- Unreasonably high CPU usage in some scenarios ([#35](https://github.com/TheQwertiest/foo_spotify/issues/35)).
- Rps limiter is not working ([#34](https://github.com/TheQwertiest/foo_spotify/issues/34)).
- Extra silence at the end of track ([#33](https://github.com/TheQwertiest/foo_spotify/issues/33)).
## [1.1.1][] - 2020-10-27
### Changed
- Better Web API handling in attempt to avoid 429 errors:
- Pre-emptive batch artist update on playlist import ([#32](https://github.com/TheQwertiest/foo_spotify/issues/32)).
## [1.1.0][] - 2020-10-26
### Added
- *Play* the artist: adds top tracks for the artist ([#15](https://github.com/TheQwertiest/foo_spotify/issues/15)).
- `%codec%` metadata field ([#14](https://github.com/TheQwertiest/foo_spotify/issues/14)).
- More options in `Preferences`:
- Cache size configuration ([#23](https://github.com/TheQwertiest/foo_spotify/issues/23)).
- Spotify `private mode` option ([#13](https://github.com/TheQwertiest/foo_spotify/issues/13)).
- Spotify normalization option ([#12](https://github.com/TheQwertiest/foo_spotify/issues/12)).
### Changed
- Better Web API handling in attempt to avoid 429 errors:
- RPS limit ([#27](https://github.com/TheQwertiest/foo_spotify/issues/27)).
- `retry-after` response header handling ([#11](https://github.com/TheQwertiest/foo_spotify/issues/11)).
- Pre-emptive batch track update on playlist change ([#30](https://github.com/TheQwertiest/foo_spotify/issues/30)).
- Pre-emptive batch artist update on playlist change ([#31](https://github.com/TheQwertiest/foo_spotify/issues/31)).
- Improved error message when track is not available in user's country ([#8](https://github.com/TheQwertiest/foo_spotify/issues/8)).
### Fixed
- Can't relogin without closing Preferences dialog ([#24](https://github.com/TheQwertiest/foo_spotify/issues/24)).
- Dynamic info change is signaled too frequently, despite data being the same ([#22](https://github.com/TheQwertiest/foo_spotify/issues/22)).
- Can't play tracks when buffering is enabled in fb2k ([#21](https://github.com/TheQwertiest/foo_spotify/issues/21)).
- Crash: Illegal operation ([#19](https://github.com/TheQwertiest/foo_spotify/issues/19)).
- Multi-value tags are not displayed correctly ([#17](https://github.com/TheQwertiest/foo_spotify/issues/17)).
- Can't import playlists with local tracks ([#16](https://github.com/TheQwertiest/foo_spotify/issues/16), [#18](https://github.com/TheQwertiest/foo_spotify/issues/18)).
## [1.0.2][] - 2020-10-04
### Added
- Proxy support ([#6](https://github.com/TheQwertiest/foo_spotify/issues/6)).
- Adjust bit-rate via `Preferences` ([#5](https://github.com/TheQwertiest/foo_spotify/issues/5)).
- Bit-rate metadata for playing track ([#4](https://github.com/TheQwertiest/foo_spotify/issues/4)).
### Fixed
- Fails to open track that has null `preview_url` ([#3](https://github.com/TheQwertiest/foo_spotify/issues/3)).
## [1.0.1][] - 2020-10-03
### Fixed
- LibSpotify login doesn't always work ([#1](https://github.com/TheQwertiest/foo_spotify/issues/1)).
## [1.0.0][] - 2020-10-02
Initial release.
[unreleased]: https://github.com/TheQwertiest/foo_spotify/compare/v1.1.3...HEAD
[1.1.3]: https://github.com/TheQwertiest/foo_spotify/compare/v1.1.2...v1.1.3
[1.1.2]: https://github.com/TheQwertiest/foo_spotify/compare/v1.1.1...v1.1.2
[1.1.1]: https://github.com/TheQwertiest/foo_spotify/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/TheQwertiest/foo_spotify/compare/v1.0.2...v1.1.0
[1.0.2]: https://github.com/TheQwertiest/foo_spotify/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/TheQwertiest/foo_spotify/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/TheQwertiest/foo_spotify/commits/master
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018-2020 Yuri Shutenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Spotify Integration
[![version][version_badge]][changelog] [![Build status][appveyor_badge]](https://ci.appveyor.com/project/TheQwertiest/foo-spotify/branch/master) [![CodeFactor][codefactor_badge]](https://www.codefactor.io/repository/github/theqwertiest/foo_spotify/overview/master) [![Codacy Badge][codacy_badge]](https://www.codacy.com/gh/TheQwertiest/foo_spotify/dashboard?utm_source=github.com&utm_medium=referral&utm_content=TheQwertiest/foo_spotify&utm_campaign=Badge_Grade)
**!!! This component is no longer being developed. See [#68](https://github.com/TheQwertiest/foo_spotify/issues/68) for more info !!!**
This is a component for the [foobar2000](https://www.foobar2000.org) audio player, which allows to play tracks from Spotify.
Visit [component homepage](https://theqwertiest.github.io/foo_spotify/) for more info.
[changelog]: CHANGELOG.md
[version_badge]: https://img.shields.io/github/release/theqwertiest/foo_spotify.svg
[appveyor_badge]: https://ci.appveyor.com/api/projects/status/t5bhoxmfgavhq81m/branch/master?svg=true
[codacy_badge]: https://app.codacy.com/project/badge/Grade/c2d3b3a99ce640ad8c1512784e15910f
[codefactor_badge]: https://www.codefactor.io/repository/github/theqwertiest/foo_spotify/badge/master
================================================
FILE: THIRD_PARTY_NOTICES.md
================================================
Spotify Integration uses third-party libraries or other resources that may
be distributed under licenses different than the Spotify Integration software.
The linked notices are provided for information only.
- [foobar2000 SDK - other](licenses/foobar2000%20SDK.txt)
- [fmt - other](licenses/fmt.txt)
- [JSON for Modern C++ - MIT](licenses/JSON%20for%20Modern%20C%2B%2B.txt)
- [LibSpotify - other](licenses/LibSpotify.txt)
- [PFC - zlib](licenses/PFC.txt)
- [range-v3 - BSL-1.0](licenses/range-v3.txt)
- [ring-span-lite - BSL-1.0](licenses/ring-span-lite.txt)
- [span-lite - BSL-1.0](licenses/span-lite.txt)
- [WTL - MS-PL](licenses/WTL.txt)
================================================
FILE: VERSION
================================================
1.1.4-beta
================================================
FILE: foo_spotify/.clang-format
================================================
---
BasedOnStyle: Mozilla
AccessModifierOffset: '-4'
AlwaysBreakAfterDefinitionReturnType: 'false'
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: 'false'
AlignConsecutiveMacros: 'true'
AlignEscapedNewlines: Left
AlignOperands: 'true'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: 'false'
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: 'true'
BinPackParameters: 'true'
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Allman
BreakBeforeInheritanceComma: 'true'
BreakConstructorInitializers: BeforeComma
ColumnLimit: '0'
ConstructorInitializerIndentWidth: '4'
ContinuationIndentWidth: '4'
Cpp11BracedListStyle: 'false'
FixNamespaceComments: 'true'
IncludeBlocks: Regroup
IncludeCategories:
# precompiled headers
- Regex: '^<stdafx.h|pch.h|precompiled.h>'
Priority: -1
# "header"
- Regex: '"[[:alnum:]_]+'
Priority: 1
# <dependency/header>
- Regex: '^<(acfu-sdk|cpprest|columns_ui-sdk|fmt|foobar2000|js|libspotify|nlohmann|nonstd|range|tim|qwr)/'
Priority: 4
# <dependency_header>
- Regex: '^<(atl.*\.h)>'
Priority: 5
# <subdir/header.h>|<subdir/header.hpp>
- Regex: '<[[:alnum:]_]+/.+\.(h|hpp)>'
Priority: 2
# <header.h>|<header.hpp>
- Regex: '<[[:alnum:]_]+\.(h|hpp)>'
Priority: 3
# <header>
- Regex: '<[[:alnum:]_]+>'
Priority: 6
IncludeIsMainRegex: '$'
IndentCaseLabels: false
IndentPPDirectives: AfterHash
IndentWidth: '4'
IndentWrappedFunctionNames: false
Language: Cpp
MacroBlockBegin: "^BEGIN_*"
MacroBlockEnd: "^END_*"
MaxEmptyLinesToKeep: '1'
PointerAlignment: Left
ReflowComments: 'true'
SortIncludes: 'false'
SpaceAfterCStyleCast: 'false'
SpaceAfterTemplateKeyword : 'true'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: 'false'
SpaceInEmptyParentheses: 'false'
SpacesInAngles: 'false'
SpacesInCStyleCastParentheses: 'false'
SpacesInParentheses: 'true'
SpacesInSquareBrackets: 'false'
SortIncludes: 'true'
Standard: Cpp11
TabWidth: '4'
TypenameMacros: ['STDMETHOD', 'STDMETHODIMP_']
UseTab: Never
...
================================================
FILE: foo_spotify/.clang-tidy
================================================
Checks: '-*,
bugprone-*,
cert-*,
clang-analyzer-*,
google-*,
-google-build-using-namespace,
-google-explicit-constructor,
-google-readability-todo,
-google-runtime-references,
hicpp-*,
-hicpp-explicit-conversions,
-hicpp-no-array-decay,
-hicpp-signed-bitwise,
-hicpp-uppercase-literal-suffix,
misc-*,
-misc-non-private-member-variables-in-classes,
llvm-*,
-llvm-include-order,
-llvm-header-guard,
-llvm-namespace-comment,
modernize-*,
-modernize-use-trailing-return-type,
performance-*,
portability-*,
readability-*,
-readability-else-after-return,
-readability-implicit-bool-conversion,
-readability-magic-numbers,
-readability-named-parameter,
-readability-redundant-access-specifiers,
-readability-uppercase-literal-suffix'
================================================
FILE: foo_spotify/backend/audio_buffer.cpp
================================================
#include <stdafx.h>
#include "audio_buffer.h"
#include <utils/abort_manager.h>
namespace sptf
{
AudioBuffer::AudioBuffer( AbortManager& abortManager )
: abortManager_( abortManager )
{
}
bool AudioBuffer::write( AudioChunkHeader header, const uint16_t* data )
{
{
std::lock_guard lock( posMutex_ );
const size_t readPos = readPos_;
const size_t writePos = writePos_;
const size_t waterMark = waterMark_;
const size_t writeSize = k_headerSizeInU16 + header.size;
const auto writeData = [&]( size_t writePos ) {
const auto curBufferPos = begin_ + writePos;
std::copy( &header, &header + 1, reinterpret_cast<AudioChunkHeader*>( curBufferPos ) );
std::copy( data, data + header.size, curBufferPos + k_headerSizeInU16 );
};
// TODO: simplify the code
if ( writePos >= readPos ) // write leads
{
if ( size_ - writePos >= writeSize )
{
writeData( writePos );
writePos_ = writePos + writeSize;
waterMark_ = size_;
}
else // wrap-around
{
if ( !readPos || readPos - 1 < writeSize )
{
return false;
}
writeData( 0 );
writePos_ = writeSize;
waterMark_ = writePos;
}
}
else // read leads
{
if ( readPos - 1 - writePos < writeSize )
{
return false;
}
writeData( writePos );
writePos_ = writePos + writeSize;
}
}
dataCv_.notify_all();
return true;
}
void AudioBuffer::write_end()
{
uint16_t dummy{};
write( AudioChunkHeader{ 0, 0, 0, 1 }, &dummy );
}
bool AudioBuffer::has_data() const
{
std::lock_guard lock( posMutex_ );
return has_data_no_lock();
}
bool AudioBuffer::wait_for_data( abort_callback& abort )
{
const auto abortableScope = abortManager_.GetAbortableScope( [&] { dataCv_.notify_all(); }, abort );
std::unique_lock lock( posMutex_ );
dataCv_.wait( lock, [&] {
return ( has_data_no_lock() || abort.is_aborting() );
} );
return has_data_no_lock();
}
void AudioBuffer::clear()
{
{
std::lock_guard lock( posMutex_ );
readPos_ = 0;
writePos_ = 0;
waterMark_ = size_;
}
dataCv_.notify_all();
}
bool AudioBuffer::has_data_no_lock() const
{
return ( readPos_ != writePos_ );
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/audio_buffer.h
================================================
#pragma once
#include <nonstd/span.hpp>
#include <array>
#include <atomic>
#include <mutex>
namespace sptf
{
class AbortManager;
// TODO: bench and replace with lock-free if needed
class AudioBuffer
{
static constexpr size_t k_maxBufferSize = 8UL * 1024 * 1024;
public:
#pragma pack( push )
#pragma pack( 1 )
struct AudioChunkHeader
{
uint32_t sampleRate;
uint16_t channels;
uint32_t size;
uint16_t eof;
};
#pragma pack( pop )
private:
static constexpr size_t k_headerSizeInU16 = sizeof( AudioChunkHeader ) / sizeof( uint16_t );
public:
AudioBuffer( AbortManager& abortManager );
~AudioBuffer() = default;
bool write( AudioChunkHeader header, const uint16_t* data );
void write_end();
template <typename Fn>
bool read( Fn fn );
bool has_data() const;
bool wait_for_data( abort_callback& abort );
void clear();
private:
bool has_data_no_lock() const;
private:
AbortManager& abortManager_;
std::array<uint16_t, k_maxBufferSize> buffer_;
uint16_t* begin_ = buffer_.data();
static constexpr size_t size_ = k_maxBufferSize;
mutable std::mutex posMutex_;
std::condition_variable dataCv_;
size_t readPos_ = 0;
size_t writePos_ = 0;
size_t waterMark_ = size_;
};
template <typename Fn>
bool sptf::AudioBuffer::read( Fn fn )
{
std::lock_guard lock( posMutex_ );
size_t readPos = readPos_;
const size_t writePos = writePos_;
const size_t waterMark = waterMark_;
if ( readPos == writePos )
{
return false;
}
readPos = ( readPos == waterMark ? 0 : readPos );
const auto curBufferPos = begin_ + readPos;
fn( *reinterpret_cast<AudioChunkHeader*>( curBufferPos ), curBufferPos + k_headerSizeInU16 );
readPos_ = readPos + k_headerSizeInU16 + reinterpret_cast<AudioChunkHeader*>( curBufferPos )->size;
return true;
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/libspotify_backend.cpp
================================================
#include <stdafx.h>
#include "libspotify_backend.h"
#include <backend/libspotify_key.h>
#include <backend/spotify_instance.h>
#include <fb2k/advanced_config.h>
#include <ui/ui_not_auth.h>
#include <utils/abort_manager.h>
#include <utils/cred_prompt.h>
#include <component_paths.h>
#include <libspotify/api.h>
#include <qwr/abort_callback.h>
#include <qwr/error_popup.h>
#include <qwr/fb2k_adv_config.h>
#include <qwr/thread_helpers.h>
#include <qwr/winapi_error_helpers.h>
#include <filesystem>
#include <fstream>
#include <tuple>
// see https://github.com/mopidy/mopidy-spotify for tips and stuff
namespace fs = std::filesystem;
using namespace sptf;
namespace sptf
{
LibSpotify_Backend::LibSpotify_Backend( AbortManager& abortManager )
: abortManager_( abortManager )
, audioBuffer_( abortManager )
{
if ( const auto settingsPath = path::LibSpotifySettings(); !fs::exists( settingsPath ) )
{
fs::create_directories( settingsPath );
}
// TODO: add fs error/exception checks
const auto cachePath = path::LibSpotifyCache().u8string();
const auto settingsPath = path::LibSpotifySettings().u8string();
config_.api_version = SPOTIFY_API_VERSION,
config_.cache_location = cachePath.c_str();
config_.settings_location = settingsPath.c_str();
config_.application_key = g_appkey;
config_.application_key_size = sizeof( g_appkey );
config_.user_agent = "foobar2000-foo_spotify-" SPTF_VERSION;
config_.userdata = this;
config_.callbacks = &callbacks_;
const auto proxyUrl = sptf::config::advanced::network_proxy.GetValue();
const auto proxyUsername = sptf::config::advanced::network_proxy_username.GetValue();
const auto proxyPassword = sptf::config::advanced::network_proxy_password.GetValue();
if ( !proxyUrl.empty() )
{
config_.proxy = proxyUrl.c_str();
if ( !proxyUsername.empty() && !proxyPassword.empty() )
{
config_.proxy_username = proxyUsername.c_str();
config_.proxy_password = proxyPassword.c_str();
}
}
/*
config_.tracefile = ...;
*/
#define SPTF_ASSIGN_CALLBACK( callbacks, name ) \
callbacks.name = []( sp_session * pSession, auto... args ) -> auto \
{ /** sp_session_userdata is assumed to be thread safe. */ \
return static_cast<LibSpotify_Backend*>( sp_session_userdata( pSession ) )->name( args... ); \
}
// dummy callbacks are needed to avoid libspotify crashes on sp_session_release
#define SPTF_ASSIGN_DUMMY_CALLBACK( callbacks, name ) \
callbacks.name = []( auto... ) {}
SPTF_ASSIGN_CALLBACK( callbacks_, logged_in );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, logged_out );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, metadata_updated );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, connection_error );
SPTF_ASSIGN_CALLBACK( callbacks_, message_to_user );
SPTF_ASSIGN_CALLBACK( callbacks_, notify_main_thread );
SPTF_ASSIGN_CALLBACK( callbacks_, music_delivery );
SPTF_ASSIGN_CALLBACK( callbacks_, play_token_lost );
SPTF_ASSIGN_CALLBACK( callbacks_, log_message );
SPTF_ASSIGN_CALLBACK( callbacks_, end_of_track );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, streaming_error );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, userinfo_updated );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, start_playback );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, stop_playback );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, get_audio_buffer_stats );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, offline_status_updated );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, offline_error );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, credentials_blob_updated );
SPTF_ASSIGN_CALLBACK( callbacks_, connectionstate_updated );
SPTF_ASSIGN_DUMMY_CALLBACK( callbacks_, scrobble_error );
SPTF_ASSIGN_CALLBACK( callbacks_, private_session_mode_changed );
#undef SPTF_ASSIGN_DUMMY_CALLBACK
#undef SPTF_ASSIGN_CALLBACK
// TODO: check if `sp_playlist_add_callbacks` works when implementing playlist handling
{
std::lock_guard lock( apiMutex_ );
const auto sp = sp_session_create( &config_, &pSpSession_ );
if ( sp != SP_ERROR_OK )
{
throw qwr::QwrException( fmt::format( "sp_session_create failed: {}", sp_error_message( sp ) ) );
}
}
StartEventLoopThread();
RefreshBitrate();
RefreshNormalization();
RefreshCacheSize();
}
void LibSpotify_Backend::Finalize()
{
StopEventLoopThread();
{
std::lock_guard lk( backendUsersMutex_ );
for ( auto& pInput: backendUsers_ )
{
pInput->Finalize();
}
}
{
std::lock_guard lock( apiMutex_ );
sp_session_player_unload( pSpSession_ );
sp_session_release( pSpSession_ );
}
}
void LibSpotify_Backend::RegisterBackendUser( LibSpotify_BackendUser& input )
{
std::lock_guard lk( backendUsersMutex_ );
assert( !backendUsers_.count( &input ) );
backendUsers_.emplace( &input );
}
void LibSpotify_Backend::UnregisterBackendUser( LibSpotify_BackendUser& input )
{
std::lock_guard lk( backendUsersMutex_ );
assert( backendUsers_.count( &input ) );
backendUsers_.erase( &input );
}
sp_session* LibSpotify_Backend::GetInitializedSpSession( abort_callback& p_abort )
{
assert( pSpSession_ );
if ( !Relogin( p_abort ) )
{
::fb2k::inMainThread( [&] {
playback_control::get()->stop();
ui::NotAuthHandler::Get().ShowDialog();
} );
throw qwr::QwrException( "Failed to get authenticated Spotify session" );
}
return pSpSession_;
}
sp_session* LibSpotify_Backend::GetWhateverSpSession()
{
assert( pSpSession_ );
return pSpSession_;
}
bool LibSpotify_Backend::Relogin( abort_callback& abort )
{
{
std::lock_guard lock( loginMutex_ );
if ( loginStatus_ == LoginStatus::logged_out )
{
return false;
}
if ( loginStatus_ == LoginStatus::uninitialized )
{
loginStatus_ = LoginStatus::login_in_process;
const auto spRet = [&] {
std::lock_guard lock( apiMutex_ );
return sp_session_relogin( pSpSession_ );
}();
if ( spRet == SP_ERROR_NO_CREDENTIALS )
{
loginStatus_ = LoginStatus::logged_out;
return false;
}
}
}
auto retStatus = WaitForLoginStatusUpdate( abort );
return ( retStatus.has_value() ? *retStatus : false );
}
bool LibSpotify_Backend::LoginWithUI( HWND hWnd )
{
// TODO: add abortable as a method argument
assert( core_api::assert_main_thread() );
{
std::lock_guard lock( loginMutex_ );
assert( loginStatus_ == LoginStatus::logged_out );
loginStatus_ = LoginStatus::login_in_process;
isLoginBad_ = false;
}
std::optional<bool> retStatus;
do
{
const auto wasLoginBad = [&] {
std::lock_guard lock( loginMutex_ );
return isLoginBad_;
}();
auto cpr = ShowCredentialsDialog( hWnd, wasLoginBad ? sp_error_message( SP_ERROR_BAD_USERNAME_OR_PASSWORD ) : nullptr );
if ( cpr->cancelled )
{
{
std::lock_guard lock( loginMutex_ );
loginStatus_ = LoginStatus::logged_out;
}
loginCv_.notify_all();
return false;
}
{
std::lock_guard lock( apiMutex_ );
sp_session_login( pSpSession_, cpr->un.data(), cpr->pw.data(), true, nullptr );
}
qwr::TimedAbortCallback tac( fmt::format( "{}: {}", SPTF_UNDERSCORE_NAME, "LibSpotify wait for login update" ) );
retStatus = WaitForLoginStatusUpdate( tac );
} while ( retStatus.has_value() && !*retStatus );
return ( retStatus.has_value() ? *retStatus : false );
}
void LibSpotify_Backend::LogoutAndForget( abort_callback& abort )
{
{
std::lock_guard lgLogin( loginMutex_ );
if ( loginStatus_ != LoginStatus::logged_in )
{
return;
}
loginStatus_ = LoginStatus::logout_in_process;
}
{
std::lock_guard lgApi( apiMutex_ );
sp_session_logout( pSpSession_ );
sp_session_forget_me( pSpSession_ );
}
WaitForLoginStatusUpdate( abort );
}
std::string LibSpotify_Backend::GetLoggedInUserName()
{
{
std::lock_guard lock( loginMutex_ );
if ( loginStatus_ != LoginStatus::logged_in )
{
return "<error: user is not logged in>";
}
}
std::lock_guard lock( apiMutex_ );
// `sp_user_display_name` always returns canonical name:
// https://stackoverflow.com/questions/23797162/sp-user-display-name-always-returns-canonical-name-even-when-user-is-loaded
const char* email = sp_session_user_name( pSpSession_ );
if ( !email )
{
return "<error: user name could not be fetched>";
}
return email;
}
void LibSpotify_Backend::RefreshBitrate()
{
std::lock_guard lock( apiMutex_ );
const auto sp = sp_session_preferred_bitrate( pSpSession_, static_cast<sp_bitrate>( static_cast<uint8_t>( config::preferred_bitrate.GetValue() ) ) );
if ( sp != SP_ERROR_OK )
{
qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, fmt::format( "sp_session_preferred_bitrate failed:\n{}", sp_error_message( sp ) ) );
}
}
void LibSpotify_Backend::RefreshNormalization()
{
std::lock_guard lock( apiMutex_ );
const auto sp = sp_session_set_volume_normalization( pSpSession_, config::enable_normalization );
if ( sp != SP_ERROR_OK )
{
qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, fmt::format( "sp_session_set_volume_normalization failed:\n{}", sp_error_message( sp ) ) );
}
}
void LibSpotify_Backend::RefreshPrivateMode()
{
{
std::lock_guard lock( loginMutex_ );
if ( loginStatus_ != LoginStatus::logged_in )
{ // private session can be set only in logged in session
return;
}
}
{
std::lock_guard lock( apiMutex_ );
RefreshPrivateModeNonBlocking();
}
}
void LibSpotify_Backend::RefreshCacheSize()
{
const auto sizeInMb = config::libspotify_cache_size_in_mb.GetValue();
const auto sizeInPercent = config::libspotify_cache_size_in_percent.GetValue();
const auto cacheSize = [sizeInMb, sizeInPercent]() -> uint32_t {
if ( !sizeInMb && ( sizeInPercent == 10 || !sizeInPercent ) )
{ // 0 - default LibSpotify behaviour
return 0;
}
uint64_t dummy1;
uint64_t dummy2;
uint64_t freeBytes;
auto bRet = GetDiskFreeSpaceEx( L"C:",
(PULARGE_INTEGER)&dummy1,
(PULARGE_INTEGER)&dummy2,
(PULARGE_INTEGER)&freeBytes );
qwr::error::CheckWinApi( bRet, "GetDiskFreeSpaceEx" );
const auto freeMb = ( uint32_t )( freeBytes / ( 1024 * 1024 ) );
if ( !sizeInPercent )
{
return std::min( sizeInMb, freeMb );
}
else
{
const auto freeSpacePercented = ( uint32_t )( freeMb * ( 1.0 / sizeInPercent ) );
if ( sizeInMb )
{
return std::min( sizeInMb, freeSpacePercented );
}
else
{
return freeSpacePercented;
}
}
}();
std::lock_guard lock( apiMutex_ );
const auto sp = sp_session_set_cache_size( pSpSession_, cacheSize );
if ( sp != SP_ERROR_OK )
{
qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, fmt::format( "sp_session_set_cache_size failed:\n{}", sp_error_message( sp ) ) );
}
}
void LibSpotify_Backend::EventLoopThread()
{
int nextTimeout = INFINITE;
while ( true )
{
{
std::unique_lock lock( workerMutex_ );
while ( !hasEvents_ && !shouldStopEventLoop_ )
{
const auto ret = eventLoopCv_.wait_for( lock, std::chrono::milliseconds( nextTimeout ) );
if ( std::cv_status::timeout == ret )
{
break;
}
}
if ( shouldStopEventLoop_ )
{
return;
}
hasEvents_ = false;
}
std::lock_guard lock( apiMutex_ );
sp_session_process_events( pSpSession_, &nextTimeout );
}
}
void LibSpotify_Backend::StartEventLoopThread()
{
assert( !pWorker_ );
pWorker_ = std::make_unique<std::thread>( &LibSpotify_Backend::EventLoopThread, this );
qwr::SetThreadName( *pWorker_, "SPTF Event Loop" );
}
void LibSpotify_Backend::StopEventLoopThread()
{
if ( !pWorker_ )
{
return;
}
{
std::unique_lock<std::mutex> lock( workerMutex_ );
shouldStopEventLoop_ = true;
}
eventLoopCv_.notify_all();
if ( pWorker_->joinable() )
{
pWorker_->join();
}
pWorker_.reset();
}
std::optional<bool> LibSpotify_Backend::WaitForLoginStatusUpdate( abort_callback& abort )
{
const auto abortableScope = abortManager_.GetAbortableScope( [&] { loginCv_.notify_all(); }, abort );
std::unique_lock lock( loginMutex_ );
loginCv_.wait( lock, [&] {
return ( ( loginStatus_ != LoginStatus::login_in_process
&& loginStatus_ != LoginStatus::logout_in_process )
|| abort.is_aborting() );
} );
if ( abort.is_aborting() )
{
return std::nullopt;
}
return ( loginStatus_ == LoginStatus::logged_in );
}
void LibSpotify_Backend::RefreshPrivateModeNonBlocking()
{
const auto sp = sp_session_set_private_session( pSpSession_, config::enable_private_mode );
if ( sp != SP_ERROR_OK )
{
qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, fmt::format( "sp_session_set_private_session failed:\n{}", sp_error_message( sp ) ) );
}
}
void LibSpotify_Backend::AcquireDecoder( void* owner )
{
std::lock_guard lk( decoderOwnerMutex_ );
if ( pDecoderOwner_ && pDecoderOwner_ != owner )
{
throw exception_io_data( "Someone else is already decoding: Spotify does not support multiple concurrent decoders" );
}
pDecoderOwner_ = owner;
}
void LibSpotify_Backend::ReleaseDecoder( void* owner )
{
(void)owner;
std::lock_guard lk( decoderOwnerMutex_ );
assert( owner == pDecoderOwner_ );
pDecoderOwner_ = nullptr;
}
AudioBuffer& LibSpotify_Backend::GetAudioBuffer()
{
return audioBuffer_;
}
void LibSpotify_Backend::log_message( const char* error )
{
FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (log): " << error;
}
void LibSpotify_Backend::logged_in( sp_error error )
{
if ( SP_ERROR_OK == error )
{
return;
}
if ( SP_ERROR_BAD_USERNAME_OR_PASSWORD == error )
{
std::lock_guard lock( loginMutex_ );
if ( loginStatus_ == LoginStatus::login_in_process )
{
isLoginBad_ = true;
loginStatus_ = LoginStatus::logged_out;
}
}
else
{
{
std::lock_guard lock( loginMutex_ );
loginStatus_ = LoginStatus::uninitialized;
}
qwr::ReportErrorWithPopup( SPTF_NAME, fmt::format( "Failed to login:\n{}", sp_error_message( error ) ) );
}
}
void LibSpotify_Backend::message_to_user( const char* message )
{
qwr::ReportErrorWithPopup( SPTF_NAME, message );
}
void LibSpotify_Backend::notify_main_thread()
{
{
std::unique_lock lock( workerMutex_ );
hasEvents_ = true;
}
eventLoopCv_.notify_all();
}
int LibSpotify_Backend::music_delivery( const sp_audioformat* format, const void* frames, int num_frames )
{
if ( !num_frames )
{
audioBuffer_.clear();
return 0;
}
assert( frames );
if ( num_frames == 22050 && !*(uint16_t*)frames )
{ // a dirty hack to remove a 1 seconds silence that libspotify pads the end of a track with
// See: https://github.com/mopidy/mopidy-spotify/pull/269
// and https://stackoverflow.com/questions/26014520/libspotify-c-sending-zeros-at-the-end-of-track
return num_frames;
}
if ( !audioBuffer_.write( AudioBuffer::AudioChunkHeader{ (uint16_t)format->sample_rate,
(uint16_t)format->channels,
( uint16_t )( num_frames * format->channels ) },
static_cast<const uint16_t*>( frames ) ) )
{
return 0;
}
return num_frames;
}
void LibSpotify_Backend::end_of_track()
{
audioBuffer_.write_end();
}
void LibSpotify_Backend::play_token_lost()
{
::fb2k::inMainThread2( [&] {
playback_control::get()->pause( true );
} );
qwr::ReportErrorWithPopup( SPTF_NAME, "Playback has been paused because your Spotify account is being used somewhere else." );
}
void LibSpotify_Backend::connectionstate_updated()
{
switch ( sp_session_connectionstate( pSpSession_ ) )
{
case SP_CONNECTION_STATE_LOGGED_IN:
case SP_CONNECTION_STATE_OFFLINE:
{
{
std::lock_guard lock( loginMutex_ );
loginStatus_ = LoginStatus::logged_in;
}
loginCv_.notify_all();
RefreshPrivateModeNonBlocking();
break;
}
case SP_CONNECTION_STATE_LOGGED_OUT:
{
{
std::lock_guard lock( loginMutex_ );
if ( loginStatus_ == LoginStatus::login_in_process )
{ // possible when invalid user/pass are supplied
break;
}
loginStatus_ = LoginStatus::logged_out;
}
loginCv_.notify_all();
break;
}
default:
{
break;
}
}
}
void LibSpotify_Backend::private_session_mode_changed( bool is_private )
{ // in case private mode expired
if ( is_private == config::enable_private_mode )
{
return;
}
RefreshPrivateModeNonBlocking();
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/libspotify_backend.h
================================================
#pragma once
#include <backend/audio_buffer.h>
#include <backend/libspotify_backend_user.h>
#include <fb2k/config.h>
#include <libspotify/api.h>
#include <condition_variable>
#include <optional>
#include <unordered_set>
namespace sptf
{
class AbortManager;
class LibSpotify_Backend
{
public:
LibSpotify_Backend( AbortManager& abortManager );
LibSpotify_Backend( const LibSpotify_Backend& ) = delete;
LibSpotify_Backend( LibSpotify_Backend&& ) = delete;
~LibSpotify_Backend() = default;
void Finalize();
void RegisterBackendUser( LibSpotify_BackendUser& backendUser );
void UnregisterBackendUser( LibSpotify_BackendUser& backendUser );
void AcquireDecoder( void* owner );
void ReleaseDecoder( void* owner );
AudioBuffer& GetAudioBuffer();
sp_session* GetInitializedSpSession( abort_callback& abort );
sp_session* GetWhateverSpSession();
template <typename Fn, typename... Args>
auto ExecSpMutex( Fn func, Args&&... args ) -> decltype( auto )
{
std::lock_guard lock( apiMutex_ );
return func( std::forward<Args>( args )... );
}
bool Relogin( abort_callback& abort );
bool LoginWithUI( HWND hWnd );
void LogoutAndForget( abort_callback& abort );
std::string GetLoggedInUserName();
void RefreshBitrate();
void RefreshNormalization();
void RefreshPrivateMode();
void RefreshCacheSize();
private:
void EventLoopThread();
void StartEventLoopThread();
void StopEventLoopThread();
std::optional<bool> WaitForLoginStatusUpdate( abort_callback& abort );
void RefreshPrivateModeNonBlocking();
// callbacks
void log_message( const char* error );
void logged_in( sp_error error );
void message_to_user( const char* error );
void notify_main_thread();
int music_delivery( const sp_audioformat* format, const void* frames, int num_frames );
void end_of_track();
void play_token_lost();
void connectionstate_updated();
void private_session_mode_changed( bool is_private );
private:
AbortManager& abortManager_;
sp_session_callbacks callbacks_{};
sp_session_config config_{};
std::mutex decoderOwnerMutex_;
void* pDecoderOwner_ = nullptr;
std::mutex apiMutex_;
sp_session* pSpSession_ = nullptr;
std::unique_ptr<std::thread> pWorker_;
std::mutex workerMutex_;
std::condition_variable eventLoopCv_;
bool hasEvents_ = false;
bool shouldStopEventLoop_ = false;
std::mutex backendUsersMutex_;
std::unordered_set<LibSpotify_BackendUser*> backendUsers_;
enum class LoginStatus
{
uninitialized,
logged_in,
logged_out,
login_in_process,
logout_in_process,
};
std::mutex loginMutex_;
std::condition_variable loginCv_;
LoginStatus loginStatus_ = LoginStatus::uninitialized;
bool isLoginBad_ = false;
AudioBuffer audioBuffer_;
};
} // namespace sptf
================================================
FILE: foo_spotify/backend/libspotify_backend_user.h
================================================
#pragma once
namespace sptf
{
class LibSpotify_BackendUser
{
public:
~LibSpotify_BackendUser() = default;
virtual void Finalize() = 0;
};
} // namespace sptf
================================================
FILE: foo_spotify/backend/libspotify_key.h
================================================
#pragma once
#include <cstdint>
// clang-format off
inline constexpr uint8_t g_appkey[] = {
0x01, 0xF8, 0x35, 0x9D, 0x45, 0x52, 0xD7, 0xA5, 0xC2, 0x91, 0xC0, 0x94, 0x18, 0x52, 0x81, 0x28,
0x6A, 0xA4, 0xAE, 0x77, 0x4B, 0xE9, 0x87, 0xC9, 0x05, 0x2D, 0x72, 0x11, 0xC2, 0x59, 0x18, 0x6F,
0xB3, 0x91, 0x4E, 0x63, 0xCE, 0x6E, 0xDA, 0xF5, 0xE3, 0xC7, 0xC6, 0xC9, 0x3B, 0x88, 0x37, 0x31,
0x3C, 0xDA, 0x71, 0xF1, 0x4C, 0x68, 0x03, 0x1B, 0xF8, 0xC0, 0x4A, 0xBA, 0x20, 0x0B, 0xBE, 0xC4,
0x2F, 0x49, 0x7A, 0xC5, 0x5C, 0xC4, 0x36, 0xF1, 0x94, 0xF7, 0xC6, 0x4F, 0x27, 0xFB, 0x5E, 0x31,
0x00, 0x31, 0x53, 0x52, 0x74, 0x0B, 0x64, 0x16, 0x4B, 0x70, 0xA6, 0x71, 0x6D, 0xC0, 0x71, 0x7F,
0x6B, 0xFD, 0xE5, 0xA4, 0x57, 0x99, 0x4A, 0x11, 0xB0, 0x0B, 0xBA, 0xC4, 0xF2, 0x9A, 0xB9, 0x33,
0xC7, 0x5F, 0x70, 0x6D, 0x73, 0xEA, 0xAB, 0x88, 0xD2, 0x83, 0xAC, 0x33, 0xA7, 0xFE, 0xDA, 0xD7,
0xF2, 0xAC, 0x04, 0x41, 0xAC, 0x7D, 0xEF, 0x88, 0x5A, 0x21, 0xEC, 0xD8, 0x02, 0x40, 0xB7, 0x0C,
0x3A, 0x42, 0x2C, 0x26, 0x17, 0x9D, 0x82, 0x43, 0x50, 0x07, 0x1F, 0x2E, 0xAF, 0x72, 0x88, 0x65,
0xDB, 0xA7, 0x83, 0xA6, 0x44, 0x06, 0x2E, 0x55, 0x08, 0xF2, 0x4C, 0x4A, 0x96, 0xF7, 0xD5, 0xFC,
0x57, 0x75, 0x5F, 0x1C, 0x1D, 0xB1, 0x35, 0x5A, 0x48, 0x83, 0x1F, 0x60, 0x65, 0xBD, 0x40, 0xDD,
0x03, 0x1D, 0x3A, 0x4E, 0xD6, 0xA8, 0xB8, 0x5E, 0xFC, 0x60, 0x59, 0xE0, 0xB3, 0xC5, 0x64, 0xD0,
0x2A, 0x20, 0x48, 0x24, 0x5E, 0x9B, 0xE4, 0xF0, 0x45, 0xAA, 0x30, 0xCE, 0xB3, 0x11, 0x45, 0xB0,
0x01, 0xDA, 0x82, 0x58, 0xFE, 0x4F, 0x97, 0xF2, 0x40, 0x4D, 0x8C, 0x83, 0x17, 0x8C, 0x97, 0xCF,
0x40, 0x17, 0xDB, 0xC7, 0x19, 0xEC, 0xFA, 0x01, 0x4F, 0xE6, 0x78, 0x3A, 0x54, 0x3F, 0xE9, 0x62,
0xA3, 0xF2, 0xDD, 0x0A, 0xDD, 0x90, 0xE4, 0x24, 0xE7, 0x57, 0x8C, 0x30, 0x18, 0x13, 0xA6, 0x9C,
0x52, 0x7D, 0xBE, 0x0B, 0x10, 0xEA, 0x0C, 0x53, 0x87, 0x9B, 0x26, 0xBA, 0xA7, 0x70, 0xAE, 0x88,
0x8A, 0xAA, 0x5E, 0x7C, 0x73, 0xC3, 0x08, 0x92, 0x29, 0xDC, 0xEA, 0x22, 0x25, 0x16, 0xB5, 0xF0,
0x89, 0xF0, 0x88, 0xBE, 0xAB, 0x84, 0x6C, 0xFE, 0xF1, 0xBE, 0x8A, 0x81, 0x19, 0x0A, 0xE7, 0x64,
0xC4,
};
// clang-format on
================================================
FILE: foo_spotify/backend/libspotify_wrapper.h
================================================
#pragma once
#include <backend/libspotify_backend.h>
#include <libspotify/api.h>
namespace sptf::wrapper
{
namespace internal
{
template <typename T>
struct SpotifyTraits
{
static_assert( "unknown specialization" );
};
template <>
struct SpotifyTraits<sp_track>
{
static void AddRef( sp_track* album )
{
sp_track_add_ref( album );
}
static void Release( sp_track* album )
{
sp_track_release( album );
}
static bool IsLoaded( sp_track* album )
{
return sp_track_is_loaded( album );
}
};
template <>
struct SpotifyTraits<sp_link>
{
static void AddRef( sp_link* link )
{
sp_link_add_ref( link );
}
static void Release( sp_link* link )
{
sp_link_release( link );
}
// does not have a "IsLoaded" method
};
} // namespace internal
template <typename T>
class Ptr
{
public:
Ptr() = default;
Ptr( T* ptr )
: ptr_( ptr )
{
if ( ptr_ )
{
internal::SpotifyTraits<T>::AddRef( ptr_ );
}
}
Ptr( const Ptr<T*>& other )
: ptr_( other.ptr_ )
{
if ( ptr_ )
{
internal::SpotifyTraits<T>::AddRef( ptr_ );
}
}
Ptr( Ptr<T*>&& other )
: ptr_( other.ptr_ )
{
other.ptr_ = nullptr;
}
void Release()
{
auto ptr = ptr_;
ptr_ = nullptr;
if ( ptr )
{
internal::SpotifyTraits<T>::Release( ptr );
}
}
void Attach( T* ptr )
{
if ( ptr_ )
{
internal::SpotifyTraits<T>::Release( ptr );
}
ptr_ = ptr;
}
operator bool() const
{
return !!ptr_;
}
bool operator!() const
{
return !ptr_;
}
T* operator=( T* ptr )
{
Release();
ptr_ = ptr;
if ( ptr_ )
{
internal::SpotifyTraits<T>::AddRef( ptr_ );
}
return ptr_;
}
operator T*() const
{
return ptr_;
}
private:
T* ptr_ = nullptr;
};
} // namespace sptf::wrapper
================================================
FILE: foo_spotify/backend/spotify_instance.cpp
================================================
#include <stdafx.h>
#include "spotify_instance.h"
#include <backend/libspotify_backend.h>
#include <backend/webapi_auth.h>
#include <backend/webapi_backend.h>
#include <fb2k/playback.h>
#include <utils/abort_manager.h>
#include <qwr/abort_callback.h>
#include <qwr/thread_pool.h>
namespace sptf
{
SpotifyInstance& SpotifyInstance::Get()
{
static SpotifyInstance si;
return si;
}
void SpotifyInstance::Finalize()
{
std::lock_guard lg( mutex_ );
isFinalized_ = true;
const auto finalize = []( auto& pElem ) {
if ( pElem )
{
try
{
pElem->Finalize();
}
catch ( const std::exception& )
{
}
pElem.reset();
}
};
if ( fb2k_playCallbacks_initialized_ )
{
fb2k::PlayCallbacks::Finalize();
fb2k_playCallbacks_initialized_ = false;
}
finalize( pWebApi_backend_ );
finalize( pLibSpotify_backend_ );
finalize( pAbortManager_ );
finalize( pThreadPool_ );
}
qwr::ThreadPool& SpotifyInstance::GetThreadPool()
{
InitializeAll();
assert( pThreadPool_ );
return *pThreadPool_;
}
AbortManager& SpotifyInstance::GetAbortManager()
{
InitializeAll();
assert( pAbortManager_ );
return *pAbortManager_;
}
LibSpotify_Backend& SpotifyInstance::GetLibSpotify_Backend()
{
InitializeAll();
assert( pLibSpotify_backend_ );
return *pLibSpotify_backend_;
}
WebApi_Backend& SpotifyInstance::GetWebApi_Backend()
{
InitializeAll();
assert( pWebApi_backend_ );
return *pWebApi_backend_;
}
void SpotifyInstance::InitializeAll()
{
std::lock_guard lg( mutex_ );
if ( isFinalized_ )
{
throw qwr::QwrException( "foobar2000 is exiting" );
}
if ( !pThreadPool_ )
{
pThreadPool_ = std::make_unique<qwr::ThreadPool>( "SPTF Worker", 2 );
}
if ( !pAbortManager_ )
{
pAbortManager_ = std::make_unique<AbortManager>();
}
if ( !pLibSpotify_backend_ )
{
pLibSpotify_backend_ = std::make_unique<LibSpotify_Backend>( *pAbortManager_ );
}
if ( !pWebApi_backend_ )
{
pWebApi_backend_ = std::make_unique<WebApi_Backend>( *pAbortManager_ );
}
if ( !fb2k_playCallbacks_initialized_ )
{
fb2k::PlayCallbacks::Initialize( *pLibSpotify_backend_ );
fb2k_playCallbacks_initialized_ = true;
}
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/spotify_instance.h
================================================
#pragma once
#include <mutex>
namespace qwr
{
class ThreadPool;
}
namespace sptf
{
class AbortManager;
class LibSpotify_Backend;
class WebApi_Backend;
namespace fb2k
{
class PlayCallbacks;
}
class SpotifyInstance
{
public:
~SpotifyInstance() = default;
static SpotifyInstance& Get();
void Finalize();
qwr::ThreadPool& GetThreadPool();
AbortManager& GetAbortManager();
LibSpotify_Backend& GetLibSpotify_Backend();
WebApi_Backend& GetWebApi_Backend();
private:
SpotifyInstance() = default;
void InitializeAll();
private:
std::mutex mutex_;
bool isFinalized_ = false;
std::unique_ptr<qwr::ThreadPool> pThreadPool_;
std::unique_ptr<AbortManager> pAbortManager_;
std::unique_ptr<LibSpotify_Backend> pLibSpotify_backend_;
std::unique_ptr<WebApi_Backend> pWebApi_backend_;
bool fb2k_playCallbacks_initialized_ = false;
};
} // namespace sptf
================================================
FILE: foo_spotify/backend/spotify_object.cpp
================================================
#include <stdafx.h>
#include "spotify_object.h"
#include <qwr/string_helpers.h>
using namespace std::literals::string_view_literals;
namespace sptf
{
bool SpotifyObject::IsValid( std::string_view input )
{
try
{
SpotifyObject tmp( input );
(void)tmp;
return true;
}
catch ( const qwr::QwrException& )
{
return false;
}
}
SpotifyObject::SpotifyObject( std::string_view input )
{
const auto schemaPrefix = "sptf://"sv;
if ( input._Starts_with( schemaPrefix ) )
{
input.remove_prefix( schemaPrefix.size() );
}
const auto urlPrefix = "https://open.spotify.com/"sv;
if ( input._Starts_with( urlPrefix ) )
{
input.remove_prefix( urlPrefix.size() );
const auto ret = qwr::string::Split( input, '/' );
qwr::QwrException::ExpectTrue( ret.size() == 2, "Invalid URL" );
const auto localType = ret[0];
type = std::string( localType.data(), localType.size() );
const auto localId = [localId = ret[1]] {
const auto pos = localId.find( '?' );
if ( pos == std::string_view::npos )
{
return localId;
}
return localId.substr( 0, pos );
}();
qwr::QwrException::ExpectTrue( !localId.empty(), "Invalid Spotify object id: {}", localId );
id = std::string( localId.cbegin(), localId.cend() );
}
else
{
const auto ret = qwr::string::Split( input, ':' );
qwr::QwrException::ExpectTrue( ret.size() == 3, "Invalid URI" );
qwr::QwrException::ExpectTrue( ret[0] == "spotify"sv, "Invalid URI" );
const auto localType = ret[1];
type = std::string( localType.data(), localType.size() );
const auto localId = ret[2];
qwr::QwrException::ExpectTrue( !localId.empty(), "Invalid Spotify object id: {}", localId );
id = std::string( localId.cbegin(), localId.cend() );
}
}
SpotifyObject::SpotifyObject( std::string_view type, std::string_view id )
: type( type )
, id( id )
{
}
std::string SpotifyObject::ToUri() const
{
return fmt::format( "spotify:{}:{}", type, id );
}
std::string SpotifyObject::ToUrl() const
{
return fmt::format( "https://open.spotify.com/{}/{}", type, id );
}
std::string SpotifyObject::ToSchema() const
{
return fmt::format( "sptf://spotify:{}:{}", type, id );
}
SpotifyFilteredTrack::SpotifyFilteredTrack( std::string_view id )
: object_( "track", id )
{
}
SpotifyFilteredTrack SpotifyFilteredTrack::Parse( std::string_view input )
{
if ( !IsValid( input, false ) )
{
throw qwr::QwrException( "Unsupported input format: {}", input );
}
SpotifyObject so( input );
assert( so.type == "track" );
return SpotifyFilteredTrack( so.id );
}
const std::string& SpotifyFilteredTrack::Id() const
{
return object_.id;
}
std::string SpotifyFilteredTrack::ToUri() const
{
return object_.ToUri();
}
std::string SpotifyFilteredTrack::ToUrl() const
{
return object_.ToUrl();
}
std::string SpotifyFilteredTrack::ToSchema() const
{
return object_.ToSchema();
}
bool SpotifyFilteredTrack::IsValid( std::string_view input, bool usePurePathOnly )
{
const auto schemaPrefix = "sptf://"sv;
if ( input._Starts_with( schemaPrefix ) )
{
input.remove_prefix( schemaPrefix.size() );
}
else if ( usePurePathOnly )
{
return false;
}
return input._Starts_with( "spotify:track:"sv );
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/spotify_object.h
================================================
#pragma once
#include <string>
namespace sptf
{
struct SpotifyObject
{
/// @throw qwr::QwrException
SpotifyObject( std::string_view input );
/// @throw qwr::QwrException
SpotifyObject( std::string_view type, std::string_view id );
std::string ToUri() const;
std::string ToUrl() const;
std::string ToSchema() const;
static bool IsValid( std::string_view input );
std::string type;
std::string id;
};
class SpotifyFilteredTrack
{
public:
/// @throw qwr::QwrException
SpotifyFilteredTrack( std::string_view id );
/// @throw qwr::QwrException
static SpotifyFilteredTrack Parse( std::string_view input );
const std::string& Id() const;
std::string ToUri() const;
std::string ToUrl() const;
std::string ToSchema() const;
static bool IsValid( std::string_view input, bool usePurePathOnly );
private:
SpotifyObject object_;
};
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_auth.cpp
================================================
#include <stdafx.h>
#include "webapi_auth.h"
#include <backend/spotify_instance.h>
#include <backend/webapi_auth_scopes.h>
#include <ui/ui_not_auth.h>
#include <utils/abort_manager.h>
#include <utils/json_std_extenders.h>
#include <bcrypt.h>
#include <component_urls.h>
#include <pplawait.h>
#include <winternl.h>
#include <cpprest/asyncrt_utils.h>
#include <cpprest/http_client.h>
#include <cpprest/http_listener.h>
#include <cpprest/json.h>
#include <cpprest/uri_builder.h>
#include <nonstd/span.hpp>
#include <qwr/file_helpers.h>
#include <qwr/final_action.h>
#include <qwr/string_helpers.h>
#include <random>
#include <string_view>
#include <unordered_set>
#include <experimental/resumable>
// TODO: move to props
#pragma comment( lib, "bcrypt.lib" )
#pragma comment( lib, "Crypt32.lib" )
#pragma comment( lib, "winhttp.lib" )
#pragma comment( lib, "httpapi.lib" )
// TODO: add abortable to cts
// TODO: clean up mutex handling - it's a mess here
namespace fs = std::filesystem;
using namespace sptf;
namespace
{
constexpr wchar_t k_clientId[] = L"30826243a65f43f19b038fd65ffaa8b5";
} // namespace
namespace
{
std::wstring GenerateCodeVerifier()
{
static constexpr wchar_t codeVerifierChars[] =
L"abcdefghijklmnopqrstuvwxyz"
L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
L"1234567890"
L"_.-~";
std::random_device rd;
const auto size = std::uniform_int_distribution<size_t>( 43, 128 )( rd );
std::wstring ret;
ret.resize( size );
std::uniform_int_distribution<size_t> charDist( 0, std::size( codeVerifierChars ) - 2 );
for ( auto& ch: ret )
{
ch = codeVerifierChars[charDist( rd )];
}
return ret;
}
std::vector<uint8_t> Sha256Hash( nonstd::span<const uint8_t> data )
{
// TODO: cleanup add error handling
NTSTATUS status;
BCRYPT_ALG_HANDLE alg_handle = nullptr;
BCRYPT_HASH_HANDLE hash_handle = nullptr;
std::vector<uint8_t> hash;
DWORD hash_len = 0;
ULONG result_len = 0;
status = BCryptOpenAlgorithmProvider( &alg_handle, BCRYPT_SHA256_ALGORITHM, nullptr, 0 );
if ( !NT_SUCCESS( status ) )
{
goto cleanup;
}
status = BCryptGetProperty( alg_handle, BCRYPT_HASH_LENGTH, (PBYTE)&hash_len, sizeof( hash_len ), &result_len, 0 );
if ( !NT_SUCCESS( status ) )
{
goto cleanup;
}
hash.resize( hash_len );
status = BCryptCreateHash( alg_handle, &hash_handle, nullptr, 0, nullptr, 0, 0 );
if ( !NT_SUCCESS( status ) )
{
goto cleanup;
}
status = BCryptHashData( hash_handle, (PBYTE)data.data(), (ULONG)data.size(), 0 );
if ( !NT_SUCCESS( status ) )
{
goto cleanup;
}
status = BCryptFinishHash( hash_handle, hash.data(), hash_len, 0 );
if ( !NT_SUCCESS( status ) )
{
goto cleanup;
}
cleanup:
if ( hash_handle )
{
BCryptDestroyHash( hash_handle );
}
if ( alg_handle )
{
BCryptCloseAlgorithmProvider( alg_handle, 0 );
}
return hash;
}
std::wstring GenerateChallengeCode( const std::wstring& codeVerifier )
{
const auto code_u8 = qwr::unicode::ToU8( codeVerifier );
const auto hash = Sha256Hash( nonstd::span<const uint8_t>( reinterpret_cast<const uint8_t*>( code_u8.data() ), code_u8.size() ) );
auto base64 = utility::conversions::to_base64( hash );
{
while ( base64[base64.size() - 1] == L'=' )
{
base64.resize( base64.size() - 1 );
}
for ( auto& ch: base64 )
{
switch ( ch )
{
case L'+':
{
ch = L'-';
break;
}
case L'/':
{
ch = L'_';
break;
}
default:
{
break;
}
}
}
}
return base64;
}
void OpenAuthConfirmationInBrowser( const std::wstring& url )
{
ShellExecuteW( nullptr, L"open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL );
}
} // namespace
namespace sptf
{
struct AuthData
{
std::wstring accessToken;
std::wstring refreshToken;
std::chrono::time_point<std::chrono::system_clock> expiresAt = std::chrono::system_clock::now();
WebApiAuthScopes scopes;
};
void to_json( nlohmann::json& j, const AuthData& p )
{
j["access_token"] = p.accessToken;
j["refresh_token"] = p.refreshToken;
j["expires_at"] = std::chrono::time_point_cast<std::chrono::minutes>( p.expiresAt ).time_since_epoch().count();
j["scopes"] = p.scopes;
}
void from_json( const nlohmann::json& j, AuthData& p )
{
j.at( "access_token" ).get_to( p.accessToken );
j.at( "refresh_token" ).get_to( p.refreshToken );
p.expiresAt = std::chrono::time_point<std::chrono::system_clock>( std::chrono::minutes( j.at( "expires_at" ).get<int>() ) );
if ( j.contains( "scopes" ) )
{
j["scopes"].get_to( p.scopes );
}
else
{ // for backwards compatibility
WebApiAuthScopes scopes;
scopes.playlist_read_collaborative = true;
scopes.playlist_read_private = true;
p.scopes = scopes;
}
}
WebApiAuthorizer::WebApiAuthorizer( const web::http::client::http_client_config& config, AbortManager& abortManager )
: abortManager_( abortManager )
, client_( url::accountsApi, config )
{
const auto authpath = path::WebApiSettings() / "auth.json";
if ( fs::exists( authpath ) )
{
try
{
const auto data = qwr::file::ReadFileW( authpath, CP_UTF8, false );
const auto dataJson = nlohmann::json::parse( data );
dataJson.get_to( pAuthData_ );
}
catch ( const std::exception& )
{
pAuthData_.reset();
try
{
fs::remove( authpath );
}
catch ( const fs::filesystem_error& )
{
}
}
}
}
WebApiAuthorizer::~WebApiAuthorizer()
{
cts_.cancel();
StopResponseListener();
}
bool WebApiAuthorizer::HasRefreshToken() const
{
std::lock_guard lock( accessTokenMutex_ );
return !!pAuthData_;
}
const std::wstring WebApiAuthorizer::GetAccessToken( abort_callback& abort )
{
std::lock_guard lock( accessTokenMutex_ );
if ( !pAuthData_ )
{
::fb2k::inMainThread( [&] {
playback_control::get()->stop();
ui::NotAuthHandler::Get().ShowDialog();
} );
throw qwr::QwrException( "Failed to get authenticated Spotify session" );
}
UpdateRefreshToken_NonBlocking( abort );
assert( !pAuthData_->accessToken.empty() );
return pAuthData_->accessToken;
}
void WebApiAuthorizer::ClearAuth()
{
pAuthData_.reset();
codeVerifier_.clear();
state_.clear();
const auto authpath = path::WebApiSettings() / "auth.json";
if ( fs::exists( authpath ) )
{
fs::remove( authpath );
}
}
void WebApiAuthorizer::CancelAuth()
{
// TODO: rethink cts handling: current scheme is not thread-safe.
// Example: CancelAuth from preferences (and reassign cts_), while some other thread is using cts_.
cts_.cancel();
cts_ = pplx::cancellation_token_source();
StopResponseListener();
ClearAuth();
}
void WebApiAuthorizer::AuthenticateClean( const WebApiAuthScopes& scopes, std::function<void()> onResponseEnd )
{
assert( core_api::is_main_thread() );
StopResponseListener();
StartResponseListener( onResponseEnd );
// <https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce>
codeVerifier_ = GenerateCodeVerifier();
web::uri_builder builder( url::accountsAuthenticate );
builder
.append_query( L"client_id", web::uri::encode_data_string( k_clientId ), false )
.append_query( L"response_type", L"code" )
.append_query( L"redirect_uri", web::uri::encode_data_string( url::redirectUri ), false )
.append_query( L"code_challenge_method", L"S256" )
.append_query( L"code_challenge", GenerateChallengeCode( codeVerifier_ ) )
.append_query( L"scope", web::uri::encode_data_string( scopes.ToWebString() ), false );
// TODO: cookie?
// builder.append_query( L"state", L"azaza" );
OpenAuthConfirmationInBrowser( builder.to_string() );
}
void WebApiAuthorizer::AuthenticateClean_Cleanup()
{
StopResponseListener();
}
void WebApiAuthorizer::UpdateRefreshToken( abort_callback& abort )
{
std::lock_guard lock( accessTokenMutex_ );
UpdateRefreshToken_NonBlocking( abort );
}
void WebApiAuthorizer::UpdateRefreshToken_NonBlocking( abort_callback& abort )
{
assert( pAuthData_ );
if ( std::chrono::system_clock::now() + std::chrono::minutes( 1 ) < pAuthData_->expiresAt )
{
return;
}
web::uri_builder builder;
builder
.append_query( L"grant_type", L"refresh_token" )
.append_query( L"refresh_token", pAuthData_->refreshToken )
.append_query( L"client_id", web::uri::encode_data_string( k_clientId ), false );
web::http::http_request req( web::http::methods::POST );
req.headers().set_content_type( L"application/x-www-form-urlencoded" );
req.set_request_uri( builder.to_uri() );
auto ctsToken = cts_.get_token();
auto localCts = Concurrency::cancellation_token_source::create_linked_source( ctsToken );
const auto abortableScope = abortManager_.GetAbortableScope( [&localCts] { localCts.cancel(); }, abort );
auto response = client_.request( req, cts_.get_token() );
HandleAuthenticationResponse( response.get() );
}
pplx::task<void> WebApiAuthorizer::CompleteAuthentication( const std::wstring& responseUrl )
{
// TODO: final_action
web::uri uri( responseUrl );
// TODO: check redirectUri?
const auto data = web::uri::split_query( uri.query() );
if ( data.count( L"error" ) )
{
throw qwr::QwrException( qwr::unicode::ToU8( fmt::format( L"Authentication failed: {}", data.at( L"error" ) ) ) );
}
if ( !state_.empty() && ( !data.count( L"state" ) || data.at( L"state" ) != state_ ) )
{
throw qwr::QwrException( "Malformed authentication response: `state` mismatch" );
}
qwr::QwrException::ExpectTrue( data.count( L"code" ), L"Malformed authentication response: missing `code`" );
web::uri_builder builder;
builder
.append_query( L"client_id", k_clientId )
.append_query( L"grant_type", L"authorization_code" )
.append_query( L"code", data.at( L"code" ) )
.append_query( L"redirect_uri", web::uri::encode_data_string( url::redirectUri ), false )
.append_query( L"code_verifier", codeVerifier_, false );
web::http::http_request req( web::http::methods::POST );
req.headers().set_content_type( L"application/x-www-form-urlencoded" );
req.set_body( builder.query() );
const auto response = co_await client_.request( req, cts_.get_token() );
HandleAuthenticationResponse( response );
}
void WebApiAuthorizer::StartResponseListener( std::function<void()> onResponseEnd )
{
assert( !pListener_ );
pListener_ = std::make_unique<web::http::experimental::listener::http_listener>( url::redirectUri );
pListener_->support( [this, onResponseEnd]( const auto& request ) {
try
{
if ( request.request_uri().path() == L"/" && request.request_uri().query() != L"" )
{
const auto callbackCaller = qwr::final_action( [&] { onResponseEnd(); } );
try
{
CompleteAuthentication( request.request_uri().to_string() ).wait();
web::http::http_response res;
res.set_status_code( web::http::status_codes::OK );
res.headers().set_content_type( L"text/html; charset=utf-8" );
res.set_body(
L"<html>\n"
L"<head>\n"
L"</head>\n"
L"<body onload=\" waitFiveSec() \">\n"
L"<p><b>foo_spotify</b> was successfully authenticated!</p>\n "
L"<p>You can close this tab now :)</p>\n "
L"</body>\n"
L"</html>" );
request.reply( res ).wait();
}
catch ( const std::exception& e )
{
pAuthData_.reset();
auto errorMsg = qwr::unicode::ToWide( std::string( e.what() ) );
{
size_t pos = 0;
while ( ( pos = errorMsg.find( L"\n", pos ) ) != std::string::npos )
{
errorMsg.replace( pos, 1, L"<br>" );
pos += 4;
}
}
web::http::http_response res;
res.set_status_code( web::http::status_codes::OK );
res.headers().set_content_type( L"text/html; charset=utf-8" );
res.set_body(
L"<html>\n"
L"<head>\n"
L"</head>\n"
L"<p><b>foo_spotify</b> failed to authenticate!</p>\n"
L"<p>Error:</p>\n"
L"<p>"
+ errorMsg + "</p>\n"
L"</body>\n"
L"</html>" );
request.reply( res ).wait();
}
}
else
{
web::http::http_response res;
res.set_status_code( web::http::status_codes::OK );
res.headers().set_content_type( L"text/html; charset=utf-8" );
res.set_body( L"<html><body><p>Congratulations! You found a useless page! o/</p></body></html>" );
request.reply( res ).wait();
}
}
catch ( ... )
{
}
} );
pListener_->open().wait();
}
void WebApiAuthorizer::StopResponseListener()
{
if ( pListener_ )
{
pListener_->close().wait();
pListener_.reset();
}
}
void WebApiAuthorizer::HandleAuthenticationResponse( const web::http::http_response& response )
{
if ( response.status_code() != 200 )
{
ClearAuth();
throw qwr::QwrException( L"{}: {}\n"
L"Additional data: {}\n",
(int)response.status_code(),
response.reason_phrase(),
[&]() -> std::wstring {
try
{
const auto responseJson = nlohmann::json::parse( response.extract_string().get() );
return qwr::unicode::ToWide( responseJson.dump( 2 ) );
}
catch ( ... )
{
return response.to_string();
}
}() );
}
const auto responseJson = response.extract_json().get();
qwr::QwrException::ExpectTrue( responseJson.is_object(),
L"Malformed authentication response: json is not an object" );
qwr::QwrException::ExpectTrue( responseJson.at( L"token_type" ).as_string() == L"Bearer",
L"Malformed authentication response: invalid `token_type`: {}",
responseJson.at( L"token_type" ).as_string() );
auto pAuthData = std::make_unique<AuthData>();
pAuthData->accessToken = responseJson.at( L"access_token" ).as_string();
pAuthData->refreshToken = responseJson.at( L"refresh_token" ).as_string();
pAuthData->expiresAt = std::chrono::system_clock::now() + std::chrono::seconds( responseJson.at( L"expires_in" ).as_integer() );
auto scopesSplit = qwr::string::Split<wchar_t>( responseJson.at( L"scope" ).as_string(), L' ' );
pAuthData->scopes = WebApiAuthScopes( scopesSplit );
const auto settingsPath = path::WebApiSettings();
if ( !fs::exists( settingsPath ) )
{
fs::create_directories( settingsPath );
}
// not using component config because it's not saved immediately
qwr::file::WriteFile( settingsPath / "auth.json", nlohmann::json( pAuthData ).dump( 2 ) );
pAuthData_ = std::move( pAuthData );
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_auth.h
================================================
#pragma once
#include <cpprest/asyncrt_utils.h>
#include <cpprest/http_client.h>
#include <cpprest/http_msg.h>
#include <chrono>
namespace web::http::experimental::listener
{
class http_listener;
}
namespace sptf
{
class AbortManager;
struct WebApiAuthScopes;
struct AuthData;
class WebApiAuthorizer
{
public:
WebApiAuthorizer( const web::http::client::http_client_config& config, AbortManager& abortManager );
~WebApiAuthorizer();
bool HasRefreshToken() const;
const std::wstring GetAccessToken( abort_callback& abort );
void ClearAuth();
void CancelAuth();
void AuthenticateClean( const WebApiAuthScopes& scopes, std::function<void()> onResponseEnd );
void AuthenticateClean_Cleanup();
void UpdateRefreshToken( abort_callback& abort );
private:
void UpdateRefreshToken_NonBlocking( abort_callback& abort );
pplx::task<void> CompleteAuthentication( const std::wstring& respondUrl );
void StartResponseListener( std::function<void()> onResponseEnd );
void StopResponseListener();
void HandleAuthenticationResponse( const web::http::http_response& response );
private:
AbortManager& abortManager_;
pplx::cancellation_token_source cts_;
web::http::client::http_client client_;
std::unique_ptr<web::http::experimental::listener::http_listener> pListener_;
std::wstring codeVerifier_;
std::wstring state_;
mutable std::mutex accessTokenMutex_;
std::unique_ptr<AuthData> pAuthData_;
};
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_auth_scopes.cpp
================================================
#include <stdafx.h>
#include "webapi_auth_scopes.h"
#include <unordered_set>
using namespace std::literals::string_view_literals;
namespace sptf
{
WebApiAuthScopes::WebApiAuthScopes( nonstd::span<const std::wstring_view> scopes )
{
std::unordered_set<std::wstring_view> scopeSet( scopes.cbegin(), scopes.cend() );
#define SPTF_FILL_SCOPE( scope_var, scope_id ) \
if ( scopeSet.count( scope_id ) ) \
{ \
scope_var = true; \
}
SPTF_FILL_SCOPE( playlist_modify_public, L"playlist-modify-public"sv )
SPTF_FILL_SCOPE( playlist_read_private, L"playlist-read-private"sv )
SPTF_FILL_SCOPE( playlist_modify_private, L"playlist-modify-private"sv )
SPTF_FILL_SCOPE( playlist_read_collaborative, L"playlist-read-collaborative"sv )
SPTF_FILL_SCOPE( user_read_playback_position, L"user-read-playback-position"sv )
SPTF_FILL_SCOPE( user_read_recently_played, L"user-read-recently-played"sv )
SPTF_FILL_SCOPE( user_top_read, L"user-top-read"sv )
SPTF_FILL_SCOPE( user_follow_modify, L"user-follow-modify"sv )
SPTF_FILL_SCOPE( user_follow_read, L"user-follow-read"sv )
SPTF_FILL_SCOPE( user_library_read, L"user-library-read"sv )
SPTF_FILL_SCOPE( user_library_modify, L"user-library-modify"sv )
SPTF_FILL_SCOPE( user_read_private, L"user-read-private"sv )
SPTF_FILL_SCOPE( user_read_email, L"user-read-email"sv )
#undef SPTF_FILL_SCOPE
}
std::wstring WebApiAuthScopes::ToWebString() const
{
std::wstring ret;
#define SPTF_FILL_SCOPE( scope_var, scope_id ) \
if ( scope_var ) \
{ \
ret += scope_id; \
ret += L" "; \
}
SPTF_FILL_SCOPE( playlist_modify_public, L"playlist-modify-public"sv )
SPTF_FILL_SCOPE( playlist_read_private, L"playlist-read-private"sv )
SPTF_FILL_SCOPE( playlist_modify_private, L"playlist-modify-private"sv )
SPTF_FILL_SCOPE( playlist_read_collaborative, L"playlist-read-collaborative"sv )
SPTF_FILL_SCOPE( user_read_playback_position, L"user-read-playback-position"sv )
SPTF_FILL_SCOPE( user_read_recently_played, L"user-read-recently-played"sv )
SPTF_FILL_SCOPE( user_top_read, L"user-top-read"sv )
SPTF_FILL_SCOPE( user_follow_modify, L"user-follow-modify"sv )
SPTF_FILL_SCOPE( user_follow_read, L"user-follow-read"sv )
SPTF_FILL_SCOPE( user_library_read, L"user-library-read"sv )
SPTF_FILL_SCOPE( user_library_modify, L"user-library-modify"sv )
SPTF_FILL_SCOPE( user_read_private, L"user-read-private"sv )
SPTF_FILL_SCOPE( user_read_email, L"user-read-email"sv )
#undef SPTF_FILL_SCOPE
return ret;
}
void to_json( nlohmann::json& j, const WebApiAuthScopes& p )
{
#define SPTF_FILL_SCOPE( scope_var ) \
if ( p.scope_var ) \
{ \
j[#scope_var] = p.scope_var; \
}
SPTF_FILL_SCOPE( playlist_modify_public )
SPTF_FILL_SCOPE( playlist_read_private )
SPTF_FILL_SCOPE( playlist_modify_private )
SPTF_FILL_SCOPE( playlist_read_collaborative )
SPTF_FILL_SCOPE( user_read_playback_position )
SPTF_FILL_SCOPE( user_read_recently_played )
SPTF_FILL_SCOPE( user_top_read )
SPTF_FILL_SCOPE( user_follow_modify )
SPTF_FILL_SCOPE( user_follow_read )
SPTF_FILL_SCOPE( user_library_read )
SPTF_FILL_SCOPE( user_library_modify )
SPTF_FILL_SCOPE( user_read_private )
SPTF_FILL_SCOPE( user_read_email )
#undef SPTF_FILL_SCOPE
}
void from_json( const nlohmann::json& j, WebApiAuthScopes& p )
{
#define SPTF_FILL_SCOPE( scope_var ) \
if ( j.contains( #scope_var ) ) \
{ \
j[#scope_var].get_to( p.scope_var ); \
}
SPTF_FILL_SCOPE( playlist_modify_public )
SPTF_FILL_SCOPE( playlist_read_private )
SPTF_FILL_SCOPE( playlist_modify_private )
SPTF_FILL_SCOPE( playlist_read_collaborative )
SPTF_FILL_SCOPE( user_read_playback_position )
SPTF_FILL_SCOPE( user_read_recently_played )
SPTF_FILL_SCOPE( user_top_read )
SPTF_FILL_SCOPE( user_follow_modify )
SPTF_FILL_SCOPE( user_follow_read )
SPTF_FILL_SCOPE( user_library_read )
SPTF_FILL_SCOPE( user_library_modify )
SPTF_FILL_SCOPE( user_read_private )
SPTF_FILL_SCOPE( user_read_email )
#undef SPTF_FILL_SCOPE
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_auth_scopes.h
================================================
#pragma once
#include <nonstd/span.hpp>
#include <string_view>
namespace sptf
{
struct WebApiAuthScopes
{
WebApiAuthScopes() = default;
WebApiAuthScopes( nonstd::span<const std::wstring_view> scopes );
std::wstring ToWebString() const;
bool playlist_modify_public = false;
bool playlist_read_private = false;
bool playlist_modify_private = false;
bool playlist_read_collaborative = false;
bool user_read_playback_position = false;
bool user_read_recently_played = false;
bool user_top_read = false;
bool user_follow_modify = false;
bool user_follow_read = false;
bool user_library_read = false;
bool user_library_modify = false;
bool user_read_private = false;
bool user_read_email = false;
};
void to_json( nlohmann::json& j, const WebApiAuthScopes& p );
void from_json( const nlohmann::json& j, WebApiAuthScopes& p );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_backend.cpp
================================================
#include <stdafx.h>
#include "webapi_backend.h"
#include <backend/webapi_auth.h>
#include <backend/webapi_objects/webapi_media_objects.h>
#include <backend/webapi_objects/webapi_paging_object.h>
#include <backend/webapi_objects/webapi_user.h>
#include <fb2k/advanced_config.h>
#include <utils/abort_manager.h>
#include <utils/json_std_extenders.h>
#include <utils/sleeper.h>
#include <component_urls.h>
#include <qwr/fb2k_adv_config.h>
#include <qwr/file_helpers.h>
#include <qwr/final_action.h>
#include <qwr/string_helpers.h>
#include <qwr/type_traits.h>
#include <qwr/winapi_error_helpers.h>
#include <filesystem>
#include <unordered_set>
// TODO: replace unique_ptr with shared_ptr wherever needed to avoid copying
namespace fs = std::filesystem;
namespace
{
constexpr size_t kRpsLimit = 2;
}
namespace sptf
{
WebApi_Backend::WebApi_Backend( AbortManager& abortManager )
: abortManager_( abortManager )
, shouldLogWebApiRequest_( config::advanced::logging_webapi_request )
, shouldLogWebApiResponse_( config::advanced::logging_webapi_response )
, rpsLimiter_( kRpsLimit )
, client_( url::spotifyApi, GetClientConfig() )
, trackCache_( "tracks" )
, artistCache_( "artists" )
, albumImageCache_( "albums" )
, artistImageCache_( "artists" )
, pAuth_( std::make_unique<WebApiAuthorizer>( GetClientConfig(), abortManager ) )
{
}
WebApi_Backend::~WebApi_Backend()
{
}
void WebApi_Backend::Finalize()
{
cts_.cancel();
pAuth_.reset();
}
WebApiAuthorizer& WebApi_Backend::GetAuthorizer()
{
return *pAuth_;
}
std::unique_ptr<const sptf::WebApi_User> WebApi_Backend::GetUser( abort_callback& abort )
{
if ( auto userOpt = userCache_.GetObjectFromCache();
userOpt )
{
return std::unique_ptr<sptf::WebApi_User>( std::move( *userOpt ) );
}
else
{
web::uri_builder builder;
builder.append_path( L"me" );
const auto responseJson = GetJsonResponse( builder.to_uri(), abort );
auto ret = responseJson.get<std::unique_ptr<WebApi_User>>();
userCache_.CacheObject( *ret );
return std::unique_ptr<sptf::WebApi_User>( std::move( ret ) );
}
}
void WebApi_Backend::RefreshCacheForTracks( nonstd::span<const std::string> trackIds, abort_callback& abort )
{
constexpr size_t kMaxItemsPerRequest = 50;
// remove duplicates
const auto uniqueIds = trackIds | ranges::to<std::unordered_set<std::string>>;
for ( const auto& trackIdsChunk:
uniqueIds
| ranges::views::remove_if( [&]( const auto& id ) { return trackCache_.IsCached( id ); } )
| ranges::views::chunk( kMaxItemsPerRequest ) )
{
const auto trackIdsStr = qwr::unicode::ToWide( qwr::string::Join( trackIdsChunk | ranges::to_vector, ',' ) );
web::uri_builder builder;
builder
.append_path( L"tracks" )
.append_query( L"ids", trackIdsStr );
const auto responseJson = GetJsonResponse( builder.to_uri(), abort );
const auto tracksIt = responseJson.find( "tracks" );
qwr::QwrException::ExpectTrue( responseJson.cend() != tracksIt,
L"Malformed track data response response: missing `tracks`" );
auto ret = tracksIt->get<std::vector<std::unique_ptr<const WebApi_Track>>>();
trackCache_.CacheObjects( ret );
}
}
std::unique_ptr<const sptf::WebApi_Track>
WebApi_Backend::GetTrack( const std::string& trackId, abort_callback& abort, bool useRelink )
{
// don't want to cache relinked tracks
if ( auto trackOpt = trackCache_.GetObjectFromCache( trackId );
!useRelink && trackOpt )
{
return std::unique_ptr<sptf::WebApi_Track>( std::move( *trackOpt ) );
}
else
{
web::uri_builder builder;
builder
.append_path( L"tracks" )
.append_path( qwr::unicode::ToWide( trackId ) );
if ( useRelink )
{
if ( const auto countryOpt = GetUser( abort )->country;
countryOpt )
{
builder.append_query( L"market", qwr::unicode::ToWide( *countryOpt ) );
}
}
const auto responseJson = GetJsonResponse( builder.to_uri(), abort );
auto ret = responseJson.get<std::unique_ptr<WebApi_Track>>();
if ( !useRelink )
{
trackCache_.CacheObject( *ret );
}
return std::unique_ptr<sptf::WebApi_Track>( std::move( ret ) );
}
}
std::vector<std::unique_ptr<const WebApi_Track>>
WebApi_Backend::GetTracks( nonstd::span<const std::string> trackIds, abort_callback& abort )
{
RefreshCacheForTracks( trackIds, abort );
return trackIds | ranges::views::transform( [&]( const auto& id ) -> std::unique_ptr<const WebApi_Track> {
assert( trackCache_.IsCached( id ) );
return std::move( *trackCache_.GetObjectFromCache( id ) );
} )
| ranges::to_vector;
}
std::tuple<
std::vector<std::unique_ptr<const WebApi_Track>>,
std::vector<std::unique_ptr<const WebApi_LocalTrack>>>
WebApi_Backend::GetTracksFromPlaylist( const std::string& playlistId, abort_callback& abort )
{
constexpr size_t kMaxItemsPerRequest = 100;
auto requestUri = [&] {
web::uri_builder builder;
builder
.append_path( fmt::format( L"playlists/{}/tracks", qwr::unicode::ToWide( playlistId ) ) )
.append_query( L"limit", kMaxItemsPerRequest, false );
return builder.to_uri();
}();
std::vector<std::unique_ptr<const WebApi_Track>> tracks;
std::vector<std::unique_ptr<const WebApi_LocalTrack>> localTracks;
while ( true )
{
const auto responseJson = GetJsonResponse( requestUri, abort );
const auto pPagingObject = responseJson.get<std::unique_ptr<const WebApi_PagingObject>>();
auto playlistTracks = pPagingObject->items.get<std::vector<std::unique_ptr<WebApi_PlaylistTrack>>>();
for ( auto& playlistTrack: playlistTracks )
{
std::visit( [&]( auto&& arg ) {
using T = std::decay_t<decltype( arg )>;
if constexpr ( std::is_same_v<T, WebApi_Track> )
{
tracks.emplace_back( std::make_unique<T>( std::move( arg ) ) );
}
else if constexpr ( std::is_same_v<T, WebApi_LocalTrack> )
{
localTracks.emplace_back( std::make_unique<T>( std::move( arg ) ) );
}
else
{
static_assert( qwr::always_false_v<T>, "non-exhaustive visitor!" );
}
},
*playlistTrack->track );
}
if ( !pPagingObject->next )
{
break;
}
requestUri = *pPagingObject->next;
}
trackCache_.CacheObjects( tracks );
return { std::move( tracks ), std::move( localTracks ) };
}
std::vector<std::unique_ptr<const sptf::WebApi_Track>>
WebApi_Backend::GetTracksFromAlbum( const std::string& albumId, abort_callback& abort )
{
std::shared_ptr<WebApi_Album_Simplified> album;
web::uri requestUri;
std::vector<std::unique_ptr<WebApi_Track_Simplified>> ret;
while ( true )
{
const auto responseJson = [&] {
if ( requestUri.is_empty() )
{ // first paging object (and uri) is retrieved from album
web::uri_builder builder;
builder.append_path( fmt::format( L"albums/{}", qwr::unicode::ToWide( albumId ) ) );
const auto responseJson = GetJsonResponse( builder.to_uri(), abort );
responseJson.get_to( album );
const auto tracksIt = responseJson.find( "tracks" );
qwr::QwrException::ExpectTrue( responseJson.cend() != tracksIt,
L"Malformed track data response: missing `tracks`" );
return *tracksIt;
}
else
{
return GetJsonResponse( requestUri, abort );
}
}();
const auto pPagingObject = responseJson.get<std::unique_ptr<const WebApi_PagingObject>>();
auto newData = pPagingObject->items.get<std::vector<std::unique_ptr<WebApi_Track_Simplified>>>();
ret.insert( ret.end(), make_move_iterator( newData.begin() ), make_move_iterator( newData.end() ) );
if ( !pPagingObject->next )
{
break;
}
requestUri = *pPagingObject->next;
}
auto newRet = ranges::views::transform( ret, [&]( auto&& elem ) {
return std::make_unique<const WebApi_Track>( std::move( elem ), album );
} )
| ranges::to_vector;
trackCache_.CacheObjects( newRet );
return newRet;
}
std::vector<std::unique_ptr<const WebApi_Track>>
WebApi_Backend::GetTopTracksForArtist( const std::string& artistId, abort_callback& abort )
{
const auto countryOpt = GetUser( abort )->country;
qwr::QwrException::ExpectTrue( countryOpt.has_value(),
"Adding artist top tracks requires `user-read-private` permission.\n"
"Re-login to update your permission scope." );
web::uri_builder builder;
builder
.append_path( L"artists" )
.append_path( qwr::unicode::ToWide( artistId ) )
.append_path( L"top-tracks" )
.append_query( L"market", qwr::unicode::ToWide( *countryOpt ) );
const auto responseJson = GetJsonResponse( builder.to_uri(), abort );
const auto tracksIt = responseJson.find( "tracks" );
qwr::QwrException::ExpectTrue( responseJson.cend() != tracksIt,
L"Malformed track data response response: missing `tracks`" );
auto ret = tracksIt->get<std::vector<std::unique_ptr<const WebApi_Track>>>();
trackCache_.CacheObjects( ret );
return ret;
}
std::vector<std::unordered_multimap<std::string, std::string>>
WebApi_Backend::GetMetaForTracks( nonstd::span<const std::unique_ptr<const WebApi_Track>> tracks )
{
std::vector<std::unordered_multimap<std::string, std::string>> ret;
for ( const auto& track: tracks )
{
auto& curMap = ret.emplace_back();
// This length will be overriden during playback
curMap.emplace( "SPTF_LENGTH", fmt::format( "{}", track->duration_ms ) );
curMap.emplace( "TITLE", track->name );
curMap.emplace( "TRACKNUMBER", fmt::format( "{}", track->track_number ) );
curMap.emplace( "DISCNUMBER", fmt::format( "{}", track->disc_number ) );
for ( const auto& artist: track->artists )
{
curMap.emplace( "ARTIST", artist->name );
}
const auto& album = track->album;
curMap.emplace( "ALBUM", album->name );
curMap.emplace( "DATE", album->release_date );
for ( const auto& artist: album->artists )
{
curMap.emplace( "ALBUM ARTIST", artist->name );
}
}
// TODO: cache data (<https://fy.3dyd.com/help/titleformatting/#Metadata_length_limitations> might be useful)
// TODO: check webapi_objects for other fields
return ret;
}
void WebApi_Backend::RefreshCacheForArtists( nonstd::span<const std::string> artistIds, abort_callback& abort )
{
// remove duplicates
const auto uniqueIds = artistIds | ranges::to<std::unordered_set<std::string>>;
for ( const auto& idsChunk:
uniqueIds
| ranges::views::remove_if( [&]( const auto& id ) { return artistCache_.IsCached( id ); } )
| ranges::views::chunk( 50 ) )
{
const auto idsStr = qwr::unicode::ToWide( qwr::string::Join( idsChunk | ranges::to_vector, ',' ) );
web::uri_builder builder;
builder
.append_path( L"artists" )
.append_query( L"ids", idsStr );
const auto responseJson = GetJsonResponse( builder.to_uri(), abort );
const auto artistsIt = responseJson.find( "artists" );
qwr::QwrException::ExpectTrue( responseJson.cend() != artistsIt,
L"Malformed track data response response: missing `artists`" );
auto ret = artistsIt->get<std::vector<std::unique_ptr<const WebApi_Artist>>>();
artistCache_.CacheObjects( ret );
}
}
std::unique_ptr<const WebApi_Artist>
WebApi_Backend::GetArtist( const std::string& artistId, abort_callback& abort )
{
if ( auto objectOpt = artistCache_.GetObjectFromCache( artistId );
objectOpt )
{
return std::unique_ptr<const WebApi_Artist>( std::move( *objectOpt ) );
}
else
{
web::uri_builder builder;
builder
.append_path( L"artists" )
.append_path( qwr::unicode::ToWide( artistId ) );
const auto responseJson = GetJsonResponse( builder.to_uri(), abort );
auto ret = responseJson.get<std::unique_ptr<const WebApi_Artist>>();
artistCache_.CacheObject( *ret );
return std::unique_ptr<const WebApi_Artist>( std::move( ret ) );
}
}
fs::path WebApi_Backend::GetAlbumImage( const std::string& albumId, const std::string& imgUrl, abort_callback& abort )
{
return albumImageCache_.GetImage( albumId, imgUrl, abort );
}
fs::path WebApi_Backend::GetArtistImage( const std::string& artistId, const std::string& imgUrl, abort_callback& abort )
{
return artistImageCache_.GetImage( artistId, imgUrl, abort );
}
web::http::client::http_client_config WebApi_Backend::GetClientConfig()
{
const auto proxyUrl = qwr::unicode::ToWide( sptf::config::advanced::network_proxy.GetValue() );
const auto proxyUsername = qwr::unicode::ToWide( sptf::config::advanced::network_proxy_username.GetValue() );
const auto proxyPassword = qwr::unicode::ToWide( sptf::config::advanced::network_proxy_password.GetValue() );
web::http::client::http_client_config config;
if ( !proxyUrl.empty() )
{
web::web_proxy proxy( web::uri::encode_uri( proxyUrl ) );
if ( !proxyUsername.empty() && !proxyPassword.empty() )
{
proxy.set_credentials( web::credentials{ proxyUsername, proxyPassword } );
}
config.set_proxy( std::move( proxy ) );
}
return config;
}
nlohmann::json WebApi_Backend::GetJsonResponse( const web::uri& requestUri, abort_callback& abort )
{
return ParseResponse( GetResponse( requestUri, abort ) );
}
web::http::http_response WebApi_Backend::GetResponse( const web::uri& requestUri, abort_callback& abort )
{
const auto adjustedRequestUri = [&] {
const auto uriStr = requestUri.to_string();
const auto baseUriStr = client_.base_uri().to_string();
if ( uriStr._Starts_with( baseUriStr ) )
{
return web::uri{ uriStr.data() + baseUriStr.size() };
}
else
{
return requestUri;
}
}();
if ( shouldLogWebApiRequest_ )
{
FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (debug): request:\n"
<< qwr::unicode::ToU8( adjustedRequestUri.to_string() );
}
web::http::http_request req( web::http::methods::GET );
req.headers().add( L"Authorization", fmt::format( L"Bearer {}", pAuth_->GetAccessToken( abort ) ) );
req.headers().add( L"Accept", L"application/json" );
req.headers().set_content_type( L"application/json" );
req.set_request_uri( adjustedRequestUri );
rpsLimiter_.WaitForRequestAvailability( abort );
qwr::QwrException::ExpectTrue( !abort.is_aborting(), "Abort was signaled, canceling request..." );
auto ctsToken = cts_.get_token();
auto localCts = Concurrency::cancellation_token_source::create_linked_source( ctsToken );
const auto abortableScope = abortManager_.GetAbortableScope( [&localCts] { localCts.cancel(); }, abort );
web::http::http_response response;
for ( size_t i = 0; i < 3; ++i )
{
response = client_.request( req, localCts.get_token() ).get();
if ( response.status_code() != 429 )
{
break;
}
const auto it = response.headers().find( L"Retry-After" );
qwr::QwrException::ExpectTrue( it != response.headers().end(), "Request failed with 429 error, but does not contain a `Retry-After` header" );
const auto& [_, retryHeader] = *it;
const auto retryInMsOpt = qwr::string::GetNumber<uint32_t>( qwr::unicode::ToU8( retryHeader ) );
qwr::QwrException::ExpectTrue( retryInMsOpt.has_value(), "Request failed with 429 error, but does not contain a valid number in `Retry-After` header" );
const auto retryIn = std::chrono::milliseconds( *retryInMsOpt ) + std::chrono::seconds( 1 );
FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (error):\n"
<< fmt::format( L"Rate limit reached: retrying in {} ms", retryIn.count() );
if ( !SleepFor( retryIn, abort ) )
{
break;
}
}
if ( response.status_code() == 429 )
{
FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (error):\n"
<< fmt::format( L"Rate limit reached: retry failed" );
}
return response;
}
nlohmann::json WebApi_Backend::ParseResponse( const web::http::http_response& response )
{
if ( response.status_code() != 200 )
{
throw qwr::QwrException( L"{}: {}\n"
L"Additional data: {}\n",
(int)response.status_code(),
response.reason_phrase(),
[&]() -> std::wstring {
try
{
const auto responseJson = nlohmann::json::parse( response.extract_string().get() );
return qwr::unicode::ToWide( responseJson.dump( 2 ) );
}
catch ( ... )
{
return response.to_string();
}
}() );
}
const auto responseJson = nlohmann::json::parse( response.extract_string().get() );
if ( shouldLogWebApiResponse_ )
{
FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (debug): response:\n"
<< responseJson.dump( 2 );
}
qwr::QwrException::ExpectTrue( responseJson.is_object(),
L"Malformed track data response response: json is not an object" );
return responseJson;
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_backend.h
================================================
#pragma once
#include <backend/webapi_cache.h>
#include <utils/rps_limiter.h>
#include <cpprest/http_client.h>
#include <nonstd/span.hpp>
#include <filesystem>
#include <tuple>
#include <unordered_map>
#include <vector>
namespace sptf
{
struct WebApi_User;
struct WebApi_Track;
struct WebApi_LocalTrack;
struct WebApi_Artist;
class WebApiAuthorizer;
class AbortManager;
class WebApi_Backend
{
public:
WebApi_Backend( AbortManager& abortManager );
~WebApi_Backend();
void Finalize();
WebApiAuthorizer& GetAuthorizer();
std::unique_ptr<const sptf::WebApi_User> GetUser( abort_callback& abort );
void RefreshCacheForTracks( nonstd::span<const std::string> trackIds, abort_callback& abort );
std::unique_ptr<const WebApi_Track>
GetTrack( const std::string& trackId, abort_callback& abort, bool useRelink = false );
std::vector<std::unique_ptr<const WebApi_Track>>
GetTracks( nonstd::span<const std::string> trackIds, abort_callback& abort );
std::tuple<
std::vector<std::unique_ptr<const WebApi_Track>>,
std::vector<std::unique_ptr<const WebApi_LocalTrack>>>
GetTracksFromPlaylist( const std::string& playlistId, abort_callback& abort );
std::vector<std::unique_ptr<const WebApi_Track>>
GetTracksFromAlbum( const std::string& albumId, abort_callback& abort );
std::vector<std::unique_ptr<const WebApi_Track>>
GetTopTracksForArtist( const std::string& artistId, abort_callback& abort );
std::vector<std::unordered_multimap<std::string, std::string>>
GetMetaForTracks( nonstd::span<const std::unique_ptr<const WebApi_Track>> tracks );
void RefreshCacheForArtists( nonstd::span<const std::string> artistIds, abort_callback& abort );
std::unique_ptr<const WebApi_Artist>
GetArtist( const std::string& artistId, abort_callback& abort );
std::filesystem::path GetAlbumImage( const std::string& albumId, const std::string& imgUrl, abort_callback& abort );
std::filesystem::path GetArtistImage( const std::string& artistId, const std::string& imgUrl, abort_callback& abort );
private:
static web::http::client::http_client_config GetClientConfig();
nlohmann::json GetJsonResponse( const web::uri& requestUri, abort_callback& abort );
web::http::http_response GetResponse( const web::uri& requestUri, abort_callback& abort );
nlohmann::json ParseResponse( const web::http::http_response& response );
private:
AbortManager& abortManager_;
RpsLimiter rpsLimiter_;
bool shouldLogWebApiRequest_ = false;
bool shouldLogWebApiResponse_ = false;
pplx::cancellation_token_source cts_;
std::unique_ptr<WebApiAuthorizer> pAuth_;
web::http::client::http_client client_;
WebApi_UserCache userCache_;
WebApi_ObjectCache<WebApi_Track> trackCache_;
WebApi_ObjectCache<WebApi_Artist> artistCache_;
WebApi_ImageCache albumImageCache_;
WebApi_ImageCache artistImageCache_;
};
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_cache.cpp
================================================
#include <stdafx.h>
#include "webapi_cache.h"
#include <backend/spotify_instance.h>
#include <backend/webapi_objects/webapi_user.h>
#include <utils/abort_manager.h>
#include <utils/json_std_extenders.h>
#include <qwr/winapi_error_helpers.h>
// RODO: move to props
#pragma comment( lib, "Urlmon.lib" )
namespace fs = std::filesystem;
namespace
{
using namespace sptf;
class DownloadStatus : public IBindStatusCallback
{
public:
DownloadStatus( abort_callback& abort );
public:
STDMETHOD( OnStartBinding )(
/* [in] */ DWORD dwReserved,
/* [in] */ IBinding __RPC_FAR* pib )
{
return E_NOTIMPL;
}
STDMETHOD( GetPriority )(
/* [out] */ LONG __RPC_FAR* pnPriority )
{
return E_NOTIMPL;
}
STDMETHOD( OnLowResource )(
/* [in] */ DWORD reserved )
{
return E_NOTIMPL;
}
STDMETHOD( OnProgress )(
/* [in] */ ULONG ulProgress,
/* [in] */ ULONG ulProgressMax,
/* [in] */ ULONG ulStatusCode,
/* [in] */ LPCWSTR wszStatusText );
STDMETHOD( OnStopBinding )(
/* [in] */ HRESULT hresult,
/* [unique][in] */ LPCWSTR szError )
{
return E_NOTIMPL;
}
STDMETHOD( GetBindInfo )(
/* [out] */ DWORD __RPC_FAR* grfBINDF,
/* [unique][out][in] */ BINDINFO __RPC_FAR* pbindinfo )
{
return E_NOTIMPL;
}
STDMETHOD( OnDataAvailable )(
/* [in] */ DWORD grfBSCF,
/* [in] */ DWORD dwSize,
/* [in] */ FORMATETC __RPC_FAR* pformatetc,
/* [in] */ STGMEDIUM __RPC_FAR* pstgmed )
{
return E_NOTIMPL;
}
STDMETHOD( OnObjectAvailable )(
/* [in] */ REFIID riid,
/* [iid_is][in] */ IUnknown __RPC_FAR* punk )
{
return E_NOTIMPL;
}
// IUnknown methods. Note that IE never calls any of these methods, since
// the caller owns the IBindStatusCallback interface, so the methods all
// return zero/E_NOTIMPL.
STDMETHOD_( ULONG, AddRef )
()
{
return 0;
}
STDMETHOD_( ULONG, Release )
()
{
return 0;
}
STDMETHOD( QueryInterface )(
/* [in] */ REFIID riid,
/* [iid_is][out] */ void __RPC_FAR* __RPC_FAR* ppvObject )
{
return E_NOTIMPL;
}
private:
std::unique_ptr<AbortManager::AbortableScope> pScope_;
std::mutex mutex_;
bool hasAborted_ = false;
};
DownloadStatus::DownloadStatus( abort_callback& abort )
{
auto& am = SpotifyInstance::Get().GetAbortManager();
pScope_ = std::make_unique<AbortManager::AbortableScope>(
am.GetAbortableScope( [&] {
std::lock_guard lg( mutex_ );
hasAborted_ = true;
},
abort ) );
}
HRESULT DownloadStatus::OnProgress( ULONG ulProgress, ULONG ulProgressMax,
ULONG ulStatusCode, LPCWSTR wszStatusText )
{
std::lock_guard lg( mutex_ );
return ( hasAborted_ ? E_ABORT : S_OK );
}
} // namespace
namespace sptf
{
WebApi_UserCache::WebApi_UserCache()
: jsonCache_( "" )
{
}
void WebApi_UserCache::CacheObject( const WebApi_User& object, bool force /*= false */ )
{
std::lock_guard lock( cacheMutex_ );
jsonCache_.CacheObject_NonBlocking( object, "me", force );
}
std::optional<std::unique_ptr<sptf::WebApi_User>> WebApi_UserCache::GetObjectFromCache()
{
std::lock_guard lock( cacheMutex_ );
return jsonCache_.GetObjectFromCache_NonBlocking( "me" );
}
WebApi_ImageCache::WebApi_ImageCache( const std::string& cacheSubdir )
: cacheSubdir_( cacheSubdir )
{
}
fs::path WebApi_ImageCache::GetImage( const std::string& id, const std::string& imgUrl, abort_callback& abort )
{
std::lock_guard lock( cacheMutex_ );
const auto imagePath = path::WebApiCache() / "images" / cacheSubdir_ / fmt::format( "{}.jpeg", id );
if ( !fs::exists( imagePath ) )
{
fs::create_directories( imagePath.parent_path() );
const auto url_w = qwr::unicode::ToWide( imgUrl );
DownloadStatus ds( abort );
auto hr = URLDownloadToFile( nullptr, url_w.c_str(), imagePath.c_str(), 0, &ds );
if ( FAILED( hr ) )
{
fs::remove( imagePath ); // in case download was aborted midway
qwr::error::CheckHR( hr, "URLDownloadToFile" );
}
}
assert( fs::exists( imagePath ) );
return imagePath;
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_cache.h
================================================
#pragma once
#include <nonstd/span.hpp>
#include <qwr/file_helpers.h>
#include <filesystem>
#include <memory>
#include <mutex>
namespace sptf
{
template <typename T>
class WebApi_JsonCache
{
public:
WebApi_JsonCache( const std::string& cacheSubdir )
: cacheSubdir_( cacheSubdir )
{
}
std::optional<std::unique_ptr<T>>
GetObjectFromCache_NonBlocking( const std::string& filename )
{
namespace fs = std::filesystem;
const auto filePath = GetCachedPath( filename );
if ( !fs::exists( filePath ) )
{
return std::nullopt;
}
const auto data = qwr::file::ReadFile( filePath, CP_UTF8, false );
try
{
return nlohmann::json::parse( data ).get<std::unique_ptr<T>>();
}
catch ( const nlohmann::detail::exception& )
{
return std::nullopt;
}
}
void CacheObject_NonBlocking( const T& object, const std::string& filename, bool force )
{
namespace fs = std::filesystem;
const auto filePath = GetCachedPath( filename );
if ( fs::exists( filePath ) )
{
if ( !force )
{
return;
}
fs::remove( filePath );
}
fs::create_directories( filePath.parent_path() );
qwr::file::WriteFile( filePath, nlohmann::json( object ).dump( 2 ) );
}
bool IsCached_NonBlocking( const std::string& filename )
{
namespace fs = std::filesystem;
const auto filePath = GetCachedPath( filename );
return fs::exists( filePath );
}
private:
std::filesystem::path GetCachedPath( const std::string& filename ) const
{
return path::WebApiCache() / "data" / cacheSubdir_ / fmt::format( "{}.json", filename );
}
private:
std::string cacheSubdir_;
};
template <typename T>
class WebApi_ObjectCache
{
public:
WebApi_ObjectCache( const std::string& cacheSubdir )
: jsonCache_( cacheSubdir )
{
}
void CacheObject( const T& object, bool force = false )
{
std::lock_guard lock( cacheMutex_ );
jsonCache_.CacheObject_NonBlocking( object, object.id, force );
}
void CacheObjects( const nonstd::span<std::unique_ptr<T>> objects, bool force = false )
{
std::lock_guard lock( cacheMutex_ );
for ( const auto& pObject: objects )
{
jsonCache_.CacheObject_NonBlocking( *pObject, pObject->id, force );
}
}
void CacheObjects( const nonstd::span<std::unique_ptr<const T>> objects, bool force = false )
{
std::lock_guard lock( cacheMutex_ );
for ( const auto& pObject: objects )
{
jsonCache_.CacheObject_NonBlocking( *pObject, pObject->id, force );
}
}
std::optional<std::unique_ptr<T>>
GetObjectFromCache( const std::string& id )
{
std::lock_guard lock( cacheMutex_ );
return jsonCache_.GetObjectFromCache_NonBlocking( id );
}
bool IsCached( const std::string& id )
{
std::lock_guard lock( cacheMutex_ );
return jsonCache_.IsCached_NonBlocking( id );
}
private:
std::mutex cacheMutex_;
WebApi_JsonCache<T> jsonCache_;
};
struct WebApi_User;
class WebApi_UserCache
{
public:
WebApi_UserCache();
void CacheObject( const WebApi_User& object, bool force = false );
std::optional<std::unique_ptr<WebApi_User>>
GetObjectFromCache();
private:
std::mutex cacheMutex_;
WebApi_JsonCache<WebApi_User> jsonCache_;
};
class WebApi_ImageCache
{
public:
WebApi_ImageCache( const std::string& cacheSubdir );
std::filesystem::path GetImage( const std::string& id,
const std::string& imgUrl,
abort_callback& abort );
private:
std::string cacheSubdir_;
std::mutex cacheMutex_;
};
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_album.cpp
================================================
#include <stdafx.h>
#include "webapi_album.h"
#include <backend/webapi_objects/webapi_media_objects.h>
#include <utils/json_std_extenders.h>
namespace sptf
{
SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Album_Simplified, artists, images, release_date, name, id );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_album.h
================================================
#pragma once
#include <memory>
#include <string>
#include <vector>
namespace sptf
{
struct WebApi_Artist_Simplified;
struct WebApi_Image;
struct WebApi_Album_Simplified
{
// album_group string, optional The field is present when getting an artists albums. Possible values are album, single, compilation, appears_on. Compare to album_type this field represents relationship between the artist and the album.
// album_type string The type of the album: one of album, single, or compilation.
// available_markets array of strings The markets in which the album is available: ISO 3166-1 alpha-2 country codes. Note that an album is considered available in a market when at least 1 of its tracks is available in that market.
// external_urls an external URL object Known external URLs for this album.
// release_date_precision string The precision with which release_date value is known: year , month , or day.
// restrictions a restrictions object Part of the response when Track Relinking is applied, the original track is not available in the given market, and Spotify did not have any tracks to relink it with. The track response will still contain metadata for the original track, and a restrictions object containing the reason why the track is not available: "restrictions" : {"reason" : "market"}
std::vector<std::unique_ptr<WebApi_Artist_Simplified>> artists;
std::vector<std::unique_ptr<WebApi_Image>> images;
std::string id;
std::string release_date;
std::string name;
};
void to_json( nlohmann::json& j, const WebApi_Album_Simplified& p );
void from_json( const nlohmann::json& j, WebApi_Album_Simplified& p );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_artist.cpp
================================================
#include <stdafx.h>
#include "webapi_artist.h"
#include <backend/webapi_objects/webapi_media_objects.h>
#include <utils/json_std_extenders.h>
namespace sptf
{
SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Artist_Simplified, id, name );
SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Artist, id, images, name, popularity );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_artist.h
================================================
#pragma once
#include <string>
namespace sptf
{
struct WebApi_Image;
struct WebApi_Artist_Simplified
{
// external_urls an external URL object Known external URLs for this artist.
std::string id;
std::string name;
};
struct WebApi_Artist
{
// external_urls an external URL object Known external URLs for this artist.
// followers A followers object Information about the followers of the artist.
// genres array of strings A list of the genres the artist is associated with. For example: "Prog Rock" , "Post-Grunge". (If not yet classified, the array is empty.)
std::string id;
std::vector<std::unique_ptr<WebApi_Image>> images;
std::string name;
uint32_t popularity;
};
void to_json( nlohmann::json& j, const WebApi_Artist_Simplified& p );
void from_json( const nlohmann::json& j, WebApi_Artist_Simplified& p );
void to_json( nlohmann::json& j, const WebApi_Artist& p );
void from_json( const nlohmann::json& j, WebApi_Artist& p );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_image.cpp
================================================
#include <stdafx.h>
#include "webapi_image.h"
namespace sptf
{
SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Image, height, url, width );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_image.h
================================================
#pragma once
#include <memory>
#include <string>
namespace sptf
{
struct WebApi_Image
{
uint32_t height;
std::string url;
uint32_t width;
};
void to_json( nlohmann::json& j, const WebApi_Image& p );
void from_json( const nlohmann::json& j, WebApi_Image& p );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_media_objects.h
================================================
#pragma once
#include <backend/webapi_objects/webapi_album.h>
#include <backend/webapi_objects/webapi_artist.h>
#include <backend/webapi_objects/webapi_image.h>
#include <backend/webapi_objects/webapi_playlist_track.h>
#include <backend/webapi_objects/webapi_restriction.h>
#include <backend/webapi_objects/webapi_track.h>
#include <backend/webapi_objects/webapi_track_link.h>
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_paging_object.cpp
================================================
#include <stdafx.h>
#include "webapi_paging_object.h"
#include <utils/json_std_extenders.h>
namespace sptf
{
void from_json( const nlohmann::json& nlohmann_json_j, WebApi_PagingObject& nlohmann_json_t )
{
NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_FROM, items, limit, next, offset, previous, total ) )
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_paging_object.h
================================================
#pragma once
#include <memory>
#include <optional>
#include <string>
namespace sptf
{
struct WebApi_PagingObject
{
// href string A link to the Web API endpoint returning the full result of the request.
nlohmann::json items;
size_t limit;
std::optional<std::wstring> next;
size_t offset;
std::optional<std::wstring> previous;
size_t total;
};
void from_json( const nlohmann::json& j, WebApi_PagingObject& p );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_playlist_track.cpp
================================================
#include <stdafx.h>
#include "webapi_playlist_track.h"
#include <backend/webapi_objects/webapi_media_objects.h>
#include <utils/json_std_extenders.h>
namespace sptf
{
void to_json( nlohmann::json& j, const WebApi_PlaylistTrack& p )
{
std::visit( [&j]( auto&& arg ) {
j["track"] = arg;
},
*p.track );
}
void from_json( const nlohmann::json& j, WebApi_PlaylistTrack& p )
{
if ( j.at( "is_local" ).get<bool>() )
{
p.track = std::make_unique<std::variant<WebApi_Track, WebApi_LocalTrack>>( j.at( "track" ).get<WebApi_LocalTrack>() );
}
else
{
p.track = std::make_unique<std::variant<WebApi_Track, WebApi_LocalTrack>>( j.at( "track" ).get<WebApi_Track>() );
}
}
SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_LocalTrack, uri, name );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_playlist_track.h
================================================
#pragma once
#include <memory>
#include <string>
#include <variant>
namespace sptf
{
struct WebApi_Track;
struct WebApi_LocalTrack
{
std::string uri;
std::optional<std::string> name;
};
struct WebApi_PlaylistTrack
{
// added_at a timestamp The date and time the track was added. Note that some very old playlists may return null in this field.
// added_by a user object The Spotify user who added the track. Note that some very old playlists may return null in this field.
std::unique_ptr<std::variant<WebApi_Track, WebApi_LocalTrack>> track;
};
void to_json( nlohmann::json& j, const WebApi_PlaylistTrack& p );
void from_json( const nlohmann::json& j, WebApi_PlaylistTrack& p );
void to_json( nlohmann::json& j, const WebApi_LocalTrack& p );
void from_json( const nlohmann::json& j, WebApi_LocalTrack& p );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_restriction.cpp
================================================
#include <stdafx.h>
#include "webapi_restriction.h"
namespace sptf
{
SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Restriction, reason );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_restriction.h
================================================
#pragma once
#include <memory>
#include <string>
namespace sptf
{
struct WebApi_Restriction
{
std::string reason;
};
void to_json( nlohmann::json& j, const WebApi_Restriction& p );
void from_json( const nlohmann::json& j, WebApi_Restriction& p );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_track.cpp
================================================
#include <stdafx.h>
#include "webapi_track.h"
#include <backend/webapi_objects/webapi_media_objects.h>
#include <utils/json_std_extenders.h>
namespace sptf
{
WebApi_Track::WebApi_Track( std::unique_ptr<WebApi_Track_Simplified> trackSimplified,
std::shared_ptr<const WebApi_Album_Simplified> albumSimplified )
{
album = albumSimplified;
artists = std::move( trackSimplified->artists );
disc_number = trackSimplified->disc_number;
duration_ms = trackSimplified->duration_ms;
name = trackSimplified->name;
linked_from = std::move( trackSimplified->linked_from );
restrictions = std::move( trackSimplified->restrictions );
preview_url = trackSimplified->preview_url;
track_number = trackSimplified->track_number;
id = trackSimplified->id;
}
void to_json( nlohmann::json& nlohmann_json_j, const WebApi_Track_Simplified& nlohmann_json_t )
{
// we don't need to save `restrictions`, since it's only used on initial parsing
NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_TO, artists, disc_number, duration_ms, linked_from, name, preview_url, track_number, id ) )
}
void from_json( const nlohmann::json& nlohmann_json_j, WebApi_Track_Simplified& nlohmann_json_t )
{
NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_FROM, artists, disc_number, duration_ms, name, preview_url, track_number, id ) )
if ( nlohmann_json_j.contains( "linked_from" ) )
{
NLOHMANN_JSON_FROM( linked_from )
}
if ( nlohmann_json_j.contains( "restrictions" ) )
{
NLOHMANN_JSON_FROM( restrictions )
}
}
void to_json( nlohmann::json& nlohmann_json_j, const WebApi_Track& nlohmann_json_t )
{
// we don't need to save `restrictions`, since it's only used on initial parsing
NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_TO, album, artists, disc_number, duration_ms, linked_from, name, preview_url, track_number, id ) )
}
void from_json( const nlohmann::json& nlohmann_json_j, WebApi_Track& nlohmann_json_t )
{
NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_FROM, album, artists, disc_number, duration_ms, name, preview_url, track_number, id ) )
if ( nlohmann_json_j.contains( "linked_from" ) )
{
NLOHMANN_JSON_FROM( linked_from )
}
if ( nlohmann_json_j.contains( "restrictions" ) )
{
NLOHMANN_JSON_FROM( restrictions )
}
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_track.h
================================================
#pragma once
#include <memory>
#include <string>
#include <vector>
namespace sptf
{
struct WebApi_Album_Simplified;
struct WebApi_Artist_Simplified;
struct WebApi_Restriction;
struct WebApi_TrackLink;
struct WebApi_Track_Simplified
{
// available_markets array of strings A list of the countries in which the track can be played, identified by their ISO 3166-1 alpha-2 code.
// explicit Boolean Whether or not the track has explicit lyrics ( true = yes it does; false = no it does not OR unknown).
// external_urls an external URL object External URLs for this track.
// is_playable boolean Part of the response when Track Relinking is applied.If true, the track is playable in the given market.Otherwise false.
// is_local boolean Whether or not the track is from a local file.
std::vector<std::unique_ptr<WebApi_Artist_Simplified>> artists;
uint32_t disc_number;
uint32_t duration_ms;
std::string id;
std::optional<std::unique_ptr<WebApi_TrackLink>> linked_from;
std::optional<std::unique_ptr<WebApi_Restriction>> restrictions;
std::string name;
std::optional<std::string> preview_url;
uint32_t track_number;
};
struct WebApi_Track
{
WebApi_Track() = default;
WebApi_Track( std::unique_ptr<WebApi_Track_Simplified> trackSimplified, std::shared_ptr<const WebApi_Album_Simplified> albumSimplified );
//available_markets array of strings A list of the countries in which the track can be played, identified by their ISO 3166-1 alpha-2 code.
//explicit Boolean Whether or not the track has explicit lyrics ( true = yes it does; false = no it does not OR unknown).
//external_ids an external ID object Known external IDs for the track.
//external_urls an external URL object Known external URLs for this track.
//is_playable boolean Part of the response when Track Relinking is applied.If true, the track is playable in the given market.Otherwise false.
//popularity integer The popularity of the track. The value will be between 0 and 100, with 100 being the most popular.
//The popularity of a track is a value between 0 and 100, with 100 being the most popular. The popularity is calculated by algorithm and is based, in the most part, on the total number of plays the track has had and how recent those plays are.
//Generally speaking, songs that are being played a lot now will have a higher popularity than songs that were played a lot in the past. Duplicate tracks (e.g. the same track from a single and an album) are rated independently. Artist and album popularity is derived mathematically from track popularity. Note that the popularity value may lag actual popularity by a few days: the value is not updated in real time.
//is_local boolean Whether or not the track is from a local file.
std::shared_ptr<const WebApi_Album_Simplified> album;
std::vector<std::unique_ptr<WebApi_Artist_Simplified>> artists;
uint32_t disc_number;
uint32_t duration_ms;
std::string id;
std::optional<std::unique_ptr<WebApi_TrackLink>> linked_from;
std::optional<std::unique_ptr<WebApi_Restriction>> restrictions;
std::string name;
std::optional<std::string> preview_url;
uint32_t track_number;
};
void to_json( nlohmann::json& j, const WebApi_Track_Simplified& p );
void from_json( const nlohmann::json& j, WebApi_Track_Simplified& p );
void to_json( nlohmann::json& j, const WebApi_Track& p );
void from_json( const nlohmann::json& j, WebApi_Track& p );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_track_link.cpp
================================================
#include <stdafx.h>
#include "webapi_track_link.h"
#include <backend/webapi_objects/webapi_media_objects.h>
#include <utils/json_std_extenders.h>
namespace sptf
{
SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_TrackLink, id );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_track_link.h
================================================
#pragma once
#include <memory>
#include <string>
namespace sptf
{
struct WebApi_TrackLink
{
// external_urls an external URL object Known external URLs for this track.
// href string A link to the Web API endpoint providing full details of the track.
std::string id;
// type string The object type: track.
// uri string The Spotify URI for the track.
};
void to_json( nlohmann::json& j, const WebApi_TrackLink& p );
void from_json( const nlohmann::json& j, WebApi_TrackLink& p );
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_user.cpp
================================================
#include <stdafx.h>
#include "webapi_user.h"
#include <utils/json_std_extenders.h>
namespace sptf
{
void to_json( nlohmann::json& nlohmann_json_j, const WebApi_User& nlohmann_json_t )
{
NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_TO, country, display_name, uri ) )
}
void from_json( const nlohmann::json& nlohmann_json_j, WebApi_User& nlohmann_json_t )
{
NLOHMANN_JSON_EXPAND( NLOHMANN_JSON_PASTE( NLOHMANN_JSON_FROM, display_name, uri ) )
if ( nlohmann_json_j.contains( "country" ) )
{
NLOHMANN_JSON_FROM( country )
}
}
} // namespace sptf
================================================
FILE: foo_spotify/backend/webapi_objects/webapi_user.h
================================================
#pragma once
#include <optional>
#include <string>
namespace sptf
{
struct WebApi_User
{
std::optional<std::string> country;
std::optional<std::string> display_name;
//std::optional<std::string> email;
//external_urls an external URL object Known external URLs for this user.
//followers A followers object Information about the followers of the user.
//href string A link to the Web API endpoint for this user.
std::string id;
//images an array of image objects The users profile image.
//product string The users Spotify subscription level: premium, free, etc. (The subscription level open can be considered the same as free.) This field is only available when the current user has granted access to the user-read-private scope.
std::string uri;
};
void to_json( nlohmann::json& j, const WebApi_User& p );
void from_json( const nlohmann::json& j, WebApi_User& p );
} // namespace sptf
================================================
FILE: foo_spotify/component_defines.h
================================================
#pragma once
// Generated by scripts from setup.py
#include <commit_hash.h>
#include <version.h>
#define SPTF_NAME "Spotify Integration"
#define SPTF_UNDERSCORE_NAME "foo_spotify"
#define SPTF_DLL_NAME SPTF_UNDERSCORE_NAME ".dll"
#define SPTF_STRINGIFY_HELPER( x ) #x
#define SPTF_STRINGIFY( x ) SPTF_STRINGIFY_HELPER( x )
#ifdef SPTF_VERSION_PRERELEASE_TEXT
# define SPTF_VERSION_PRERELEASE "-" SPTF_VERSION_PRERELEASE_TEXT
# define SPTF_VERSION_METADATA "+" SPTF_STRINGIFY( SPTF_COMMIT_HASH )
#else
# define SPTF_VERSION_PRERELEASE ""
# define SPTF_VERSION_METADATA ""
#endif
#ifdef _DEBUG
# define SPTF_VERSION_DEBUG_SUFFIX " (Debug)"
#else
# define SPTF_VERSION_DEBUG_SUFFIX ""
#endif
#define SPTF_VERSION \
SPTF_STRINGIFY( SPTF_VERSION_MAJOR ) \
"." SPTF_STRINGIFY( SPTF_VERSION_MINOR ) "." SPTF_STRINGIFY( SPTF_VERSION_PATCH ) \
SPTF_VERSION_PRERELEASE SPTF_VERSION_METADATA
#define SPTF_NAME_WITH_VERSION SPTF_NAME " v" SPTF_VERSION SPTF_VERSION_DEBUG_SUFFIX
#define SPTF_ABOUT \
SPTF_NAME_WITH_VERSION " by TheQwertiest"
================================================
FILE: foo_spotify/component_guids.h
================================================
#pragma once
namespace sptf::guid
{
constexpr GUID acfu_source = { 0xbfbd48bc, 0x9f3b, 0x42bd, { 0x8e, 0xfc, 0x9d, 0x5a, 0xf1, 0x2f, 0xf3, 0xa1 } };
constexpr GUID adv_branch = { 0x3e2d241a, 0x306b, 0x49bc, { 0x80, 0xb3, 0x6a, 0x77, 0xe9, 0x21, 0x32, 0xc7 } };
constexpr GUID adv_branch_logging = { 0xa69190a1, 0x3abd, 0x4a45, { 0x9c, 0x4a, 0x66, 0xbd, 0xb, 0x7f, 0xec, 0x11 } };
constexpr GUID adv_branch_network = { 0x53328c11, 0x156e, 0x4b5c, { 0x8f, 0x82, 0xe5, 0x3d, 0x5d, 0xb5, 0x7c, 0x2b } };
constexpr GUID adv_var_network_proxy = { 0x2626706b, 0x19a9, 0x4ccf, { 0x85, 0xdd, 0x55, 0xd4, 0x2f, 0x8b, 0x57, 0x46 } };
constexpr GUID adv_var_network_proxy_username = { 0xd9e86980, 0xcee4, 0x4075, { 0x96, 0xef, 0x79, 0xed, 0xba, 0x87, 0x79, 0x58 } };
constexpr GUID adv_var_network_proxy_password = { 0xd138fb5, 0x3e6f, 0x48d6, { 0x9b, 0x44, 0x44, 0x6c, 0x78, 0xd4, 0x6f, 0xa3 } };
constexpr GUID adv_var_logging_webapi_debug = { 0xea784339, 0x21d7, 0x47ab, { 0xbc, 0xeb, 0x7a, 0xf7, 0xc, 0x8f, 0xb0, 0x18 } };
constexpr GUID adv_var_logging_webapi_request = { 0x90066d1d, 0x1233, 0x4fcc, { 0xab, 0xc3, 0xbc, 0x17, 0xb4, 0x68, 0x65, 0x84 } };
constexpr GUID adv_var_logging_webapi_response = { 0x349d3d49, 0xfffc, 0x4b32, { 0x8b, 0xf7, 0xc0, 0x78, 0x3a, 0x87, 0x5e, 0xa4 } };
constexpr GUID config_enable_normalization = { 0x7917bfbc, 0x3731, 0x4523, { 0xa4, 0x9e, 0xf8, 0xb3, 0x8e, 0xad, 0xbd, 0xb1 } };
constexpr GUID config_enable_private_mode = { 0xfd7aad3c, 0x3e8f, 0x45c2, { 0xaa, 0x62, 0xbe, 0xcc, 0x55, 0xe1, 0x2d, 0xfb } };
constexpr GUID config_libspotify_cache_size_in_mb = { 0xf23f3e, 0x5d86, 0x4092, { 0x8d, 0xf, 0xf1, 0x7d, 0x5b, 0xa1, 0x22, 0xf7 } };
constexpr GUID config_libspotify_cache_size_in_percent = { 0xf2107600, 0x4e31, 0x4039, { 0x9a, 0x5, 0xd3, 0x8f, 0x23, 0x25, 0x88, 0x72 } };
constexpr GUID config_preferred_bitrate = { 0xfaf55f2c, 0xe6d6, 0x4adc, { 0xae, 0x85, 0x3e, 0xcc, 0x8c, 0x19, 0xa, 0xe0 } };
constexpr GUID config_webapi_auth_scopes = { 0xabc0e6b2, 0x2569, 0x42ee, { 0xb8, 0xb0, 0x86, 0x7e, 0x3a, 0xc3, 0x21, 0x9 } };
constexpr GUID input = { 0xb376ad64, 0xe029, 0x40f6, { 0x89, 0xf8, 0xae, 0xc, 0xfa, 0x73, 0xa5, 0xa3 } };
constexpr GUID preferences = { 0x7285bbbd, 0x2109, 0x437c, { 0x8f, 0x3e, 0xaa, 0xa9, 0xc9, 0x90, 0x65, 0x2c } };
} // namespace sptf::guid
================================================
FILE: foo_spotify/component_paths.cpp
================================================
#include <stdafx.h>
#include "component_paths.h"
#include <qwr/fbk2_paths.h>
namespace fs = std::filesystem;
namespace sptf::path
{
fs::path LibSpotifyCache()
{
return qwr::path::Profile() / SPTF_UNDERSCORE_NAME / "ls" / "cache";
}
fs::path LibSpotifySettings()
{
return qwr::path::Profile() / SPTF_UNDERSCORE_NAME / "ls" / "settings";
}
fs::path WebApiCache()
{
return qwr::path::Profile() / SPTF_UNDERSCORE_NAME / "wa" / "cache";
}
fs::path WebApiSettings()
{
return qwr::path::Profile() / SPTF_UNDERSCORE_NAME / "wa" / "settings";
}
} // namespace sptf::path
================================================
FILE: foo_spotify/component_paths.h
================================================
#pragma once
#include <filesystem>
namespace sptf::path
{
std::filesystem::path LibSpotifyCache();
std::filesystem::path LibSpotifySettings();
std::filesystem::path WebApiCache();
std::filesystem::path WebApiSettings();
} // namespace sptf::guid
================================================
FILE: foo_spotify/component_urls.h
================================================
#pragma once
namespace sptf::url
{
constexpr wchar_t accountsAuthenticate[] = L"https://accounts.spotify.com/authorize";
constexpr wchar_t accountsApi[] = L"https://accounts.spotify.com/api/token";
constexpr wchar_t spotifyApi[] = L"https://api.spotify.com/v1";
constexpr wchar_t redirectUri[] = L"http://localhost:23018";
constexpr wchar_t homepage[] = L"https://github.com/TheQwertiest/" SPTF_UNDERSCORE_NAME;
} // namespace sptf::url
================================================
FILE: foo_spotify/dllmain.cpp
================================================
#include <stdafx.h>
#include <backend/spotify_instance.h>
#include <ui/ui_not_auth.h>
#include <qwr/abort_callback.h>
#include <qwr/delayed_executor.h>
#include <qwr/error_popup.h>
DECLARE_COMPONENT_VERSION( SPTF_NAME, SPTF_VERSION, SPTF_ABOUT );
VALIDATE_COMPONENT_FILENAME( SPTF_DLL_NAME );
using namespace sptf;
namespace
{
bool HasComponent( const std::u8string& componentName )
{
pfc::string8_fast temp;
for ( service_enum_t<componentversion> e; !e.finished(); ++e )
{
auto cv = e.get();
cv->get_file_name( temp );
if ( temp.c_str() == componentName )
{
return true;
}
}
return false;
}
} // namespace
namespace
{
class sptf_initquit : public initquit
{
public:
void on_init() override
{
qwr::DelayedExecutor::GetInstance().EnableExecution(); ///< Enable task processing (e.g. error popups)
if ( HasComponent( "foo_input_spotify" ) )
{
qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME,
"!!! `foo_input_spotify` component detected! !!!\n"
"!!! It is incompatible with `foo_spotify` and must be removed! !!!\n"
"!!! Otherwise foobar2000 *will* crash! !!!" );
}
}
void on_quit() override
{ // Careful when changing invocation order here!
qwr::GlobalAbortCallback::GetInstance().Abort();
SpotifyInstance::Get().Finalize();
ui::NotAuthHandler::Get().CloseDialog();
}
};
initquit_factory_t<sptf_initquit> g_initquit;
} // namespace
extern "C" BOOL WINAPI DllMain( HINSTANCE ins, DWORD reason, [[maybe_unused]] LPVOID lp )
{
switch ( reason )
{
case DLL_PROCESS_ATTACH:
{
break;
}
case DLL_PROCESS_DETACH:
{
break;
}
default:
break;
}
return TRUE;
}
================================================
FILE: foo_spotify/fb2k/acfu_integration.cpp
================================================
#include <stdafx.h>
#include <component_urls.h>
#include <qwr/acfu_integration.h>
using namespace sptf;
namespace
{
class SptfSource
: public qwr::acfu::QwrSource
{
public:
// ::acfu::source
GUID get_guid() override;
::acfu::request::ptr create_request() override;
// qwr::acfu::github_conf
static pfc::string8 get_owner();
static pfc::string8 get_repo();
// qwr::acfu::QwrSource
std::string GetComponentName() const override;
std::string GetComponentFilename() const override;
};
} // namespace
namespace
{
GUID SptfSource::get_guid()
{
return guid::acfu_source;
}
::acfu::request::ptr SptfSource::create_request()
{
return fb2k::service_new<qwr::acfu::github_latest_release<SptfSource>>();
}
pfc::string8 SptfSource::get_owner()
{
return "TheQwertiest";
}
pfc::string8 SptfSource::get_repo()
{
return qwr::unicode::ToU8( std::wstring_view{ url::homepage } ).c_str();
}
std::string SptfSource::GetComponentName() const
{
return SPTF_NAME;
}
std::string SptfSource::GetComponentFilename() const
{
return SPTF_UNDERSCORE_NAME;
}
service_factory_single_t<SptfSource> g_acfuSource;
} // namespace
================================================
FILE: foo_spotify/fb2k/advanced_config.cpp
================================================
#include <stdafx.h>
#include "advanced_config.h"
namespace
{
advconfig_branch_factory branch_sptf(
"Spotify Integration", sptf::guid::adv_branch, advconfig_branch::guid_branch_tools, 0 );
advconfig_branch_factory branch_network(
"Network: restart is required", sptf::guid::adv_branch_network, sptf::guid::adv_branch, 0 );
advconfig_branch_factory branch_logging(
"Logging: restart is required", sptf::guid::adv_branch_logging, sptf::guid::adv_branch, 1 );
} // namespace
namespace sptf::config::advanced
{
qwr::fb2k::AdvConfigString_MT network_proxy(
"Proxy: url",
sptf::guid::adv_var_network_proxy, sptf::guid::adv_branch_network, 0,
"" );
qwr::fb2k::AdvConfigString_MT network_proxy_username(
"Proxy: username",
sptf::guid::adv_var_network_proxy_username, sptf::guid::adv_branch_network, 1,
"" );
qwr::fb2k::AdvConfigString_MT network_proxy_password(
"Proxy: password",
sptf::guid::adv_var_network_proxy_password, sptf::guid::adv_branch_network, 2,
"" );
qwr::fb2k::AdvConfigBool_MT logging_webapi_request(
"Log Spotify Web API: request",
sptf::guid::adv_var_logging_webapi_request, sptf::guid::adv_branch_logging, 0,
false );
qwr::fb2k::AdvConfigBool_MT logging_webapi_response(
"Log Spotify Web API: response",
sptf::guid::adv_var_logging_webapi_response, sptf::guid::adv_branch_logging, 1,
false );
qwr::fb2k::AdvConfigBool_MT logging_webapi_debug(
"Log Spotify Web API: debug",
sptf::guid::adv_var_logging_webapi_debug, sptf::guid::adv_branch_logging, 2,
false );
} // namespace sptf::config::advanced
================================================
FILE: foo_spotify/fb2k/advanced_config.h
================================================
#pragma once
#include <qwr/fb2k_adv_config.h>
namespace sptf::config::advanced
{
extern qwr::fb2k::AdvConfigString_MT network_proxy;
extern qwr::fb2k::AdvConfigString_MT network_proxy_username;
extern qwr::fb2k::AdvConfigString_MT network_proxy_password;
extern qwr::fb2k::AdvConfigBool_MT logging_webapi_request;
extern qwr::fb2k::AdvConfigBool_MT logging_webapi_response;
extern qwr::fb2k::AdvConfigBool_MT logging_webapi_debug;
} // namespace sptf::config::advanced
================================================
FILE: foo_spotify/fb2k/album_art.cpp
================================================
#include <stdafx.h>
#include <backend/spotify_instance.h>
#include <backend/spotify_object.h>
#include <backend/webapi_backend.h>
#include <backend/webapi_objects/webapi_media_objects.h>
using namespace sptf;
namespace
{
class AlbumArtExtractorInstanceSpotify : public album_art_extractor_instance
{
public:
AlbumArtExtractorInstanceSpotify( std::unique_ptr<const WebApi_Track> track, std::unique_ptr<const WebApi_Artist> artist );
album_art_data_ptr query( const GUID& p_what, abort_callback& p_abort ) override;
private:
WebApi_Backend& waBackend_;
std::unique_ptr<const WebApi_Track> track_;
std::unique_ptr<const WebApi_Artist> artist_;
};
class AlbumArtExtractorSpotify : public album_art_extractor
{
public:
AlbumArtExtractorSpotify() = default;
bool is_our_path( const char* p_path, const char* p_extension ) override;
album_art_extractor_instance_ptr open( file_ptr p_filehint, const char* p_path, abort_callback& p_abort ) override;
};
} // namespace
namespace
{
AlbumArtExtractorInstanceSpotify::AlbumArtExtractorInstanceSpotify( std::unique_ptr<const WebApi_Track> track, std::unique_ptr<const WebApi_Artist> artist )
: waBackend_( SpotifyInstance::Get().GetWebApi_Backend() )
, track_( std::move( track ) )
, artist_( std::move( artist ) )
{
assert( track_ );
assert( artist_ );
}
album_art_data_ptr AlbumArtExtractorInstanceSpotify::query( const GUID& p_what, abort_callback& p_abort )
{
const auto imagePath = [&] {
if ( p_what == album_art_ids::cover_front )
{
if ( track_->album->images.empty() )
{
throw exception_album_art_not_found();
}
return waBackend_.GetAlbumImage( track_->album->id, track_->album->images[0]->url, p_abort );
}
else if ( p_what == album_art_ids::artist )
{
if ( artist_->images.empty() )
{
throw exception_album_art_not_found();
}
return waBackend_.GetArtistImage( artist_->id, artist_->images[0]->url, p_abort );
}
else
{
throw exception_album_art_not_found();
}
}();
pfc::string8_fast canPath;
filesystem::g_get_canonical_path( imagePath.u8string().c_str(), canPath );
file::ptr file;
filesystem::g_open( file, canPath, filesystem::open_mode_read, p_abort );
return album_art_data_impl::g_create( file.get_ptr(), (size_t)file->get_size_ex( p_abort ), p_abort );
}
bool AlbumArtExtractorSpotify::is_our_path( const char* p_path, const char* p_extension )
{
return SpotifyFilteredTrack::IsValid( p_path, false );
}
album_art_extractor_instance_ptr AlbumArtExtractorSpotify::open( file_ptr p_filehint, const char* p_path, abort_callback& p_abort )
{
const auto spotifyObject = SpotifyFilteredTrack::Parse( p_path );
auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend();
auto track = waBackend.GetTrack( spotifyObject.Id(), p_abort );
auto artist = waBackend.GetArtist( track->artists[0]->id, p_abort );
if ( track->album->images.empty() && artist->images.empty() )
{
throw exception_album_art_not_found();
}
return ::fb2k::service_new<AlbumArtExtractorInstanceSpotify>( std::move( track ), std::move( artist ) );
}
} // namespace
namespace
{
static service_factory_single_t<AlbumArtExtractorSpotify> g_album_art_extractor;
}
================================================
FILE: foo_spotify/fb2k/config.cpp
================================================
#include <stdafx.h>
#include "config.h"
namespace sptf::config
{
qwr::fb2k::ConfigBool_MT enable_normalization( sptf::guid::config_enable_normalization, true );
qwr::fb2k::ConfigBool_MT enable_private_mode( sptf::guid::config_enable_private_mode, false );
qwr::fb2k::ConfigUint8_MT libspotify_cache_size_in_percent( sptf::guid::config_libspotify_cache_size_in_percent, 10 );
qwr::fb2k::ConfigUint32_MT libspotify_cache_size_in_mb( sptf::guid::config_libspotify_cache_size_in_mb, 2048 );
qwr::fb2k::ConfigUint8Enum_MT<BitrateSettings> preferred_bitrate( sptf::guid::config_preferred_bitrate, BitrateSettings::Bitrate320k );
qwr::fb2k::ConfigString_MT webapi_auth_scopes( sptf::guid::config_webapi_auth_scopes, "playlist-read-collaborative playlist-read-private user-read-private" );
} // namespace sptf::config
================================================
FILE: foo_spotify/fb2k/config.h
================================================
#pragma once
#include <qwr/fb2k_config.h>
namespace sptf::config
{
enum class BitrateSettings : uint8_t
{ // values are the same as in libspotify's sp_bitrate
Bitrate160k = 0,
Bitrate320k = 1,
Bitrate96k = 2
};
extern qwr::fb2k::ConfigBool_MT enable_normalization;
extern qwr::fb2k::ConfigBool_MT enable_private_mode;
extern qwr::fb2k::ConfigUint8_MT libspotify_cache_size_in_percent;
extern qwr::fb2k::ConfigUint32_MT libspotify_cache_size_in_mb;
extern qwr::fb2k::ConfigUint8Enum_MT<BitrateSettings> preferred_bitrate;
extern qwr::fb2k::ConfigString_MT webapi_auth_scopes;
} // namespace sptf::config
================================================
FILE: foo_spotify/fb2k/file_info_filler.cpp
================================================
#include <stdafx.h>
#include "file_info_filler.h"
#include <qwr/string_helpers.h>
namespace
{
void FillMetaInfo( const std::unordered_multimap<std::string, std::string>& meta, file_info& info )
{
auto addMeta = [&]( file_info& info, std::string_view metaName ) {
const auto er = meta.equal_range( std::string( metaName.begin(), metaName.end() ) );
if ( er.first != er.second )
{
const auto [key, val] = *er.first;
info.meta_add( metaName.data(), val.c_str() );
}
};
auto addMetaIfPositive = [&]( file_info& info, std::string_view metaName ) {
const auto er = meta.equal_range( std::string( metaName.begin(), metaName.end() ) );
if ( er.first != er.second )
{
const auto [key, val] = *er.first;
auto numOpt = qwr::string::GetNumber<uint32_t>( static_cast<std::string_view>( val ) );
if ( numOpt && *numOpt )
{
info.meta_add( metaName.data(), val.c_str() );
}
}
};
auto addMetaMultiple = [&]( file_info& info, std::string_view metaName ) {
const auto er = meta.equal_range( std::string( metaName.begin(), metaName.end() ) );
for ( auto it = er.first; it != er.second; ++it )
{
const auto [key, val] = *it;
info.meta_add( metaName.data(), val.c_str() );
}
};
addMeta( info, "TITLE" );
addMeta( info, "ALBUM" );
addMeta( info, "DATE" );
addMetaMultiple( info, "ARTIST" );
addMetaMultiple( info, "ALBUM ARTIST" );
addMetaIfPositive( info, "TRACKNUMBER" );
addMetaIfPositive( info, "DISCNUMBER" );
const auto er = meta.equal_range( "SPTF_LENGTH" );
if ( er.first != er.second )
{
const auto [key, val] = *er.first;
auto numOpt = qwr::string::GetNumber<uint32_t>( static_cast<std::string_view>( val ) );
if ( numOpt && *numOpt )
{
info.set_length( *numOpt / 1000.0 );
}
}
}
void FillTechnicalInfo( const std::unordered_multimap<std::string, std::string>& meta, file_info& info )
{
if ( pfc_infinite == info.info_find( "codec" ) )
{
info.info_set( "codec", "Vorbis" );
}
}
} // namespace
namespace sptf::fb2k
{
void FillFileInfoWithMeta( const std::unordered_multimap<std::string, std::string>& meta, file_info& info )
{
FillMetaInfo( meta, info );
FillTechnicalInfo( meta, info );
}
} // namespace sptf::fb2k
================================================
FILE: foo_spotify/fb2k/file_info_filler.h
================================================
#pragma once
#include <string>
#include <unordered_map>
namespace sptf::fb2k
{
void FillFileInfoWithMeta( const std::unordered_multimap<std::string, std::string>& meta, file_info& info );
}
================================================
FILE: foo_spotify/fb2k/filesystem.cpp
================================================
#include <stdafx.h>
#include <backend/spotify_object.h>
using namespace std::literals::string_view_literals;
using namespace sptf;
namespace
{
class FilesystemSpotify : public foobar2000_io::filesystem
{
public:
bool get_canonical_path( const char* p_path, pfc::string_base& p_out ) override;
bool is_our_path( const char* p_path ) override;
bool get_display_path( const char* p_path, pfc::string_base& p_out ) override;
void open( service_ptr_t<file>& p_out, const char* p_path, t_open_mode p_mode, abort_callback& p_abort ) override;
void remove( const char* p_path, abort_callback& p_abort ) override;
//! Moves/renames a file. Will fail if the destination file already exists. \n
//! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios.
void move( const char* p_src, const char* p_dst, abort_callback& p_abort ) override;
//! Queries whether a file at specified path belonging to this filesystem is a remote object or not.
bool is_remote( const char* p_src ) override;
//! Retrieves stats of a file at specified path.
void get_stats( const char* p_path, t_filestats& p_stats, bool& p_is_writeable, abort_callback& p_abort ) override;
//! Creates a directory.
void create_directory( const char* p_path, abort_callback& p_abort ) override;
void list_directory( const char* p_path, directory_callback& p_out, abort_callback& p_abort ) override;
//! Hint; returns whether this filesystem supports mime types. \n
//! When this returns false, all file::get_content_type() calls on files opened thru this filesystem implementation will return false; otherwise, file::get_content_type() calls may return true depending on the file.
bool supports_content_types() override;
;
};
} // namespace
namespace
{
bool FilesystemSpotify::get_canonical_path( const char* p_path, pfc::string_base& p_out )
{
if ( !SpotifyFilteredTrack::IsValid( p_path, true ) )
{
return false;
}
try
{
const auto ret = SpotifyObject( p_path ).ToSchema();
p_out = ret.c_str();
return true;
}
catch ( qwr::QwrException& )
{
return false;
}
}
bool FilesystemSpotify::is_our_path( const char* p_path )
{
// accept only filtered input
return SpotifyFilteredTrack::IsValid( p_path, true );
}
bool FilesystemSpotify::get_display_path( const char* p_path, pfc::string_base& p_out )
{
if ( !SpotifyFilteredTrack::IsValid( p_path, true ) )
{
return false;
}
try
{
const auto ret = SpotifyObject( p_path ).ToUri();
p_out = ret.c_str();
return true;
}
catch ( qwr::QwrException& )
{
return false;
}
}
void FilesystemSpotify::open( service_ptr_t<file>& p_out, const char* p_path, t_open_mode p_mode, abort_callback& p_abort )
{
(void)p_abort;
if ( p_mode != open_mode_read )
{
throw pfc::exception( "Can't modify remote files" );
}
p_out = ::fb2k::service_new<file_streamstub>();
}
void FilesystemSpotify::remove( const char* p_path, abort_callback& p_abort )
{
(void)p_path;
(void)p_abort;
throw pfc::exception( "Can't delete remote files" );
}
void FilesystemSpotify::move( const char* p_src, const char* p_dst, abort_callback& p_abort )
{
(void)p_src;
(void)p_dst;
(void)p_abort;
throw pfc::exception( "Can't move remote files" );
}
bool FilesystemSpotify::is_remote( const char* p_src )
{
(void)p_src;
return false;
}
void FilesystemSpotify::get_stats( const char* p_path, t_filestats& p_stats, bool& p_is_writeable, abort_callback& p_abort )
{
(void)p_abort;
p_stats = filestats_invalid;
p_is_writeable = false;
}
void FilesystemSpotify::create_directory( const char* p_path, abort_callback& p_abort )
{
(void)p_path;
(void)p_abort;
throw pfc::exception( "Directories are not supported" );
}
void FilesystemSpotify::list_directory( const char* p_path, directory_callback& p_out, abort_callback& p_abort )
{
(void)p_path;
(void)p_out;
(void)p_abort;
throw pfc::exception( "Directories are not supported" );
}
bool FilesystemSpotify::supports_content_types()
{
return false;
}
} // namespace
namespace
{
static service_factory_single_t<FilesystemSpotify> g_filesystem;
}
================================================
FILE: foo_spotify/fb2k/input.cpp
================================================
#include <stdafx.h>
#include <backend/libspotify_backend.h>
#include <backend/libspotify_wrapper.h>
#include <backend/spotify_instance.h>
#include <backend/spotify_object.h>
#include <backend/webapi_backend.h>
#include <backend/webapi_objects/webapi_media_objects.h>
#include <fb2k/config.h>
#include <fb2k/file_info_filler.h>
#include <qwr/string_helpers.h>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <vector>
using namespace std::literals::string_view_literals;
using namespace sptf;
namespace
{
// input_impl::input_impl
class InputSpotify
: public input_stubs
, public LibSpotify_BackendUser
{
public:
InputSpotify() = default;
~InputSpotify();
// SpotifyBackendUser
void Finalize() override;
// input_impl::open
void open( service_ptr_t<file> m_file, const char* p_path, t_input_open_reason p_reason, abort_callback& p_abort );
// input_info_reader::get_info
void get_info( t_int32 subsong, file_info& p_info, abort_callback& p_abort );
// input_info_reader::get_file_stats
t_filestats get_file_stats( abort_callback& p_abort );
// input_decoder::initialize
void decode_initialize( t_int32 subsong, unsigned p_flags, abort_callback& p_abort );
// input_decoder::run
bool decode_run( audio_chunk& p_chunk, abort_callback& p_abort );
// input_decoder::seek
void decode_seek( double p_seconds, abort_callback& p_abort );
// input_decoder::can_seek
bool decode_can_seek();
// input_decoder::get_dynamic_info
bool decode_get_dynamic_info( file_info& p_out, double& p_timestamp_delta );
// input_decoder::get_dynamic_info_track
bool decode_get_dynamic_info_track( file_info& p_out, double& p_timestamp_delta );
// input_decoder::on_idle
void decode_on_idle( abort_callback& p_abort );
// input_impl::retag_set_info
void retag_set_info( t_int32 subsong, const file_info& p_info, abort_callback& p_abort );
// input_impl::retag_commit
void retag_commit( abort_callback& p_abort );
// input_info_reader::get_subsong_count
t_uint32 get_subsong_count();
// input_info_reader::get_subsong
t_uint32 get_subsong( unsigned song );
// input_entry::is_our_content_type
static bool g_is_our_content_type( const char* p_content_type );
// input_entry::is_our_path
static bool g_is_our_path( const char* p_full_path, const char* p_extension );
// input_entry::get_guid
static GUID g_get_guid();
// input_entry::get_name
static const char* g_get_name();
private:
LibSpotify_Backend& GetInitializedLibSpotify();
private:
bool usingLibSpotify_ = false;
bool hasDecoder_ = false;
std::optional<t_input_open_reason> openedReason_;
wrapper::Ptr<sp_track> track_;
std::string trackId_;
std::unordered_multimap<std::string, std::string> trackMeta_;
bool isFirstBlock_ = false;
int channels_{};
int sampleRate_{};
int bitRate_{};
};
} // namespace
namespace
{
std::string GetPlaybackErrorMessage( sp_error sp, const std::string& trackId, abort_callback& p_abort )
{
if ( sp == SP_ERROR_TRACK_NOT_PLAYABLE )
{
try
{
auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend();
const auto track = waBackend.GetTrack( trackId, p_abort, true );
if ( track->restrictions )
{
const auto& reason = track->restrictions.value()->reason;
if ( reason == "market"sv )
{
return "track is not accessible in your country";
}
else if ( reason == "product"sv )
{
return "track is not accessible for your subscription type";
}
else if ( reason == "explicit"sv )
{
return "track is explicit and your account is set to not play explicit content";
}
else
{
return reason;
}
}
}
catch ( const std::exception& )
{
}
}
return sp_error_message( sp );
}
} // namespace
namespace
{
InputSpotify::~InputSpotify()
{
try
{
Finalize();
}
catch ( const std::exception& )
{
}
}
void InputSpotify::Finalize()
{
if ( !usingLibSpotify_ )
{
return;
}
usingLibSpotify_ = false;
auto& lsBackend = SpotifyInstance::Get().GetLibSpotify_Backend();
{
if ( hasDecoder_ )
{
lsBackend.ReleaseDecoder( this );
}
}
if ( track_ )
{
lsBackend.ExecSpMutex( [&] {
track_.Release();
} );
}
lsBackend.UnregisterBackendUser( *this );
}
void InputSpotify::open( service_ptr_t<file> m_file, const char* p_path, t_input_open_reason p_reason, abort_callback& p_abort )
{
openedReason_ = p_reason;
if ( p_reason == input_open_info_write )
{
throw exception_io_denied_readonly();
}
auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend();
const auto spotifyObject = SpotifyFilteredTrack::Parse( p_path );
trackId_ = spotifyObject.Id();
const auto track = waBackend.GetTrack( trackId_, p_abort );
trackMeta_ = waBackend.GetMetaForTracks( nonstd::span<const std::unique_ptr<const WebApi_Track>>( &track, 1 ) )[0];
if ( p_reason == input_open_info_read )
{ // don't use LibSpotify stuff if it's not needed
return;
}
auto& lsBackend = GetInitializedLibSpotify();
auto pSession = lsBackend.GetInitializedSpSession( p_abort );
lsBackend.ExecSpMutex( [&] {
wrapper::Ptr<sp_link> link( sp_link_create_from_string( spotifyObject.ToUri().c_str() ) );
if ( !link )
{
throw exception_io_data( "Couldn't parse url" );
}
track_.Release();
switch ( sp_link_type( link ) )
{
case SP_LINKTYPE_TRACK:
{
track_ = sp_link_as_track( link );
break;
}
default:
{
throw exception_io_data( "Only track links should be passed to input" );
}
}
} );
while ( true )
{
// TODO: clean up here
const auto done = lsBackend.ExecSpMutex( [&] {
const auto sp = sp_track_error( track_ );
if ( SP_ERROR_OK == sp )
{
return true;
}
else if ( SP_ERROR_IS_LOADING == sp )
{
return false;
}
else
{
throw qwr::QwrException( fmt::format( "sp_track_error failed: {}", sp_error_message( sp ) ) );
}
} );
if ( done )
{
break;
}
p_abort.sleep( 0.05 );
}
bitRate_ = [] {
switch ( config::preferred_bitrate )
{
case config::BitrateSettings::Bitrate96k:
{
return 96;
}
case config::BitrateSettings::Bitrate160k:
{
return 160;
}
case config::BitrateSettings::Bitrate320k:
{
return 320;
}
default:
{
assert( false );
return 320;
}
}
}();
}
void InputSpotify::get_info( t_int32 subsong, file_info& p_info, abort_callback& p_abort )
{
if ( subsong )
{
throw std::exception( "This track does not have any sub-songs" );
}
sptf::fb2k::FillFileInfoWithMeta( trackMeta_, p_info );
if ( openedReason_ == input_open_decode )
{ // Use exact length when possible
auto& lsBackend = GetInitializedLibSpotify();
lsBackend.ExecSpMutex( [&] {
p_info.set_length( sp_track_duration( track_ ) / 1000.0 );
} );
}
}
foobar2000_io::t_filestats InputSpotify::get_file_stats( abort_callback& p_abort )
{
return t_filestats{};
}
void InputSpotify::decode_initialize( t_int32 subsong, unsigned p_flags, abort_callback& p_abort )
{
isFirstBlock_ = true;
if ( subsong )
{
throw std::exception( "This track does not have any sub-songs" );
}
if ( !( p_flags & input_flag_playback ) )
{
throw exception_io_denied();
}
auto& lsBackend = GetInitializedLibSpotify();
lsBackend.AcquireDecoder( this );
hasDecoder_ = true;
lsBackend.GetAudioBuffer().clear();
auto pSession = lsBackend.GetInitializedSpSession( p_abort );
lsBackend.ExecSpMutex( [&] {
const auto sp = sp_session_player_load( pSession, track_ );
if ( sp != SP_ERROR_OK )
{
throw qwr::QwrException( fmt::format( "sp_session_player_load failed: {}", GetPlaybackErrorMessage( sp, trackId_, p_abort ) ) );
}
sp_session_player_play( pSession, true );
} );
}
bool InputSpotify::decode_run( audio_chunk& p_chunk, abort_callback& p_abort )
{
bool isEof = false;
const auto dataReader = [&]( const AudioBuffer::AudioChunkHeader& header,
const uint16_t* data ) {
if ( header.eof )
{
isEof = true;
return;
}
channels_ = header.channels;
sampleRate_ = header.sampleRate;
p_chunk.set_data_fixedpoint( data,
header.size * sizeof( uint16_t ),
header.sampleRate,
header.channels,
16,
audio_chunk::channel_config_stereo );
};
auto& lsBackend = GetInitializedLibSpotify();
auto& buf = lsBackend.GetAudioBuffer();
if ( !buf.read( dataReader ) )
{
buf.wait_for_data( p_abort );
if ( !buf.read( dataReader ) )
{ // wait was aborted, ending playback
isEof = true;
}
}
if ( isEof )
{
lsBackend.ReleaseDecoder( this );
hasDecoder_ = false;
return false;
}
return true;
}
void InputSpotify::decode_seek( double p_seconds, abort_callback& p_abort )
{
isFirstBlock_ = true;
auto& lsBackend = GetInitializedLibSpotify();
lsBackend.GetAudioBuffer().clear();
auto pSession = lsBackend.GetInitializedSpSession( p_abort );
lsBackend.ExecSpMutex( [&] {
sp_session_player_seek( pSession, static_cast<int>( p_seconds * 1000 ) );
} );
}
bool InputSpotify::decode_can_seek()
{
return true;
}
bool InputSpotify::decode_get_dynamic_info( file_info& p_out, double& p_timestamp_delta )
{
if ( !isFirstBlock_ )
{
return false;
}
p_out.info_set_int( "CHANNELS", channels_ );
p_out.info_set_int( "SAMPLERATE", sampleRate_ );
p_out.info_set_bitrate( bitRate_ );
isFirstBlock_ = false;
return true;
}
bool InputSpotify::decode_get_dynamic_info_track( file_info& p_out, double& p_timestamp_delta )
{
return false;
}
void InputSpotify::decode_on_idle( abort_callback& p_abort )
{
(void)p_abort;
}
void InputSpotify::retag_set_info( t_int32 subsong, const file_info& p_info, abort_callback& p_abort )
{
throw exception_io_data();
}
void InputSpotify::retag_commit( abort_callback& p_abort )
{
throw exception_io_data();
}
bool InputSpotify::g_is_our_content_type( const char* p_content_type )
{
return false;
}
bool InputSpotify::g_is_our_path( const char* p_full_path, const char* p_extension )
{
(void)p_extension;
// accept only filtered input
return SpotifyFilteredTrack::IsValid( p_full_path, false );
}
t_uint32 InputSpotify::get_subsong_count()
{
return 1;
}
t_uint32 InputSpotify::get_subsong( unsigned song )
{
return song;
}
GUID InputSpotify::g_get_guid()
{
return guid::input;
}
const char* InputSpotify::g_get_name()
{
return SPTF_NAME ": decoder";
}
LibSpotify_Backend& InputSpotify::GetInitializedLibSpotify()
{
auto& lsBackend = SpotifyInstance::Get().GetLibSpotify_Backend();
if ( !usingLibSpotify_ )
{
lsBackend.RegisterBackendUser( *this );
usingLibSpotify_ = true;
}
return lsBackend;
}
} // namespace
namespace
{
input_factory_t<InputSpotify> g_input;
} // namespace
================================================
FILE: foo_spotify/fb2k/playback.cpp
================================================
#include <stdafx.h>
#include "playback.h"
#include <backend/libspotify_backend.h>
namespace sptf::fb2k
{
std::mutex PlayCallbacks::mutex_;
LibSpotify_Backend* PlayCallbacks::pLsBackend_ = nullptr;
PlayCallbacks::PlayCallbacks()
{
}
PlayCallbacks::~PlayCallbacks()
{
}
void PlayCallbacks::Initialize( LibSpotify_Backend& lsBackend )
{
std::lock_guard lg( mutex_ );
pLsBackend_ = &lsBackend;
}
void PlayCallbacks::Finalize()
{
std::lock_guard lg( mutex_ );
pLsBackend_ = nullptr;
}
unsigned PlayCallbacks::get_flags()
{
return ( flag_on_playback_stop | flag_on_playback_pause );
}
void PlayCallbacks::on_playback_pause( bool isPaused )
{
std::lock_guard lg( mutex_ );
if ( !pLsBackend_ )
{
return;
}
auto pSession = pLsBackend_->GetWhateverSpSession();
pLsBackend_->ExecSpMutex( [&] {
sp_session_player_play( pSession, !isPaused );
} );
}
void PlayCallbacks::on_playback_stop( play_control::t_stop_reason reason )
{
if ( play_control::stop_reason_starting_another == reason )
{
return;
}
std::lock_guard lg( mutex_ );
if ( !pLsBackend_ )
{
return;
}
auto pSession = pLsBackend_->GetWhateverSpSession();
pLsBackend_->ExecSpMutex( [&] {
sp_session_player_unload( pSession );
} );
}
} // namespace sptf::fb2k
namespace
{
play_callback_static_factory_t<sptf::fb2k::PlayCallbacks> g_play_callback_static;
}
================================================
FILE: foo_spotify/fb2k/playback.h
================================================
#pragma once
#include <mutex>
namespace sptf
{
class LibSpotify_Backend;
}
namespace sptf::fb2k
{
class PlayCallbacks
: public play_callback_static
{
public:
PlayCallbacks();
~PlayCallbacks();
static void Initialize( LibSpotify_Backend& lsBackend );
static void Finalize();
// play_callback_static
unsigned get_flags() override;
void on_playback_pause( bool isPaused ) override;
void on_playback_stop( play_control::t_stop_reason reason ) override;
void on_playback_starting( play_control::t_track_command p_command, bool p_paused ) override{};
void on_playback_new_track( metadb_handle_ptr p_track ) override{};
void on_playback_seek( double p_time ) override{};
void on_playback_edited( metadb_handle_ptr p_track ) override{};
void on_playback_dynamic_info( const file_info& p_info ) override{};
void on_playback_dynamic_info_track( const file_info& p_info ) override{};
void on_playback_time( double p_time ) override{};
void on_volume_change( float p_new_val ) override{};
private:
static std::mutex mutex_;
static LibSpotify_Backend* pLsBackend_;
};
} // namespace sptf::fb2k
================================================
FILE: foo_spotify/fb2k/playlist.cpp
================================================
#include <stdafx.h>
#include <backend/spotify_instance.h>
#include <backend/spotify_object.h>
#include <backend/webapi_backend.h>
#include <backend/webapi_objects/webapi_media_objects.h>
#include <qwr/abort_callback.h>
#include <qwr/thread_pool.h>
using namespace sptf;
namespace
{
class PlaylistCallbackSpotify : public playlist_callback_static
{
public:
unsigned get_flags() override;
void on_default_format_changed() override
{
}
void on_item_ensure_visible( t_size p_playlist, t_size p_idx ) override
{
}
void on_item_focus_change( t_size p_playlist, t_size p_from, t_size p_to ) override
{
}
void on_items_added( t_size p_playlist, t_size p_start, metadb_handle_list_cref p_data, const pfc::bit_array& p_selection ) override
{
}
void on_items_removing( t_size p_playlist, const pfc::bit_array& p_mask, t_size p_old_count, t_size p_new_count ) override
{
}
void on_items_removed( t_size p_playlist, const pfc::bit_array& p_mask, t_size p_old_count, t_size p_new_count ) override
{
}
void on_items_reordered( t_size p_playlist, const t_size* p_order, t_size p_count ) override
{
}
void on_items_replaced( t_size p_playlist, const pfc::bit_array& p_mask, const pfc::list_base_const_t<t_on_items_replaced_entry>& p_data ) override
{
}
void on_items_selection_change( t_size p_playlist, const pfc::bit_array& p_affected, const pfc::bit_array& p_state ) override
{
}
void on_items_modified( t_size p_playlist, const pfc::bit_array& p_mask ) override
{
}
void on_items_modified_fromplayback( t_size p_playlist, const pfc::bit_array& p_mask, play_control::t_display_level p_level ) override
{
}
void on_playback_order_changed( t_size p_new_index ) override
{
}
void on_playlist_activate( t_size p_old, t_size p_new ) override;
void on_playlist_created( t_size p_index, const char* p_name, t_size p_name_len ) override
{
}
void on_playlist_locked( t_size p_playlist, bool p_locked ) override
{
}
void on_playlist_renamed( t_size p_index, const char* p_new_name, t_size p_new_name_len ) override
{
}
void on_playlists_removing( const pfc::bit_array& p_mask, t_size p_old_count, t_size p_new_count ) override
{
}
void on_playlists_removed( const pfc::bit_array& p_mask, t_size p_old_count, t_size p_new_count ) override
{
}
void on_playlists_reorder( const t_size* p_order, t_size p_count ) override
{
}
};
} // namespace
namespace
{
unsigned PlaylistCallbackSpotify::get_flags()
{
return flag_on_playlist_activate;
}
void PlaylistCallbackSpotify::on_playlist_activate( t_size p_old, t_size p_new )
{
if ( p_old == p_new )
{
return;
}
metadb_handle_list items;
playlist_manager::get()->playlist_get_all_items( p_new, items );
std::vector<std::string> trackIds;
for ( const auto& pMeta: qwr::pfc_x::Make_Stl_CRef( items ) )
{
const char* path = pMeta->get_location().get_path();
if ( !SpotifyFilteredTrack::IsValid( path, false ) )
{
continue;
}
trackIds.emplace_back( SpotifyFilteredTrack::Parse( path ).Id() );
}
if ( trackIds.empty() )
{
return;
}
auto& threadPool = SpotifyInstance::Get().GetThreadPool();
threadPool.AddTask( [trackIds = std::move( trackIds )] {
try
{
auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend();
// pre-cache tracks
qwr::TimedAbortCallback tac1;
const auto tracks = waBackend.GetTracks( trackIds, tac1 );
const auto artistIds =
tracks
| ranges::views::transform( []( const auto& pTrack ) -> std::string { return pTrack->artists[0]->id; } )
| ranges::to_vector;
// pre-cache artists
qwr::TimedAbortCallback tac2;
waBackend.RefreshCacheForArtists( artistIds, tac2 );
}
catch ( const std::exception& e )
{
FB2K_console_formatter() << SPTF_UNDERSCORE_NAME " (error):\n"
<< "Failed to refresh tracks:\n"
<< e.what();
}
} );
}
} // namespace
namespace
{
service_factory_single_t<PlaylistCallbackSpotify> g_playlist_callback_static;
}
================================================
FILE: foo_spotify/fb2k/playlist_loader.cpp
================================================
#include <stdafx.h>
#include <backend/spotify_instance.h>
#include <backend/spotify_object.h>
#include <backend/webapi_backend.h>
#include <backend/webapi_objects/webapi_media_objects.h>
#include <fb2k/file_info_filler.h>
#include <qwr/error_popup.h>
#include <qwr/string_helpers.h>
using namespace std::literals::string_view_literals;
using namespace sptf;
namespace
{
class PlaylistLoaderSpotify : public playlist_loader
{
public:
PlaylistLoaderSpotify() = default;
public:
// playlist_loader
//! Parses specified playlist file into list of playable locations. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown.
//! @param p_path Path of playlist file to parse. Used for relative path handling purposes (p_file parameter is used for actual file access).
//! @param p_file File interface to use for reading. Read/write pointer must be set to beginning by caller before calling.
//! @param p_callback Callback object receiving enumerated playable item locations.
void open( const char* p_path, const service_ptr_t<file>& p_file, playlist_loader_callback::ptr p_callback, abort_callback& p_abort ) override;
//! Writes a playlist file containing specific item list to specified file. Will fail (pfc::exception_not_implemented) if specified playlist_loader is read-only (can_write() returns false).
//! @param p_path Path of playlist file to write. Used for relative path handling purposes (p_file parameter is used for actual file access).
//! @param p_file File interface to use for writing. Caller should ensure that the file is empty (0 bytes long) before calling.
//! @param p_data List of items to save to playlist file.
//! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file.
void write( const char* p_path, const service_ptr_t<file>& p_file, metadb_handle_list_cref p_data, abort_callback& p_abort ) override;
//! Returns extension of file format handled by this playlist_loader implementation (a UTF-8 encoded null-terminated string).
const char* get_extension() override;
//! Returns whether this playlist_loader implementation supports writing. If can_write() returns false, all write() calls will fail.
bool can_write() override;
//! Returns whether specified content type is one of playlist types supported by this playlist_loader implementation or not.
//! @param p_content_type Content type to query, a UTF-8 encoded null-terminated string.
bool is_our_content_type( const char* p_content_type ) override;
//! Returns whether playlist format extension supported by this implementation should be listed on file types associations page.
bool is_associatable() override;
/*
//! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. \n
//! Equivalent to g_load_playlist_filehint(NULL,p_path,p_callback).
//! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string.
//! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation.
static void g_load_playlist( const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort );
//! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown.
//! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string.
//! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation.
//! @param fileHint File object to read from, can be NULL if not available.
static void g_load_playlist_filehint( file::ptr fileHint, const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort );
//! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, returns false; returns true upon successful playlist load.
//! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string.
//! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation.
//! @param fileHint File object to read from, can be NULL if not available.
static bool g_try_load_playlist( file::ptr fileHint, const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort );
//! Saves specified list of locations into a playlist file. Throws exception_io or derivatives on failure, exception_aborted on abort.
//! @param p_path Filesystem path to save playlist to, a UTF-8 encoded null-terminated string.
//! @param p_data List of items to save to playlist file.
//! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file.
static void g_save_playlist( const char* p_path, metadb_handle_list_cref p_data, abort_callback& p_abort );
//! Processes specified path to generate list of playable items. Includes recursive directory/archive enumeration. \n
//! Does not touch playlist files encountered - use g_process_path_ex() if specified path is possibly a playlist file; playlist files found inside directories or archives are ignored regardless.\n
//! Warning: caller must handle exceptions which will occur in case of I/O failure.
//! @param p_path Filesystem path to process; a UTF-8 encoded null-terminated string.
//! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation.
//! @param p_type Origin of p_path string. Reserved for internal use in recursive calls, should be left at default value; it controls various internal behaviors.
static void g_process_path( const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort, playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested );
//! Calls attempts to process specified path as a playlist; if that fails (i.e. not a playlist), calls g_process_path with same parameters. See g_process_path for parameter descriptions. \n
//! Warning: caller must handle exceptions which will occur in case of I/O failure or playlist parsing failure.
//! @returns True if specified path was processed as a playlist file, false otherwise (relevant in some scenarios where output is sorted after loading, playlist file contents should not be sorted).
static bool g_process_path_ex( const char* p_path, playlist_loader_callback::ptr p_callback, abort_callback& p_abort, playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested );
*/
};
} // namespace
namespace
{
struct SkippedTrack
{
std::string name;
std::string reason;
};
std::vector<SkippedTrack>
TransformToSkippedTracks( nonstd::span<const std::unique_ptr<const WebApi_LocalTrack>> tracks )
{
return ranges::views::transform( tracks,
[&]( const auto& pTrack ) -> SkippedTrack {
return { ( pTrack->name ? *pTrack->name : pTrack->uri ), "local track" };
} )
| ranges::to_vector;
}
std::tuple<std::vector<std::unique_ptr<const sptf::WebApi_Track>>, std::vector<SkippedTrack>>
GetTracks( const SpotifyObject spotifyObject, abort_callback& p_abort )
{
auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend();
const auto preCacheArtists = [&waBackend]( const auto& tracks, auto& p_abort ) {
const auto artistIds =
tracks
| ranges::views::transform( []( const auto& pTrack ) -> std::string { return pTrack->artists[0]->id; } )
| ranges::to_vector;
waBackend.RefreshCacheForArtists( artistIds, p_abort );
};
if ( spotifyObject.type == "album" )
{
auto tracks = waBackend.GetTracksFromAlbum( spotifyObject.id, p_abort );
preCacheArtists( tracks, p_abort );
return { std::move( tracks ), std::vector<SkippedTrack>{} };
}
else if ( spotifyObject.type == "playlist" )
{
auto [tracks, localTracks] = waBackend.GetTracksFromPlaylist( spotifyObject.id, p_abort );
preCacheArtists( tracks, p_abort );
// ??? Dunno why this is required. Smth to do with structured bindings and RVO.
return { std::move( tracks ), TransformToSkippedTracks( localTracks ) };
}
else if ( spotifyObject.type == "artist" )
{
return { waBackend.GetTopTracksForArtist( spotifyObject.id, p_abort ), std::vector<SkippedTrack>{} };
}
else if ( spotifyObject.type == "track" )
{
std::vector<std::unique_ptr<const WebApi_Track>> tmp;
tmp.emplace_back( waBackend.GetTrack( spotifyObject.id, p_abort ) );
return { std::move( tmp ), std::vector<SkippedTrack>{} };
}
else if ( spotifyObject.type == "local" )
{
std::vector<SkippedTrack> tmp;
tmp.emplace_back( SkippedTrack{ spotifyObject.id, "local track" } );
return { std::vector<std::unique_ptr<const sptf::WebApi_Track>>{}, tmp };
}
else
{
throw qwr::QwrException( "Unexpected Spotify object type: {}", spotifyObject.type );
}
}
void ReportSkippedTracks( nonstd::span<const SkippedTrack> skippedTracks )
{
if ( skippedTracks.empty() )
{
return;
}
const auto formatedSkippedTracks = ranges::views::transform( skippedTracks,
[&]( const auto& track ) -> std::string {
return fmt::format( "{}: {}", track.name, track.reason );
} )
| ranges::to_vector;
const auto msg = fmt::format( "Failed to add the following tracks:\n"
" {}\n",
fmt::join( formatedSkippedTracks, "\n " ) );
qwr::ReportErrorWithPopup( SPTF_UNDERSCORE_NAME, msg );
}
} // namespace
namespace
{
void PlaylistLoaderSpotify::open( const char* p_path, const service_ptr_t<file>& p_file, playlist_loader_callback::ptr p_callback, abort_callback& p_abort )
{
const auto spotifyObject = [&] {
try
{
return SpotifyObject( p_path );
}
catch ( const qwr::QwrException& )
{
throw exception_io_unsupported_format();
}
}();
p_callback->on_progress( p_path );
auto& waBackend = SpotifyInstance::Get().GetWebApi_Backend();
auto [tracks, skippedTracks] = GetTracks( spotifyObject, p_abort );
std::vector<std::string> relinkedTracks;
const auto tracksMeta = waBackend.GetMetaForTracks( tracks );
for ( const auto& [track, trackMeta]: ranges::views::zip( tracks, tracksMeta ) )
{
file_info_impl f_info;
sptf::fb2k::FillFileInfoWithMeta( trackMeta, f_info );
metadb_handle_ptr f_handle;
p_callback->handle_create( f_handle, make_playable_location( SpotifyFilteredTrack( track->id ).ToSchema().c_str(), 0 ) );
p_callback->on_entry_info( f_handle, playlist_loader_callback::entry_user_requested, filestats_invalid, f_info, false );
}
ReportSkippedTracks( skippedTracks );
}
void PlaylistLoaderSpotify::write( const char* p_path, const service_ptr_t<file>& p_file, metadb_handle_list_cref p_data, abort_callback& p_abort )
{
throw pfc::exception_not_implemented();
}
const char* PlaylistLoaderSpotify::get_extension()
{
return "";
}
bool PlaylistLoaderSpotify::can_write()
{
return false;
}
bool PlaylistLoaderSpotify::is_our_content_type( const char* p_content_type )
{
constexpr auto mime = "text/html; charset=utf-8"sv;
const auto lowerContentType = [&p_content_type]() {
// Content type should not contain anything other than ASCII
return std::string_view{ p_content_type }
| ranges::views::transform( []( auto i ) { return static_cast<char>( ::tolower( i ) ); } )
| ranges::to<std::string>;
}();
return ( mime == lowerContentType );
}
bool PlaylistLoaderSpotify::is_associatable()
{
return false;
}
} // namespace
namespace
{
playlist_loader_factory_t<PlaylistLoaderSpotify> g_playlist_loader;
} // namespace
================================================
FILE: foo_spotify/foo_spotify.rc
================================================
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#define APSTUDIO_HIDDEN_SYMBOLS
#include "windows.h"
#undef APSTUDIO_HIDDEN_SYMBOLS
#include <atlres.h>
#include <component_defines.h>
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_NOT_AUTH DIALOGEX 0, 0, 249, 78
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,30,56,78,14
PUSHBUTTON "Authenticate...",IDC_BTN_OPEN_PREF,141,56,78,14
LTEXT "Static",IDC_STATIC_NOT_AUTH,7,7,235,41
END
IDD_PREF_TAB_HOST DIALOGEX 0, 0, 334, 288
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_CLIPSIBLINGS
EXSTYLE WS_EX_CONTROLPARENT
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
CONTROL "Tabs",IDC_TAB_HOST,"SysTabControl32",WS_TABSTOP,0,0,332,287
END
IDD_PREF_TAB_AUTH DIALOGEX 0, 0, 327, 268
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
GROUPBOX "LibSpotify",IDC_STATIC,11,13,282,40
PUSHBUTTON "LS Login",IDC_BTN_LOGIN_LIBSPOTIFY,17,28,50,14
LTEXT "LS status:",IDC_STATIC_LIBSPOTIFY_STATUS,72,30,217,19
GROUPBOX "WebAPI",IDC_STATIC,11,61,282,40
PUSHBUTTON "WA Login",IDC_BTN_LOGIN_WEBAPI,17,76,50,14
LTEXT "WA status:",IDC_STATIC_WEBAPI_STATUS,72,78,217,19
END
IDD_PREF_TAB_PLAYBACK DIALOGEX 0, 0, 327, 268
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
GROUPBOX "Playback",IDC_STATIC,11,13,285,87
LTEXT "Prefered bitrate",IDC_STATIC,22,28,52,8
COMBOBOX IDC_COMBO_BITRATE,22,39,126,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
CONTROL "Enable volume normalization",IDC_CHECK_NORMALIZE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,24,62,106,10
CONTROL "Enable private mode",IDC_CHECK_PRIVATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,24,77,81,10
GROUPBOX "Cache",IDC_STATIC,11,114,285,99
LTEXT "Maximum size in MB (0 - disabled)",IDC_STATIC,29,132,107,8
CONTROL "",IDC_SLIDER_CACHE_MB,"msctls_trackbar32",TBS_AUTOTICKS | TBS_TOOLTIPS | TBS_NOTIFYBEFOREMOVE | WS_TABSTOP,19,142,148,15
EDITTEXT IDC_EDIT_CACHE_MB,170,141,40,14,ES_AUTOHSCROLL | ES_NUMBER
LTEXT "Maximum size in % of total free space (0 - disabled)",IDC_STATIC,29,169,122,8
CONTROL "",IDC_SLIDER_CACHE_PERCENT,"msctls_trackbar32",TBS_AUTOTICKS | TBS_TOOLTIPS | TBS_NOTIFYBEFOREMOVE | WS_TABSTOP,19,179,148,15
EDITTEXT IDC_EDIT_CACHE_PERCENT,170,178,40,14,ES_AUTOHSCROLL | ES_NUMBER
END
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_NOT_AUTH, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 242
TOPMARGIN, 7
BOTTOMMARGIN, 70
END
IDD_PREF_TAB_HOST, DIALOG
BEGIN
END
IDD_PREF_TAB_AUTH, DIALOG
BEGIN
END
IDD_PREF_TAB_PLAYBACK, DIALOG
BEGIN
END
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
//
IDD_PREF_TAB_HOST AFX_DIALOG_LAYOUT
BEGIN
0
END
IDD_NOT_AUTH AFX_DIALOG_LAYOUT
BEGIN
0
END
IDD_PREF_TAB_AUTH AFX_DIALOG_LAYOUT
BEGIN
0
END
IDD_PREF_TAB_PLAYBACK AFX_DIALOG_LAYOUT
BEGIN
0
END
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
"#include ""windows.h""\r\n"
"#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
"\r\n"
"#include <atlres.h>\r\n"
"#include <component_defines.h>\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"#include ""foo_spotify.rc2""\0"
END
#endif // APSTUDIO_INVOKED
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
#include "foo_spotify.rc2"
//////////////////////////////////////////////////////////////
gitextract_nrm_lg86/
├── .appveyor.yml
├── .bandit
├── .codacy.yml
├── .gitattributes
├── .gitignore
├── .gitmodules
├── .license_index.txt
├── CHANGELOG.md
├── LICENSE
├── README.md
├── THIRD_PARTY_NOTICES.md
├── VERSION
├── foo_spotify/
│ ├── .clang-format
│ ├── .clang-tidy
│ ├── backend/
│ │ ├── audio_buffer.cpp
│ │ ├── audio_buffer.h
│ │ ├── libspotify_backend.cpp
│ │ ├── libspotify_backend.h
│ │ ├── libspotify_backend_user.h
│ │ ├── libspotify_key.h
│ │ ├── libspotify_wrapper.h
│ │ ├── spotify_instance.cpp
│ │ ├── spotify_instance.h
│ │ ├── spotify_object.cpp
│ │ ├── spotify_object.h
│ │ ├── webapi_auth.cpp
│ │ ├── webapi_auth.h
│ │ ├── webapi_auth_scopes.cpp
│ │ ├── webapi_auth_scopes.h
│ │ ├── webapi_backend.cpp
│ │ ├── webapi_backend.h
│ │ ├── webapi_cache.cpp
│ │ ├── webapi_cache.h
│ │ └── webapi_objects/
│ │ ├── webapi_album.cpp
│ │ ├── webapi_album.h
│ │ ├── webapi_artist.cpp
│ │ ├── webapi_artist.h
│ │ ├── webapi_image.cpp
│ │ ├── webapi_image.h
│ │ ├── webapi_media_objects.h
│ │ ├── webapi_paging_object.cpp
│ │ ├── webapi_paging_object.h
│ │ ├── webapi_playlist_track.cpp
│ │ ├── webapi_playlist_track.h
│ │ ├── webapi_restriction.cpp
│ │ ├── webapi_restriction.h
│ │ ├── webapi_track.cpp
│ │ ├── webapi_track.h
│ │ ├── webapi_track_link.cpp
│ │ ├── webapi_track_link.h
│ │ ├── webapi_user.cpp
│ │ └── webapi_user.h
│ ├── component_defines.h
│ ├── component_guids.h
│ ├── component_paths.cpp
│ ├── component_paths.h
│ ├── component_urls.h
│ ├── dllmain.cpp
│ ├── fb2k/
│ │ ├── acfu_integration.cpp
│ │ ├── advanced_config.cpp
│ │ ├── advanced_config.h
│ │ ├── album_art.cpp
│ │ ├── config.cpp
│ │ ├── config.h
│ │ ├── file_info_filler.cpp
│ │ ├── file_info_filler.h
│ │ ├── filesystem.cpp
│ │ ├── input.cpp
│ │ ├── playback.cpp
│ │ ├── playback.h
│ │ ├── playlist.cpp
│ │ └── playlist_loader.cpp
│ ├── foo_spotify.rc
│ ├── foo_spotify.rc2
│ ├── foo_spotify.vcxproj
│ ├── foo_spotify.vcxproj.filters
│ ├── packages.config
│ ├── resource.h
│ ├── stdafx.cpp
│ ├── stdafx.h
│ ├── ui/
│ │ ├── ui_ipref_tab.h
│ │ ├── ui_not_auth.cpp
│ │ ├── ui_not_auth.h
│ │ ├── ui_pref_tab_auth.cpp
│ │ ├── ui_pref_tab_auth.h
│ │ ├── ui_pref_tab_manager.cpp
│ │ ├── ui_pref_tab_manager.h
│ │ ├── ui_pref_tab_playback.cpp
│ │ └── ui_pref_tab_playback.h
│ └── utils/
│ ├── abort_manager.cpp
│ ├── abort_manager.h
│ ├── async_mutex.hpp
│ ├── cred_prompt.cpp
│ ├── cred_prompt.h
│ ├── json_macro_fix.h
│ ├── json_std_extenders.h
│ ├── rps_limiter.cpp
│ ├── rps_limiter.h
│ ├── secure_vector.h
│ ├── sleeper.cpp
│ └── sleeper.h
├── libspotify/
│ ├── ChangeLog
│ ├── LICENSE
│ ├── README
│ ├── docs/
│ │ ├── html/
│ │ │ ├── annotated.html
│ │ │ ├── api_8h.html
│ │ │ ├── browse_8c-example.html
│ │ │ ├── classes.html
│ │ │ ├── doxygen.css
│ │ │ ├── examples.html
│ │ │ ├── files.html
│ │ │ ├── functions.html
│ │ │ ├── functions_vars.html
│ │ │ ├── globals.html
│ │ │ ├── globals_0x69.html
│ │ │ ├── globals_0x73.html
│ │ │ ├── globals_0x74.html
│ │ │ ├── globals_defs.html
│ │ │ ├── globals_enum.html
│ │ │ ├── globals_eval.html
│ │ │ ├── globals_func.html
│ │ │ ├── globals_type.html
│ │ │ ├── group__album.html
│ │ │ ├── group__albumbrowse.html
│ │ │ ├── group__artist.html
│ │ │ ├── group__artistbrowse.html
│ │ │ ├── group__error.html
│ │ │ ├── group__image.html
│ │ │ ├── group__inbox.html
│ │ │ ├── group__link.html
│ │ │ ├── group__playlist.html
│ │ │ ├── group__search.html
│ │ │ ├── group__session.html
│ │ │ ├── group__toplist.html
│ │ │ ├── group__track.html
│ │ │ ├── group__types.html
│ │ │ ├── group__user.html
│ │ │ ├── index.html
│ │ │ ├── jukebox_8c-example.html
│ │ │ ├── modules.html
│ │ │ ├── search_8c-example.html
│ │ │ ├── structsp__audio__buffer__stats.html
│ │ │ ├── structsp__audioformat.html
│ │ │ ├── structsp__offline__sync__status.html
│ │ │ ├── structsp__playlist__callbacks.html
│ │ │ ├── structsp__playlistcontainer__callbacks.html
│ │ │ ├── structsp__session__callbacks.html
│ │ │ ├── structsp__session__config.html
│ │ │ ├── structsp__subscribers.html
│ │ │ ├── tabs.css
│ │ │ └── toplist_8c-example.html
│ │ └── images/
│ │ └── spotify-core.txt
│ ├── examples/
│ │ ├── Makefile
│ │ ├── Randomify/
│ │ │ ├── English.lproj/
│ │ │ │ ├── InfoPlist.strings
│ │ │ │ └── MainMenu.xib
│ │ │ ├── Randomify-Info.plist
│ │ │ ├── Randomify.xcodeproj/
│ │ │ │ └── project.pbxproj
│ │ │ ├── RandomifyAppDelegate.h
│ │ │ ├── RandomifyAppDelegate.m
│ │ │ ├── SpotifyPlaylist.h
│ │ │ ├── SpotifyPlaylist.m
│ │ │ ├── SpotifySession.h
│ │ │ ├── SpotifySession.m
│ │ │ ├── SpotifyTrack.h
│ │ │ ├── SpotifyTrack.m
│ │ │ └── main.m
│ │ ├── appkey.c
│ │ ├── common.mk
│ │ ├── jukebox/
│ │ │ ├── Makefile
│ │ │ ├── alsa-audio.c
│ │ │ ├── audio.c
│ │ │ ├── audio.h
│ │ │ ├── dummy-audio.c
│ │ │ ├── jukebox.c
│ │ │ ├── openal-audio.c
│ │ │ ├── osx/
│ │ │ │ └── jukebox.xcodeproj/
│ │ │ │ └── project.pbxproj
│ │ │ ├── osx-audio.c
│ │ │ ├── playtrack.c
│ │ │ └── queue.h
│ │ ├── localfiles/
│ │ │ ├── Makefile
│ │ │ ├── main.c
│ │ │ └── main.h
│ │ ├── spshell/
│ │ │ ├── Makefile
│ │ │ ├── browse.c
│ │ │ ├── cmd.c
│ │ │ ├── cmd.h
│ │ │ ├── inbox.c
│ │ │ ├── osx/
│ │ │ │ └── spshell.xcodeproj/
│ │ │ │ └── project.pbxproj
│ │ │ ├── playlist.c
│ │ │ ├── scrobbling.c
│ │ │ ├── search.c
│ │ │ ├── spshell.c
│ │ │ ├── spshell.h
│ │ │ ├── spshell_posix.c
│ │ │ ├── spshell_win32.c
│ │ │ ├── star.c
│ │ │ ├── test.c
│ │ │ ├── toplist.c
│ │ │ └── win32/
│ │ │ ├── spshell.vcproj
│ │ │ ├── spshell.vcxproj
│ │ │ └── spshell.vcxproj.filters
│ │ └── stub/
│ │ ├── Makefile
│ │ ├── main.c
│ │ └── main.h
│ ├── include/
│ │ └── libspotify/
│ │ └── api.h
│ ├── lib/
│ │ └── libspotify.lib
│ └── licenses.xhtml
├── licenses/
│ ├── JSON for Modern C++.txt
│ ├── LibSpotify.txt
│ ├── PFC.txt
│ ├── WTL.txt
│ ├── fmt.txt
│ ├── foobar2000 SDK.txt
│ ├── range-v3.txt
│ ├── ring-span-lite.txt
│ └── span-lite.txt
├── props/
│ ├── local_dependencies/
│ │ └── libspotify.props
│ └── submodules/
│ ├── fb2k_utils.props
│ └── submodules.props
├── scripts/
│ ├── README.md
│ ├── call_wrapper.py
│ ├── download_submodules.py
│ ├── pack_component.py
│ ├── setup.py
│ └── update_gh_pages.py
└── workspaces/
└── foo_spotify.sln
SYMBOL INDEX (452 symbols across 106 files)
FILE: foo_spotify/backend/audio_buffer.cpp
type sptf (line 7) | namespace sptf
FILE: foo_spotify/backend/audio_buffer.h
function namespace (line 9) | namespace sptf
FILE: foo_spotify/backend/libspotify_backend.cpp
type sptf (line 31) | namespace sptf
function sp_session (line 162) | sp_session* LibSpotify_Backend::GetInitializedSpSession( abort_callback&...
function sp_session (line 177) | sp_session* LibSpotify_Backend::GetWhateverSpSession()
function AudioBuffer (line 488) | AudioBuffer& LibSpotify_Backend::GetAudioBuffer()
FILE: foo_spotify/backend/libspotify_backend.h
function namespace (line 13) | namespace sptf
FILE: foo_spotify/backend/libspotify_backend_user.h
function namespace (line 3) | namespace sptf
FILE: foo_spotify/backend/libspotify_wrapper.h
function namespace (line 7) | namespace sptf::wrapper
FILE: foo_spotify/backend/spotify_instance.cpp
type sptf (line 14) | namespace sptf
function SpotifyInstance (line 17) | SpotifyInstance& SpotifyInstance::Get()
function AbortManager (line 61) | AbortManager& SpotifyInstance::GetAbortManager()
function LibSpotify_Backend (line 68) | LibSpotify_Backend& SpotifyInstance::GetLibSpotify_Backend()
function WebApi_Backend (line 75) | WebApi_Backend& SpotifyInstance::GetWebApi_Backend()
FILE: foo_spotify/backend/spotify_instance.h
function namespace (line 5) | namespace qwr
function namespace (line 10) | namespace sptf
FILE: foo_spotify/backend/spotify_object.cpp
type sptf (line 9) | namespace sptf
function SpotifyFilteredTrack (line 98) | SpotifyFilteredTrack SpotifyFilteredTrack::Parse( std::string_view inp...
FILE: foo_spotify/backend/spotify_object.h
function namespace (line 5) | namespace sptf
FILE: foo_spotify/backend/webapi_auth.cpp
function GenerateCodeVerifier (line 55) | std::wstring GenerateCodeVerifier()
function Sha256Hash (line 78) | std::vector<uint8_t> Sha256Hash( nonstd::span<const uint8_t> data )
function GenerateChallengeCode (line 131) | std::wstring GenerateChallengeCode( const std::wstring& codeVerifier )
function OpenAuthConfirmationInBrowser (line 166) | void OpenAuthConfirmationInBrowser( const std::wstring& url )
type sptf (line 173) | namespace sptf
type AuthData (line 176) | struct AuthData
function to_json (line 184) | void to_json( nlohmann::json& j, const AuthData& p )
function from_json (line 192) | void from_json( const nlohmann::json& j, AuthData& p )
FILE: foo_spotify/backend/webapi_auth.h
function namespace (line 9) | namespace web::http::experimental::listener
function namespace (line 14) | namespace sptf
FILE: foo_spotify/backend/webapi_auth_scopes.cpp
type sptf (line 9) | namespace sptf
function to_json (line 69) | void to_json( nlohmann::json& j, const WebApiAuthScopes& p )
function from_json (line 94) | void from_json( const nlohmann::json& j, WebApiAuthScopes& p )
FILE: foo_spotify/backend/webapi_auth_scopes.h
function namespace (line 7) | namespace sptf
FILE: foo_spotify/backend/webapi_backend.cpp
type sptf (line 37) | namespace sptf
function WebApiAuthorizer (line 64) | WebApiAuthorizer& WebApi_Backend::GetAuthorizer()
FILE: foo_spotify/backend/webapi_backend.h
function namespace (line 14) | namespace sptf
FILE: foo_spotify/backend/webapi_cache.cpp
class DownloadStatus (line 22) | class DownloadStatus : public IBindStatusCallback
function HRESULT (line 123) | HRESULT DownloadStatus::OnProgress( ULONG ulProgress, ULONG ulProgressMax,
type sptf (line 132) | namespace sptf
FILE: foo_spotify/backend/webapi_cache.h
function namespace (line 10) | namespace sptf
function IsCached (line 120) | bool IsCached( const std::string& id )
type WebApi_User (line 131) | struct WebApi_User
function class (line 133) | class WebApi_UserCache
function class (line 148) | class WebApi_ImageCache
FILE: foo_spotify/backend/webapi_objects/webapi_album.cpp
type sptf (line 8) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_album.h
function namespace (line 7) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_artist.cpp
type sptf (line 8) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_artist.h
function namespace (line 5) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_image.cpp
type sptf (line 5) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_image.h
function namespace (line 6) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_paging_object.cpp
type sptf (line 7) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_paging_object.h
function namespace (line 7) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_playlist_track.cpp
type sptf (line 8) | namespace sptf
function to_json (line 11) | void to_json( nlohmann::json& j, const WebApi_PlaylistTrack& p )
function from_json (line 19) | void from_json( const nlohmann::json& j, WebApi_PlaylistTrack& p )
FILE: foo_spotify/backend/webapi_objects/webapi_playlist_track.h
function namespace (line 7) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_restriction.cpp
type sptf (line 5) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_restriction.h
function namespace (line 6) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_track.cpp
type sptf (line 8) | namespace sptf
function from_json (line 32) | void from_json( const nlohmann::json& nlohmann_json_j, WebApi_Track_Si...
function from_json (line 51) | void from_json( const nlohmann::json& nlohmann_json_j, WebApi_Track& n...
FILE: foo_spotify/backend/webapi_objects/webapi_track.h
function namespace (line 7) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_track_link.cpp
type sptf (line 8) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_track_link.h
function namespace (line 5) | namespace sptf
FILE: foo_spotify/backend/webapi_objects/webapi_user.cpp
type sptf (line 7) | namespace sptf
function from_json (line 15) | void from_json( const nlohmann::json& nlohmann_json_j, WebApi_User& nl...
FILE: foo_spotify/backend/webapi_objects/webapi_user.h
function namespace (line 6) | namespace sptf
FILE: foo_spotify/component_guids.h
function namespace (line 3) | namespace sptf::guid
FILE: foo_spotify/component_paths.cpp
type sptf::path (line 9) | namespace sptf::path
function LibSpotifyCache (line 12) | fs::path LibSpotifyCache()
function LibSpotifySettings (line 16) | fs::path LibSpotifySettings()
function WebApiCache (line 21) | fs::path WebApiCache()
function WebApiSettings (line 25) | fs::path WebApiSettings()
FILE: foo_spotify/component_paths.h
function namespace (line 5) | namespace sptf::path
FILE: foo_spotify/component_urls.h
function namespace (line 3) | namespace sptf::url
FILE: foo_spotify/dllmain.cpp
function HasComponent (line 19) | bool HasComponent( const std::u8string& componentName )
class sptf_initquit (line 40) | class sptf_initquit : public initquit
method on_init (line 43) | void on_init() override
method on_quit (line 55) | void on_quit() override
function BOOL (line 67) | BOOL WINAPI DllMain( HINSTANCE ins, DWORD reason, [[maybe_unused]] LPVOI...
FILE: foo_spotify/fb2k/acfu_integration.cpp
class SptfSource (line 12) | class SptfSource
function GUID (line 34) | GUID SptfSource::get_guid()
FILE: foo_spotify/fb2k/advanced_config.cpp
type sptf::config::advanced (line 17) | namespace sptf::config::advanced
FILE: foo_spotify/fb2k/advanced_config.h
function namespace (line 5) | namespace sptf::config::advanced
FILE: foo_spotify/fb2k/album_art.cpp
class AlbumArtExtractorInstanceSpotify (line 13) | class AlbumArtExtractorInstanceSpotify : public album_art_extractor_inst...
class AlbumArtExtractorSpotify (line 26) | class AlbumArtExtractorSpotify : public album_art_extractor
method AlbumArtExtractorSpotify (line 29) | AlbumArtExtractorSpotify() = default;
function album_art_data_ptr (line 49) | album_art_data_ptr AlbumArtExtractorInstanceSpotify::query( const GUID& ...
function album_art_extractor_instance_ptr (line 90) | album_art_extractor_instance_ptr AlbumArtExtractorSpotify::open( file_pt...
FILE: foo_spotify/fb2k/config.cpp
type sptf::config (line 5) | namespace sptf::config
FILE: foo_spotify/fb2k/config.h
function namespace (line 5) | namespace sptf::config
FILE: foo_spotify/fb2k/file_info_filler.cpp
function FillMetaInfo (line 10) | void FillMetaInfo( const std::unordered_multimap<std::string, std::strin...
function FillTechnicalInfo (line 61) | void FillTechnicalInfo( const std::unordered_multimap<std::string, std::...
type sptf::fb2k (line 71) | namespace sptf::fb2k
function FillFileInfoWithMeta (line 74) | void FillFileInfoWithMeta( const std::unordered_multimap<std::string, ...
FILE: foo_spotify/fb2k/file_info_filler.h
function namespace (line 6) | namespace sptf::fb2k
FILE: foo_spotify/fb2k/filesystem.cpp
class FilesystemSpotify (line 12) | class FilesystemSpotify : public foobar2000_io::filesystem
FILE: foo_spotify/fb2k/input.cpp
class InputSpotify (line 27) | class InputSpotify
method InputSpotify (line 32) | InputSpotify() = default;
function GetPlaybackErrorMessage (line 115) | std::string GetPlaybackErrorMessage( sp_error sp, const std::string& tra...
function t_uint32 (line 454) | t_uint32 InputSpotify::get_subsong_count()
function t_uint32 (line 459) | t_uint32 InputSpotify::get_subsong( unsigned song )
function GUID (line 464) | GUID InputSpotify::g_get_guid()
function LibSpotify_Backend (line 474) | LibSpotify_Backend& InputSpotify::GetInitializedLibSpotify()
FILE: foo_spotify/fb2k/playback.cpp
type sptf::fb2k (line 7) | namespace sptf::fb2k
FILE: foo_spotify/fb2k/playback.h
function namespace (line 5) | namespace sptf
function namespace (line 12) | namespace sptf::fb2k
FILE: foo_spotify/fb2k/playlist.cpp
class PlaylistCallbackSpotify (line 16) | class PlaylistCallbackSpotify : public playlist_callback_static
method on_default_format_changed (line 20) | void on_default_format_changed() override
method on_item_ensure_visible (line 23) | void on_item_ensure_visible( t_size p_playlist, t_size p_idx ) override
method on_item_focus_change (line 26) | void on_item_focus_change( t_size p_playlist, t_size p_from, t_size p_...
method on_items_added (line 29) | void on_items_added( t_size p_playlist, t_size p_start, metadb_handle_...
method on_items_removing (line 32) | void on_items_removing( t_size p_playlist, const pfc::bit_array& p_mas...
method on_items_removed (line 35) | void on_items_removed( t_size p_playlist, const pfc::bit_array& p_mask...
method on_items_reordered (line 38) | void on_items_reordered( t_size p_playlist, const t_size* p_order, t_s...
method on_items_replaced (line 41) | void on_items_replaced( t_size p_playlist, const pfc::bit_array& p_mas...
method on_items_selection_change (line 44) | void on_items_selection_change( t_size p_playlist, const pfc::bit_arra...
method on_items_modified (line 47) | void on_items_modified( t_size p_playlist, const pfc::bit_array& p_mas...
method on_items_modified_fromplayback (line 50) | void on_items_modified_fromplayback( t_size p_playlist, const pfc::bit...
method on_playback_order_changed (line 53) | void on_playback_order_changed( t_size p_new_index ) override
method on_playlist_created (line 57) | void on_playlist_created( t_size p_index, const char* p_name, t_size p...
method on_playlist_locked (line 60) | void on_playlist_locked( t_size p_playlist, bool p_locked ) override
method on_playlist_renamed (line 63) | void on_playlist_renamed( t_size p_index, const char* p_new_name, t_si...
method on_playlists_removing (line 66) | void on_playlists_removing( const pfc::bit_array& p_mask, t_size p_old...
method on_playlists_removed (line 69) | void on_playlists_removed( const pfc::bit_array& p_mask, t_size p_old_...
method on_playlists_reorder (line 72) | void on_playlists_reorder( const t_size* p_order, t_size p_count ) ove...
FILE: foo_spotify/fb2k/playlist_loader.cpp
class PlaylistLoaderSpotify (line 19) | class PlaylistLoaderSpotify : public playlist_loader
method PlaylistLoaderSpotify (line 22) | PlaylistLoaderSpotify() = default;
type SkippedTrack (line 93) | struct SkippedTrack
function TransformToSkippedTracks (line 99) | std::vector<SkippedTrack>
function GetTracks (line 109) | std::tuple<std::vector<std::unique_ptr<const sptf::WebApi_Track>>, std::...
function ReportSkippedTracks (line 162) | void ReportSkippedTracks( nonstd::span<const SkippedTrack> skippedTracks )
FILE: foo_spotify/stdafx.h
function namespace (line 50) | namespace std // NOLINT(cert-dcl58-cpp)
FILE: foo_spotify/ui/ui_ipref_tab.h
function namespace (line 3) | namespace sptf::ui
FILE: foo_spotify/ui/ui_not_auth.cpp
type sptf::ui (line 7) | namespace sptf::ui
function BOOL (line 15) | BOOL NotAuth::OnInitDialog( HWND hwndFocus, LPARAM lParam )
function NotAuthHandler (line 77) | NotAuthHandler& NotAuthHandler::Get()
FILE: foo_spotify/ui/ui_not_auth.h
function namespace (line 7) | namespace sptf::ui
FILE: foo_spotify/ui/ui_pref_tab_auth.cpp
type sptf::ui (line 20) | namespace sptf::ui
function HWND (line 37) | HWND PreferenceTabAuth::CreateTab( HWND hParent )
function CDialogImplBase (line 42) | CDialogImplBase& PreferenceTabAuth::Dialog()
function wchar_t (line 47) | const wchar_t* PreferenceTabAuth::Name() const
function t_uint32 (line 52) | t_uint32 PreferenceTabAuth::get_state()
function BOOL (line 83) | BOOL PreferenceTabAuth::OnInitDialog( HWND hwndFocus, LPARAM lParam )
function HBRUSH (line 151) | HBRUSH PreferenceTabAuth::OnCtlColorStatic( CDCHandle dc, CStatic wndS...
function LRESULT (line 256) | LRESULT PreferenceTabAuth::OnWebApiLoginResponse( UINT uMsg, WPARAM wP...
function LRESULT (line 272) | LRESULT PreferenceTabAuth::OnStatusUpdateFinish( UINT uMsg, WPARAM wPa...
FILE: foo_spotify/ui/ui_pref_tab_auth.h
function namespace (line 14) | namespace sptf::ui
FILE: foo_spotify/ui/ui_pref_tab_manager.cpp
class preferences_page_impl (line 16) | class preferences_page_impl
method GUID (line 25) | GUID get_guid() override
method GUID (line 30) | GUID get_parent_guid() override
method get_help_url (line 35) | bool get_help_url( pfc::string_base& p_out ) override
method instantiate (line 41) | preferences_page_instance::ptr instantiate( HWND parent, preferences_p...
type sptf::ui (line 53) | namespace sptf::ui
function HWND (line 70) | HWND PreferenceTabManager::get_wnd()
function t_uint32 (line 75) | t_uint32 PreferenceTabManager::get_state()
function BOOL (line 106) | BOOL PreferenceTabManager::OnInitDialog( HWND hwndFocus, LPARAM lParam )
function LRESULT (line 129) | LRESULT PreferenceTabManager::OnSelectionChanged( LPNMHDR pNmhdr )
function LRESULT (line 137) | LRESULT PreferenceTabManager::OnWindowPosChanged( UINT, WPARAM, LPARAM...
FILE: foo_spotify/ui/ui_pref_tab_manager.h
function namespace (line 9) | namespace sptf::ui
FILE: foo_spotify/ui/ui_pref_tab_playback.cpp
function GetNearestScrollPos (line 24) | int GetNearestScrollPos( float curValue, int direction )
type sptf::ui (line 50) | namespace sptf::ui
function HWND (line 85) | HWND PreferenceTabPlayback::CreateTab( HWND hParent )
function CDialogImplBase (line 90) | CDialogImplBase& PreferenceTabPlayback::Dialog()
function wchar_t (line 95) | const wchar_t* PreferenceTabPlayback::Name() const
function t_uint32 (line 100) | t_uint32 PreferenceTabPlayback::get_state()
function BOOL (line 133) | BOOL PreferenceTabPlayback::OnInitDialog( HWND hwndFocus, LPARAM lParam )
function LRESULT (line 196) | LRESULT PreferenceTabPlayback::OnTrackBarPosChangedNotify( LPNMHDR pnmh )
FILE: foo_spotify/ui/ui_pref_tab_playback.h
function namespace (line 16) | namespace sptf::ui
FILE: foo_spotify/utils/abort_manager.cpp
type sptf (line 7) | namespace sptf
FILE: foo_spotify/utils/abort_manager.h
function namespace (line 7) | namespace sptf
FILE: foo_spotify/utils/async_mutex.hpp
type cppcoro (line 14) | namespace cppcoro
class async_mutex_lock (line 16) | class async_mutex_lock
method async_mutex_lock (line 129) | explicit async_mutex_lock( async_mutex& mutex, std::adopt_lock_t ) n...
method async_mutex_lock (line 134) | async_mutex_lock( async_mutex_lock&& other ) noexcept
method async_mutex_lock (line 140) | async_mutex_lock( const async_mutex_lock& other ) = delete;
method async_mutex_lock (line 141) | async_mutex_lock& operator=( const async_mutex_lock& other ) = delete;
class async_mutex_lock_operation (line 17) | class async_mutex_lock_operation
method async_mutex_lock_operation (line 159) | explicit async_mutex_lock_operation( async_mutex& mutex ) noexcept
method await_ready (line 164) | bool await_ready() const noexcept
method await_resume (line 169) | void await_resume() const noexcept
class async_mutex_scoped_lock_operation (line 18) | class async_mutex_scoped_lock_operation
method async_mutex_lock (line 188) | [[nodiscard]] async_mutex_lock await_resume() const noexcept
class async_mutex (line 30) | class async_mutex
class async_mutex_lock (line 126) | class async_mutex_lock
method async_mutex_lock (line 129) | explicit async_mutex_lock( async_mutex& mutex, std::adopt_lock_t ) n...
method async_mutex_lock (line 134) | async_mutex_lock( async_mutex_lock&& other ) noexcept
method async_mutex_lock (line 140) | async_mutex_lock( const async_mutex_lock& other ) = delete;
method async_mutex_lock (line 141) | async_mutex_lock& operator=( const async_mutex_lock& other ) = delete;
class async_mutex_lock_operation (line 156) | class async_mutex_lock_operation
method async_mutex_lock_operation (line 159) | explicit async_mutex_lock_operation( async_mutex& mutex ) noexcept
method await_ready (line 164) | bool await_ready() const noexcept
method await_resume (line 169) | void await_resume() const noexcept
class async_mutex_scoped_lock_operation (line 183) | class async_mutex_scoped_lock_operation : public async_mutex_lock_oper...
method async_mutex_lock (line 188) | [[nodiscard]] async_mutex_lock await_resume() const noexcept
FILE: foo_spotify/utils/cred_prompt.cpp
type sptf (line 8) | namespace sptf
function ShowCredentialsDialog (line 11) | std::unique_ptr<CredentialsResult> ShowCredentialsDialog( HWND hWnd, c...
FILE: foo_spotify/utils/cred_prompt.h
function namespace (line 5) | namespace sptf
FILE: foo_spotify/utils/json_std_extenders.h
function namespace (line 3) | namespace nlohmann
function from_json (line 42) | static void from_json( const nlohmann::json& j, std::shared_ptr<T>& p )
function from_json (line 68) | static void from_json( const nlohmann::json& j, std::optional<T>& p )
function wstring (line 82) | struct adl_serializer<std::wstring>
FILE: foo_spotify/utils/rps_limiter.cpp
function GetTimestampInMs (line 16) | std::chrono::milliseconds GetTimestampInMs()
type sptf (line 23) | namespace sptf
FILE: foo_spotify/utils/rps_limiter.h
function namespace (line 10) | namespace sptf
FILE: foo_spotify/utils/secure_vector.h
function namespace (line 5) | namespace sptf
FILE: foo_spotify/utils/sleeper.cpp
type sptf (line 11) | namespace sptf
function SleepFor (line 14) | bool SleepFor( const std::chrono::milliseconds& duration, abort_callba...
FILE: foo_spotify/utils/sleeper.h
function namespace (line 5) | namespace sptf
FILE: libspotify/examples/Randomify/SpotifyTrack.h
function interface (line 10) | interface SpotifyTrack : NSObject {
FILE: libspotify/examples/jukebox/alsa-audio.c
function snd_pcm_t (line 39) | static snd_pcm_t *alsa_open(char *dev, int rate, int channels)
function audio_init (line 216) | void audio_init(audio_fifo_t *af)
FILE: libspotify/examples/jukebox/audio.c
function audio_fifo_data_t (line 31) | audio_fifo_data_t* audio_get(audio_fifo_t *af)
function audio_fifo_flush (line 46) | void audio_fifo_flush(audio_fifo_t *af)
FILE: libspotify/examples/jukebox/audio.h
type audio_fifo_data_t (line 36) | typedef struct audio_fifo_data {
type audio_fifo_t (line 44) | typedef struct audio_fifo {
FILE: libspotify/examples/jukebox/dummy-audio.c
function audio_init (line 47) | void audio_init(audio_fifo_t *af)
FILE: libspotify/examples/jukebox/jukebox.c
function try_jukebox_start (line 81) | static void try_jukebox_start(void)
function tracks_added (line 135) | static void tracks_added(sp_playlist *pl, sp_track * const *tracks,
function tracks_removed (line 154) | static void tracks_removed(sp_playlist *pl, const int *tracks,
function tracks_moved (line 182) | static void tracks_moved(sp_playlist *pl, const int *tracks,
function playlist_renamed (line 199) | static void playlist_renamed(sp_playlist *pl, void *userdata)
function playlist_added (line 237) | static void playlist_added(sp_playlistcontainer *pc, sp_playlist *pl,
function playlist_removed (line 258) | static void playlist_removed(sp_playlistcontainer *pc, sp_playlist *pl,
function container_loaded (line 272) | static void container_loaded(sp_playlistcontainer *pc, void *userdata)
function logged_in (line 295) | static void logged_in(sp_session *sess, sp_error error)
function notify_main_thread (line 338) | static void notify_main_thread(sp_session *sess)
function music_delivery (line 351) | static int music_delivery(sp_session *sess, const sp_audioformat *format,
function end_of_track (line 395) | static void end_of_track(sp_session *sess)
function metadata_updated (line 413) | static void metadata_updated(sp_session *sess)
function play_token_lost (line 424) | static void play_token_lost(sp_session *sess)
function track_ended (line 469) | static void track_ended(void)
function usage (line 490) | static void usage(const char *progname)
function main (line 496) | int main(int argc, char **argv)
FILE: libspotify/examples/jukebox/openal-audio.c
function error_exit (line 41) | static void error_exit(const char *msg)
function queue_buffer (line 47) | static int queue_buffer(ALuint source, audio_fifo_t *af, ALuint buffer)
function audio_init (line 140) | void audio_init(audio_fifo_t *af)
FILE: libspotify/examples/jukebox/osx-audio.c
type AQPlayerState (line 32) | struct AQPlayerState {
function audio_callback (line 39) | static void audio_callback (void *aux, AudioQueueRef aq, AudioQueueBuffe...
function audio_init (line 55) | void audio_init(audio_fifo_t *af)
FILE: libspotify/examples/jukebox/playtrack.c
function logged_in (line 71) | static void logged_in(sp_session *sess, sp_error error)
function metadata_updated (line 103) | static void metadata_updated(sp_session *sess)
function notify_main_thread (line 125) | static void notify_main_thread(sp_session *sess)
function music_delivery (line 138) | static int music_delivery(sp_session *sess, const sp_audioformat *format,
function end_of_track (line 182) | static void end_of_track(sp_session *sess)
function play_token_lost (line 196) | static void play_token_lost(sp_session *sess)
function log_message (line 206) | static void log_message(sp_session *session, const char *msg)
function track_ended (line 246) | static void track_ended(void)
function usage (line 261) | static void usage(const char *progname)
function main (line 266) | int main(int argc, char **argv)
FILE: libspotify/examples/jukebox/queue.h
type type (line 95) | struct type
type type (line 214) | struct type
type type (line 288) | struct type
type type (line 456) | struct type
type type (line 465) | struct type
FILE: libspotify/examples/localfiles/main.c
function connection_error (line 40) | static void connection_error(sp_session *session, sp_error error)
function logged_in (line 45) | static void logged_in(sp_session *session, sp_error error)
function logged_out (line 61) | static void logged_out(sp_session *session)
function log_message (line 72) | static void log_message(sp_session *session, const char *data)
function notify_main_thread (line 77) | void notify_main_thread(sp_session *session)
function spotify_init (line 97) | int spotify_init(const char *username,const char *password)
function main (line 154) | int main(int argc, char **argv)
FILE: libspotify/examples/spshell/browse.c
function print_track (line 36) | void print_track(sp_track *track)
function print_albumbrowse (line 76) | static void print_albumbrowse(sp_albumbrowse *browse)
function print_artistbrowse (line 102) | static void print_artistbrowse(sp_artistbrowse *browse)
function browse_album_callback (line 130) | static void SP_CALLCONV browse_album_callback(sp_albumbrowse *browse, vo...
function browse_artist_callback (line 149) | static void SP_CALLCONV browse_artist_callback(sp_artistbrowse *browse, ...
function track_browse_try (line 166) | static void track_browse_try(void)
function playlist_browse_try (line 191) | static void playlist_browse_try(void)
function pl_tracks_added (line 227) | static void SP_CALLCONV pl_tracks_added(sp_playlist *pl, sp_track * cons...
function pl_tracks_removed (line 236) | static void SP_CALLCONV pl_tracks_removed(sp_playlist *pl, const int *tr...
function pl_tracks_moved (line 245) | static void SP_CALLCONV pl_tracks_moved(sp_playlist *pl, const int *tracks,
function pl_renamed (line 254) | static void SP_CALLCONV pl_renamed(sp_playlist *pl, void *userdata)
function pl_state_change (line 262) | static void SP_CALLCONV pl_state_change(sp_playlist *pl, void *userdata)
function browse_playlist (line 276) | void browse_playlist(sp_playlist *pl)
function browse_usage (line 286) | static void browse_usage(void)
function cmd_browse (line 295) | int cmd_browse(int argc, char **argv)
FILE: libspotify/examples/spshell/cmd.c
function tokenize (line 80) | static int tokenize(char *buf, char **vec, int vsize)
function cmd_exec_unparsed (line 105) | void cmd_exec_unparsed(char *l)
function cmd_dispatch (line 116) | void cmd_dispatch(int argc, char **argv)
function cmd_help (line 139) | static int cmd_help(int argc, char **argv)
FILE: libspotify/examples/spshell/inbox.c
function post_usage (line 32) | static void post_usage(void)
function inbox_post_completed (line 44) | static void SP_CALLCONV inbox_post_completed(sp_inbox *result, void *use...
function cmd_post (line 54) | int cmd_post(int argc, char **argv)
function cmd_inbox (line 109) | int cmd_inbox(int argc, char **argv)
FILE: libspotify/examples/spshell/playlist.c
function cmd_update_subscriptions (line 40) | int cmd_update_subscriptions(int argc, char **argv)
function cmd_playlists (line 64) | int cmd_playlists(int argc, char **argv)
function print_track2 (line 115) | static void print_track2(sp_track *track, int i)
function cmd_playlist (line 129) | int cmd_playlist(int argc, char **argv)
function cmd_set_autolink (line 188) | int cmd_set_autolink(int argc, char **argv)
function cmd_add_folder (line 217) | int cmd_add_folder(int argc, char **argv)
type pl_update_work (line 245) | struct pl_update_work {
function apply_changes (line 251) | static int apply_changes(sp_playlist *pl, struct pl_update_work *puw)
function pl_state_change (line 288) | static void SP_CALLCONV pl_state_change(sp_playlist *pl, void *userdata)
function cmd_playlist_add_track (line 311) | int cmd_playlist_add_track(int argc, char **argv)
function cmd_playlist_offline (line 378) | int cmd_playlist_offline(int argc, char **argv)
FILE: libspotify/examples/spshell/scrobbling.c
function print_scrobbling_state (line 27) | void print_scrobbling_state(sp_scrobbling_state state) {
function cmd_facebook_scrobbling (line 41) | int cmd_facebook_scrobbling(int argc, char **argv)
function cmd_is_facebook_scrobbling (line 59) | int cmd_is_facebook_scrobbling(int argc, char **argv)
function cmd_spotify_social (line 69) | int cmd_spotify_social(int argc, char **argv)
function cmd_is_spotify_social (line 87) | int cmd_is_spotify_social(int argc, char **argv)
function cmd_set_lastfm_scrobbling_credentials (line 97) | int cmd_set_lastfm_scrobbling_credentials(int argc, char **argv)
function cmd_lastfm_scrobbling (line 108) | int cmd_lastfm_scrobbling(int argc, char **argv)
function cmd_is_lastfm_scrobbling (line 122) | int cmd_is_lastfm_scrobbling(int argc, char **argv)
function cmd_private_session (line 139) | extern int cmd_private_session(int argc, char** argv)
function cmd_is_private_session (line 150) | extern int cmd_is_private_session(int argc, char** argv)
FILE: libspotify/examples/spshell/search.c
function print_album (line 35) | static void print_album(sp_album *album)
function print_artist (line 47) | static void print_artist(sp_artist *artist)
function print_search (line 57) | static void print_search(sp_search *search)
function search_complete (line 95) | static void SP_CALLCONV search_complete(sp_search *search, void *userdata)
function search_usage (line 112) | static void search_usage(void)
function cmd_search (line 121) | int cmd_search(int argc, char **argv)
function cmd_whatsnew (line 144) | int cmd_whatsnew(int argc, char **argv)
FILE: libspotify/examples/spshell/spshell.c
function connection_error (line 42) | static void SP_CALLCONV connection_error(sp_session *session, sp_error e...
function logged_in (line 53) | static void SP_CALLCONV logged_in(sp_session *session, sp_error error)
function logged_out (line 86) | static void SP_CALLCONV logged_out(sp_session *session)
function scrobble_error (line 97) | static void SP_CALLCONV scrobble_error(sp_session* session, sp_error err...
function private_session_mode_changed (line 107) | static void SP_CALLCONV private_session_mode_changed(sp_session *session...
function credentials_blob_updated (line 118) | static void SP_CALLCONV credentials_blob_updated(sp_session *session, co...
function log_message (line 128) | static void SP_CALLCONV log_message(sp_session *session, const char *data)
function metadata_updated (line 144) | static void SP_CALLCONV metadata_updated(sp_session *sess)
function offline_status_updated (line 158) | static void SP_CALLCONV offline_status_updated(sp_session *sess)
function spshell_init (line 186) | int spshell_init(const char *username, const char *password, const char...
function cmd_logout (line 275) | int cmd_logout(int argc, char **argv)
function test_finished (line 289) | void test_finished(void)
function cmd_log (line 297) | int cmd_log(int argc, char **argv)
FILE: libspotify/examples/spshell/spshell_posix.c
function start_prompt (line 86) | void start_prompt(void)
function trim (line 100) | static void trim(char *buf)
function main (line 111) | int main(int argc, char **argv)
function cmd_done (line 234) | void cmd_done(void)
function notify_main_thread (line 246) | void notify_main_thread(sp_session *session)
function sp_uint64 (line 258) | sp_uint64 get_ts(void)
FILE: libspotify/examples/spshell/spshell_win32.c
function console_input (line 40) | static void console_input(void)
function trim (line 85) | static void trim(char *buf)
function main (line 93) | int __cdecl main(int argc, char **argv)
function cmd_done (line 175) | void cmd_done(void)
function start_prompt (line 186) | void start_prompt(void)
function notify_main_thread (line 195) | void SP_CALLCONV notify_main_thread(sp_session *session)
function sp_uint64 (line 203) | sp_uint64 get_ts(void)
FILE: libspotify/examples/spshell/star.c
function star_usage (line 30) | static void star_usage(const char *prefix)
function dostar (line 39) | static int dostar(int argc, char **argv, int set)
function cmd_star (line 72) | int cmd_star(int argc, char **argv)
function cmd_unstar (line 81) | int cmd_unstar(int argc, char **argv)
function cmd_starred (line 89) | int cmd_starred(int argc, char **argv)
FILE: libspotify/examples/spshell/test.c
type msglevel (line 39) | enum msglevel {
type Test (line 61) | typedef struct Test {
type Test (line 76) | struct Test
function test_activate (line 86) | void test_activate(struct Test *t)
function test_list_active (line 93) | void test_list_active(void)
function output (line 101) | void output(enum msglevel level, const char *fmt, ...)
function test_deactivate (line 114) | void test_deactivate(const struct Test * const t)
function test_done (line 130) | static int test_done(Test *t, int backend_time)
function test_ok (line 161) | static void test_ok(Test *t)
function test_ok_backend_duration (line 170) | static void test_ok_backend_duration(Test *t, int backend_duration)
function test_report (line 185) | static void test_report(Test *t, const char *fmt, ...)
function test_start (line 206) | static void test_start(Test *t)
function sp_artist (line 215) | static sp_artist *artist_from_uri(const char *uri)
function sp_album (line 230) | static sp_album *album_from_uri(const char *uri)
function sp_track (line 245) | static sp_track *track_from_uri(const char *uri)
function search1_cb (line 268) | static void SP_CALLCONV search1_cb(sp_search *result, void *userdata)
function search2_cb (line 300) | static void SP_CALLCONV search2_cb(sp_search *result, void *userdata)
function did_you_mean_cb (line 311) | static void SP_CALLCONV did_you_mean_cb(sp_search *result, void *userdata)
function search_test (line 326) | static void search_test(void)
function album_cb (line 344) | static void SP_CALLCONV album_cb(sp_albumbrowse *result, void *userdata)
function albumbrowse_test (line 369) | static void albumbrowse_test(void)
function no_albums_cb (line 390) | static void SP_CALLCONV no_albums_cb(sp_artistbrowse *result, void *user...
function artistbrowse_test (line 407) | static void artistbrowse_test(void)
function no_tracks_cb (line 444) | static void SP_CALLCONV no_tracks_cb(sp_artistbrowse *result, void *user...
function artistbrowse_no_tracks_test (line 459) | static void artistbrowse_no_tracks_test(void)
function toplist_cb (line 487) | static void SP_CALLCONV toplist_cb(sp_toplistbrowse *result, void *userd...
function toplistbrowse_test (line 510) | static void toplistbrowse_test(void)
function radio_cb (line 591) | static void radio_cb(sp_radio *result, void *userdata)
function radio_test (line 616) | static void radio_test(void)
function end_of_track (line 647) | void SP_CALLCONV end_of_track(sp_session *s)
function music_delivery (line 657) | int SP_CALLCONV music_delivery(sp_session *s, const sp_audioformat *fmt,...
function play_token_lost (line 665) | void SP_CALLCONV play_token_lost(sp_session *s)
function playtrack_test (line 673) | static void playtrack_test(void)
function check_streaming_done (line 686) | static int check_streaming_done(void)
function image_cb (line 711) | static void SP_CALLCONV image_cb(sp_image *image, void *userdata)
function load_image (line 732) | static void load_image(Test *t, const char *uri)
function image_test (line 746) | static void image_test(void)
function print_resource_usage (line 768) | static void print_resource_usage()
function test_process (line 804) | void test_process(void)
function cmd_test (line 849) | int cmd_test(int argc, char **argv)
FILE: libspotify/examples/spshell/toplist.c
function print_album (line 32) | static void print_album(int index, sp_album *album)
function print_artist (line 41) | static void print_artist(int index, sp_artist *artist)
function got_toplist (line 62) | static void SP_CALLCONV got_toplist(sp_toplistbrowse *result, void *user...
function toplist_usage (line 88) | static void toplist_usage(void)
function cmd_toplist (line 96) | int cmd_toplist(int argc, char **argv)
FILE: libspotify/examples/stub/main.c
function connection_error (line 40) | static void connection_error(sp_session *session, sp_error error)
function logged_in (line 45) | static void logged_in(sp_session *session, sp_error error)
function logged_out (line 51) | static void logged_out(sp_session *session)
function log_message (line 62) | static void log_message(sp_session *session, const char *data)
function notify_main_thread (line 67) | void notify_main_thread(sp_session *session)
function spotify_init (line 87) | int spotify_init(const char *username,const char *password)
function main (line 144) | int main(int argc, char **argv)
FILE: libspotify/include/libspotify/api.h
type sp_uint64 (line 47) | typedef unsigned __int64 sp_uint64;
type sp_uint64 (line 50) | typedef uint64_t sp_uint64;
type byte (line 59) | typedef unsigned char byte;
type sp_session (line 67) | typedef struct sp_session sp_session;
type sp_track (line 68) | typedef struct sp_track sp_track;
type sp_album (line 69) | typedef struct sp_album sp_album;
type sp_artist (line 70) | typedef struct sp_artist sp_artist;
type sp_artistbrowse (line 71) | typedef struct sp_artistbrowse sp_artistbrowse;
type sp_albumbrowse (line 72) | typedef struct sp_albumbrowse sp_albumbrowse;
type sp_toplistbrowse (line 73) | typedef struct sp_toplistbrowse sp_toplistbrowse;
type sp_search (line 74) | typedef struct sp_search sp_search;
type sp_link (line 75) | typedef struct sp_link sp_link;
type sp_image (line 76) | typedef struct sp_image sp_image;
type sp_user (line 77) | typedef struct sp_user sp_user;
type sp_playlist (line 78) | typedef struct sp_playlist sp_playlist;
type sp_playlistcontainer (line 79) | typedef struct sp_playlistcontainer sp_playlistcontainer;
type sp_inbox (line 80) | typedef struct sp_inbox sp_inbox;
type sp_error (line 95) | typedef enum sp_error {
type sp_connectionstate (line 170) | typedef enum sp_connectionstate {
type sp_sampletype (line 182) | typedef enum sp_sampletype {
type sp_audioformat (line 189) | typedef struct sp_audioformat {
type sp_bitrate (line 198) | typedef enum sp_bitrate {
type sp_playlist_type (line 207) | typedef enum sp_playlist_type {
type sp_search_type (line 219) | typedef enum sp_search_type {
type sp_playlist_offline_status (line 227) | typedef enum sp_playlist_offline_status {
type sp_track_availability (line 237) | typedef enum sp_availability {
type sp_track_offline_status (line 247) | typedef enum sp_track_offline_status {
type sp_image_size (line 261) | typedef enum sp_image_size {
type sp_audio_buffer_stats (line 270) | typedef struct sp_audio_buffer_stats {
type sp_subscribers (line 278) | typedef struct sp_subscribers {
type sp_connection_type (line 287) | typedef enum sp_connection_type {
type sp_connection_rules (line 302) | typedef enum sp_connection_rules {
type sp_artistbrowse_type (line 313) | typedef enum sp_artistbrowse_type {
type sp_social_provider (line 327) | typedef enum sp_social_provider {
type sp_scrobbling_state (line 333) | typedef enum sp_scrobbling_state {
type sp_offline_sync_status (line 345) | typedef struct sp_offline_sync_status {
type sp_session_callbacks (line 393) | typedef struct sp_session_callbacks {
type sp_session_config (line 643) | typedef struct sp_session_config {
type sp_linktype (line 1250) | typedef enum {
type sp_albumtype (line 1775) | typedef enum {
type SP_CALLCONV (line 1957) | typedef void SP_CALLCONV
type SP_CALLCONV (line 2123) | typedef void SP_CALLCONV
type sp_imageformat (line 2334) | typedef enum {
type SP_CALLCONV (line 2343) | typedef void SP_CALLCONV
type SP_CALLCONV (line 2488) | typedef void SP_CALLCONV
type sp_playlist_callbacks (line 2742) | typedef struct sp_playlist_callbacks {
type sp_playlistcontainer_callbacks (line 3322) | typedef struct sp_playlistcontainer_callbacks {
type sp_relation_type (line 3611) | typedef enum sp_relation_type {
type sp_toplisttype (line 3681) | typedef enum {
type sp_toplistregion (line 3700) | typedef enum {
type SP_CALLCONV (line 3716) | typedef void SP_CALLCONV
type SP_CALLCONV (line 3869) | typedef void SP_CALLCONV
FILE: scripts/call_wrapper.py
class SkippedError (line 6) | class SkippedError(Exception):
method __init__ (line 7) | def __init__(self):
function final_call_decorator (line 10) | def final_call_decorator(start_msg: str,
FILE: scripts/download_submodules.py
function download_submodule (line 8) | def download_submodule(root_dir, submodule_name):
function download (line 23) | def download():
FILE: scripts/pack_component.py
function path_basename_tuple (line 10) | def path_basename_tuple(path):
function zipdir (line 13) | def zipdir(zip_file, path, arc_path):
function pack (line 26) | def pack(is_debug = False):
FILE: scripts/setup.py
function call_decorator (line 16) | def call_decorator(command_name: str):
function load_module (line 32) | def load_module(script_path):
function setup (line 38) | def setup( skip_submodules_download,
FILE: scripts/update_gh_pages.py
function update (line 12) | def update(gh_pages_dir: PathLike):
Condensed preview — 226 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,198K chars).
[
{
"path": ".appveyor.yml",
"chars": 6022,
"preview": "\n#---------------------------------#\n# general configuration #\n#---------------------------------#\n\n# version "
},
{
"path": ".bandit",
"chars": 37,
"preview": "skips: ['B101','B404','B602','B607']\n"
},
{
"path": ".codacy.yml",
"chars": 167,
"preview": "engines:\n prospector:\n enabled: true\n python_version: 3\n pylint:\n enabled: true\n python_version: 3\nexclude"
},
{
"path": ".gitattributes",
"chars": 494,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n# Custom for Visual Studio\n*.cs diff=csharp\n# Stan"
},
{
"path": ".gitignore",
"chars": 4534,
"preview": "# Created by https://www.gitignore.io/api/visualstudio\n\n### VisualStudio ###\n## Ignore Visual Studio temporary files, bu"
},
{
"path": ".gitmodules",
"chars": 1079,
"preview": "[submodule \"submodules/acfu-sdk\"]\n\tpath = submodules/acfu-sdk\n\turl = https://github.com/3dyd/acfu-sdk/\n[submodule \"submo"
},
{
"path": ".license_index.txt",
"chars": 158,
"preview": "foobar2000 SDK: other\nfmt: other\nJSON for Modern C++: MIT\nLibSpotify: other\nPFC: zlib\nrange-v3: BSL-1.0\nring-span-lite: "
},
{
"path": "CHANGELOG.md",
"chars": 4437,
"preview": "# Changelog\n\n#### Table of Contents\n- [Unreleased](#unreleased)\n- [1.1.3](#113---2021-02-18)\n- [1.1.2](#112---2020-11-03"
},
{
"path": "LICENSE",
"chars": 1075,
"preview": "MIT License\n\nCopyright (c) 2018-2020 Yuri Shutenko\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "README.md",
"chars": 1256,
"preview": "# Spotify Integration\n[![version][version_badge]][changelog] [![Build status][appveyor_badge]](https://ci.appveyor.com/p"
},
{
"path": "THIRD_PARTY_NOTICES.md",
"chars": 642,
"preview": "Spotify Integration uses third-party libraries or other resources that may\nbe distributed under licenses different than "
},
{
"path": "VERSION",
"chars": 11,
"preview": "1.1.4-beta\n"
},
{
"path": "foo_spotify/.clang-format",
"chars": 2346,
"preview": "---\nBasedOnStyle: Mozilla\nAccessModifierOffset: '-4'\nAlwaysBreakAfterDefinitionReturnType: 'false'\nAlignAfterOpenBracke"
},
{
"path": "foo_spotify/.clang-tidy",
"chars": 997,
"preview": "Checks: '-*,\n bugprone-*,\n cert-*,\n clang-analyzer-*,\n google-*,\n -google-build-"
},
{
"path": "foo_spotify/backend/audio_buffer.cpp",
"chars": 2584,
"preview": "#include <stdafx.h>\n\n#include \"audio_buffer.h\"\n\n#include <utils/abort_manager.h>\n\nnamespace sptf\n{\n\nAudioBuffer::AudioBu"
},
{
"path": "foo_spotify/backend/audio_buffer.h",
"chars": 1929,
"preview": "#pragma once\n\n#include <nonstd/span.hpp>\n\n#include <array>\n#include <atomic>\n#include <mutex>\n\nnamespace sptf\n{\n\nclass A"
},
{
"path": "foo_spotify/backend/libspotify_backend.cpp",
"chars": 18279,
"preview": "#include <stdafx.h>\n\n#include \"libspotify_backend.h\"\n\n#include <backend/libspotify_key.h>\n#include <backend/spotify_inst"
},
{
"path": "foo_spotify/backend/libspotify_backend.h",
"chars": 2951,
"preview": "#pragma once\n\n#include <backend/audio_buffer.h>\n#include <backend/libspotify_backend_user.h>\n#include <fb2k/config.h>\n\n#"
},
{
"path": "foo_spotify/backend/libspotify_backend_user.h",
"chars": 169,
"preview": "#pragma once\n\nnamespace sptf\n{\n\nclass LibSpotify_BackendUser\n{\npublic:\n ~LibSpotify_BackendUser() = default;\n virt"
},
{
"path": "foo_spotify/backend/libspotify_key.h",
"chars": 2065,
"preview": "#pragma once\n\n#include <cstdint>\n\n// clang-format off\n\ninline constexpr uint8_t g_appkey[] = {\n\t0x01, 0xF8, 0x35, 0x9D, "
},
{
"path": "foo_spotify/backend/libspotify_wrapper.h",
"chars": 2103,
"preview": "#pragma once\n\n#include <backend/libspotify_backend.h>\n\n#include <libspotify/api.h>\n\nnamespace sptf::wrapper\n{\n\nnamespace"
},
{
"path": "foo_spotify/backend/spotify_instance.cpp",
"chars": 2435,
"preview": "#include <stdafx.h>\n\n#include \"spotify_instance.h\"\n\n#include <backend/libspotify_backend.h>\n#include <backend/webapi_aut"
},
{
"path": "foo_spotify/backend/spotify_instance.h",
"chars": 913,
"preview": "#pragma once\n\n#include <mutex>\n\nnamespace qwr\n{\nclass ThreadPool;\n}\n\nnamespace sptf\n{\n\nclass AbortManager;\nclass LibSpot"
},
{
"path": "foo_spotify/backend/spotify_object.cpp",
"chars": 3528,
"preview": "#include <stdafx.h>\n\n#include \"spotify_object.h\"\n\n#include <qwr/string_helpers.h>\n\nusing namespace std::literals::string"
},
{
"path": "foo_spotify/backend/spotify_object.h",
"chars": 930,
"preview": "#pragma once\n\n#include <string>\n\nnamespace sptf\n{\n\nstruct SpotifyObject\n{\n /// @throw qwr::QwrException\n SpotifyOb"
},
{
"path": "foo_spotify/backend/webapi_auth.cpp",
"chars": 16700,
"preview": "#include <stdafx.h>\n\n#include \"webapi_auth.h\"\n\n#include <backend/spotify_instance.h>\n#include <backend/webapi_auth_scope"
},
{
"path": "foo_spotify/backend/webapi_auth.h",
"chars": 1505,
"preview": "#pragma once\n\n#include <cpprest/asyncrt_utils.h>\n#include <cpprest/http_client.h>\n#include <cpprest/http_msg.h>\n\n#includ"
},
{
"path": "foo_spotify/backend/webapi_auth_scopes.cpp",
"chars": 4476,
"preview": "#include <stdafx.h>\n\n#include \"webapi_auth_scopes.h\"\n\n#include <unordered_set>\n\nusing namespace std::literals::string_vi"
},
{
"path": "foo_spotify/backend/webapi_auth_scopes.h",
"chars": 911,
"preview": "#pragma once\n\n#include <nonstd/span.hpp>\n\n#include <string_view>\n\nnamespace sptf\n{\n\nstruct WebApiAuthScopes\n{\n WebApi"
},
{
"path": "foo_spotify/backend/webapi_backend.cpp",
"chars": 18803,
"preview": "#include <stdafx.h>\n\n#include \"webapi_backend.h\"\n\n#include <backend/webapi_auth.h>\n#include <backend/webapi_objects/weba"
},
{
"path": "foo_spotify/backend/webapi_backend.h",
"chars": 2964,
"preview": "#pragma once\n\n#include <backend/webapi_cache.h>\n#include <utils/rps_limiter.h>\n\n#include <cpprest/http_client.h>\n#includ"
},
{
"path": "foo_spotify/backend/webapi_cache.cpp",
"chars": 4425,
"preview": "#include <stdafx.h>\n\n#include \"webapi_cache.h\"\n\n#include <backend/spotify_instance.h>\n#include <backend/webapi_objects/w"
},
{
"path": "foo_spotify/backend/webapi_cache.h",
"chars": 3938,
"preview": "#pragma once\n\n#include <nonstd/span.hpp>\n#include <qwr/file_helpers.h>\n\n#include <filesystem>\n#include <memory>\n#include"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_album.cpp",
"chars": 292,
"preview": "#include <stdafx.h>\n\n#include \"webapi_album.h\"\n\n#include <backend/webapi_objects/webapi_media_objects.h>\n#include <utils"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_album.h",
"chars": 1700,
"preview": "#pragma once\n\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace sptf\n{\n\nstruct WebApi_Artist_Simplified;\n"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_artist.cpp",
"chars": 351,
"preview": "#include <stdafx.h>\n\n#include \"webapi_artist.h\"\n\n#include <backend/webapi_objects/webapi_media_objects.h>\n#include <util"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_artist.h",
"chars": 1010,
"preview": "#pragma once\n\n#include <string>\n\nnamespace sptf\n{\n\nstruct WebApi_Image;\n\nstruct WebApi_Artist_Simplified\n{\n // extern"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_image.cpp",
"chars": 164,
"preview": "#include <stdafx.h>\n\n#include \"webapi_image.h\"\n\nnamespace sptf\n{\n\nSPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_Image,"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_image.h",
"chars": 295,
"preview": "#pragma once\n\n#include <memory>\n#include <string>\n\nnamespace sptf\n{\n\nstruct WebApi_Image\n{\n uint32_t height;\n std:"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_media_objects.h",
"chars": 378,
"preview": "#pragma once\n\n#include <backend/webapi_objects/webapi_album.h>\n#include <backend/webapi_objects/webapi_artist.h>\n#includ"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_paging_object.cpp",
"chars": 347,
"preview": "#include <stdafx.h>\n\n#include \"webapi_paging_object.h\"\n\n#include <utils/json_std_extenders.h>\n\nnamespace sptf\n{\n\nvoid fr"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_paging_object.h",
"chars": 465,
"preview": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <string>\n\nnamespace sptf\n{\n\nstruct WebApi_PagingObject\n{\n "
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_playlist_track.cpp",
"chars": 829,
"preview": "#include <stdafx.h>\n\n#include \"webapi_playlist_track.h\"\n\n#include <backend/webapi_objects/webapi_media_objects.h>\n#inclu"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_playlist_track.h",
"chars": 860,
"preview": "#pragma once\n\n#include <memory>\n#include <string>\n#include <variant>\n\nnamespace sptf\n{\n\nstruct WebApi_Track;\n\nstruct Web"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_restriction.cpp",
"chars": 164,
"preview": "#include <stdafx.h>\n\n#include \"webapi_restriction.h\"\n\nnamespace sptf\n{\n\nSPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( WebApi_"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_restriction.h",
"chars": 275,
"preview": "#pragma once\n\n#include <memory>\n#include <string>\n\nnamespace sptf\n{\n\nstruct WebApi_Restriction\n{\n std::string reason;"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_track.cpp",
"chars": 2422,
"preview": "#include <stdafx.h>\n\n#include \"webapi_track.h\"\n\n#include <backend/webapi_objects/webapi_media_objects.h>\n#include <utils"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_track.h",
"chars": 3535,
"preview": "#pragma once\n\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace sptf\n{\n\nstruct WebApi_Album_Simplified;\ns"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_track_link.cpp",
"chars": 253,
"preview": "#include <stdafx.h>\n\n#include \"webapi_track_link.h\"\n\n#include <backend/webapi_objects/webapi_media_objects.h>\n#include <"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_track_link.h",
"chars": 532,
"preview": "#pragma once\n\n#include <memory>\n#include <string>\nnamespace sptf\n{\n\nstruct WebApi_TrackLink\n{\n // external_urls \tan e"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_user.cpp",
"chars": 588,
"preview": "#include <stdafx.h>\n\n#include \"webapi_user.h\"\n\n#include <utils/json_std_extenders.h>\n\nnamespace sptf\n{\n\nvoid to_json( nl"
},
{
"path": "foo_spotify/backend/webapi_objects/webapi_user.h",
"chars": 942,
"preview": "#pragma once\n\n#include <optional>\n#include <string>\n\nnamespace sptf\n{\n\nstruct WebApi_User\n{\n std::optional<std::strin"
},
{
"path": "foo_spotify/component_defines.h",
"chars": 1213,
"preview": "#pragma once\n\n// Generated by scripts from setup.py\n#include <commit_hash.h>\n#include <version.h>\n\n#define SPTF_NAME "
},
{
"path": "foo_spotify/component_guids.h",
"chars": 2309,
"preview": "#pragma once\n\nnamespace sptf::guid\n{\n\nconstexpr GUID acfu_source = { 0xbfbd48bc, 0x9f3b, 0x42bd, { 0x8e, 0xfc, 0x9d, 0x5"
},
{
"path": "foo_spotify/component_paths.cpp",
"chars": 584,
"preview": "#include <stdafx.h>\n\n#include \"component_paths.h\"\n\n#include <qwr/fbk2_paths.h>\n\nnamespace fs = std::filesystem;\n\nnamespa"
},
{
"path": "foo_spotify/component_paths.h",
"chars": 251,
"preview": "#pragma once\n\n#include <filesystem>\n\nnamespace sptf::path\n{\n\nstd::filesystem::path LibSpotifyCache();\nstd::filesystem::p"
},
{
"path": "foo_spotify/component_urls.h",
"chars": 442,
"preview": "#pragma once\n\nnamespace sptf::url\n{\n\nconstexpr wchar_t accountsAuthenticate[] = L\"https://accounts.spotify.com/authorize"
},
{
"path": "foo_spotify/dllmain.cpp",
"chars": 1955,
"preview": "#include <stdafx.h>\n\n#include <backend/spotify_instance.h>\n#include <ui/ui_not_auth.h>\n\n#include <qwr/abort_callback.h>\n"
},
{
"path": "foo_spotify/fb2k/acfu_integration.cpp",
"chars": 1175,
"preview": "#include <stdafx.h>\n\n#include <component_urls.h>\n\n#include <qwr/acfu_integration.h>\n\nusing namespace sptf;\n\nnamespace\n{\n"
},
{
"path": "foo_spotify/fb2k/advanced_config.cpp",
"chars": 1604,
"preview": "#include <stdafx.h>\n\n#include \"advanced_config.h\"\n\nnamespace\n{\n\nadvconfig_branch_factory branch_sptf(\n \"Spotify Integ"
},
{
"path": "foo_spotify/fb2k/advanced_config.h",
"chars": 474,
"preview": "#pragma once\n\n#include <qwr/fb2k_adv_config.h>\n\nnamespace sptf::config::advanced\n{\n\nextern qwr::fb2k::AdvConfigString_MT"
},
{
"path": "foo_spotify/fb2k/album_art.cpp",
"chars": 3432,
"preview": "#include <stdafx.h>\n\n#include <backend/spotify_instance.h>\n#include <backend/spotify_object.h>\n#include <backend/webapi_"
},
{
"path": "foo_spotify/fb2k/config.cpp",
"chars": 814,
"preview": "#include <stdafx.h>\n\n#include \"config.h\"\n\nnamespace sptf::config\n{\n\nqwr::fb2k::ConfigBool_MT enable_normalization( sptf:"
},
{
"path": "foo_spotify/fb2k/config.h",
"chars": 620,
"preview": "#pragma once\n\n#include <qwr/fb2k_config.h>\n\nnamespace sptf::config\n{\n\nenum class BitrateSettings : uint8_t\n{ // values a"
},
{
"path": "foo_spotify/fb2k/file_info_filler.cpp",
"chars": 2481,
"preview": "#include <stdafx.h>\n\n#include \"file_info_filler.h\"\n\n#include <qwr/string_helpers.h>\n\nnamespace\n{\n\nvoid FillMetaInfo( con"
},
{
"path": "foo_spotify/fb2k/file_info_filler.h",
"chars": 193,
"preview": "#pragma once\n\n#include <string>\n#include <unordered_map>\n\nnamespace sptf::fb2k\n{\n\nvoid FillFileInfoWithMeta( const std::"
},
{
"path": "foo_spotify/fb2k/filesystem.cpp",
"chars": 4499,
"preview": "#include <stdafx.h>\n\n#include <backend/spotify_object.h>\n\nusing namespace std::literals::string_view_literals;\n\nusing na"
},
{
"path": "foo_spotify/fb2k/input.cpp",
"chars": 12285,
"preview": "#include <stdafx.h>\n\n#include <backend/libspotify_backend.h>\n#include <backend/libspotify_wrapper.h>\n#include <backend/s"
},
{
"path": "foo_spotify/fb2k/playback.cpp",
"chars": 1448,
"preview": "#include <stdafx.h>\n\n#include \"playback.h\"\n\n#include <backend/libspotify_backend.h>\n\nnamespace sptf::fb2k\n{\n\nstd::mutex "
},
{
"path": "foo_spotify/fb2k/playback.h",
"chars": 1168,
"preview": "#pragma once\n\n#include <mutex>\n\nnamespace sptf\n{\n\nclass LibSpotify_Backend;\n\n}\n\nnamespace sptf::fb2k\n{\n\nclass PlayCallba"
},
{
"path": "foo_spotify/fb2k/playlist.cpp",
"chars": 4414,
"preview": "#include <stdafx.h>\n\n#include <backend/spotify_instance.h>\n#include <backend/spotify_object.h>\n#include <backend/webapi_"
},
{
"path": "foo_spotify/fb2k/playlist_loader.cpp",
"chars": 13105,
"preview": "#include <stdafx.h>\n\n#include <backend/spotify_instance.h>\n#include <backend/spotify_object.h>\n#include <backend/webapi_"
},
{
"path": "foo_spotify/foo_spotify.rc",
"chars": 4954,
"preview": "// Microsoft Visual C++ generated resource script.\n//\n#include \"resource.h\"\n\n#define APSTUDIO_READONLY_SYMBOLS\n/////////"
},
{
"path": "foo_spotify/foo_spotify.rc2",
"chars": 1473,
"preview": "//\n// foo_spotify.rc2 - resources Microsoft Visual C++ does not edit directly\n//\n\n#ifdef APSTUDIO_INVOKED\n #error this"
},
{
"path": "foo_spotify/foo_spotify.vcxproj",
"chars": 14404,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"14.0\" xmlns=\"http://schemas.micros"
},
{
"path": "foo_spotify/foo_spotify.vcxproj.filters",
"chars": 9313,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuil"
},
{
"path": "foo_spotify/packages.config",
"chars": 411,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n <package id=\"Microsoft.Build.Tasks.Git\" version=\"1.0.0\" targetFrame"
},
{
"path": "foo_spotify/resource.h",
"chars": 1196,
"preview": "//{{NO_DEPENDENCIES}}\n// Microsoft Visual C++ generated include file.\n// Used by foo_spotify.rc\n//\n#define VS_VERSION_IN"
},
{
"path": "foo_spotify/stdafx.cpp",
"chars": 20,
"preview": "#include <stdafx.h>\n"
},
{
"path": "foo_spotify/stdafx.h",
"chars": 1479,
"preview": "#pragma once\n\n// clang-format off\n// !!! Include order is important here (esp. for Win headers) !!!\n\n// Support only Win"
},
{
"path": "foo_spotify/ui/ui_ipref_tab.h",
"chars": 671,
"preview": "#pragma once\n\nnamespace sptf::ui\n{\n\nclass IPrefTab\n{\npublic:\n virtual ~IPrefTab() = default;\n\n virtual HWND Create"
},
{
"path": "foo_spotify/ui/ui_not_auth.cpp",
"chars": 2988,
"preview": "#include <stdafx.h>\n\n#include \"ui_not_auth.h\"\n\n#include <qwr/hook_handler.h>\n\nnamespace sptf::ui\n{\n\nNotAuth::NotAuth( st"
},
{
"path": "foo_spotify/ui/ui_not_auth.h",
"chars": 1291,
"preview": "#pragma once\n\n#include <resource.h>\n\n#include <mutex>\n\nnamespace sptf::ui\n{\n\nclass NotAuth\n : public CDialogImpl<NotA"
},
{
"path": "foo_spotify/ui/ui_pref_tab_auth.cpp",
"chars": 10111,
"preview": "#include <stdafx.h>\n\n#include \"ui_pref_tab_auth.h\"\n\n#include <backend/libspotify_backend.h>\n#include <backend/spotify_in"
},
{
"path": "foo_spotify/ui/ui_pref_tab_auth.h",
"chars": 2845,
"preview": "#pragma once\n\n#include <fb2k/config.h>\n#include <ui/ui_ipref_tab.h>\n\n#include <resource.h>\n\n#include <qwr/fb2k_config_ui"
},
{
"path": "foo_spotify/ui/ui_pref_tab_manager.cpp",
"chars": 4099,
"preview": "#include <stdafx.h>\n\n#include \"ui_pref_tab_manager.h\"\n\n#include <fb2k/config.h>\n#include <ui/ui_pref_tab_auth.h>\n#includ"
},
{
"path": "foo_spotify/ui/ui_pref_tab_manager.h",
"chars": 1451,
"preview": "#pragma once\n\n#include <ui/ui_ipref_tab.h>\n\n#include <resource.h>\n\n#include <array>\n\nnamespace sptf::ui\n{\n\nclass Prefere"
},
{
"path": "foo_spotify/ui/ui_pref_tab_playback.cpp",
"chars": 9607,
"preview": "#include <stdafx.h>\n\n#include \"ui_pref_tab_playback.h\"\n\n#include <backend/libspotify_backend.h>\n#include <backend/spotif"
},
{
"path": "foo_spotify/ui/ui_pref_tab_playback.h",
"chars": 2858,
"preview": "#pragma once\n\n#include <fb2k/config.h>\n#include <ui/ui_ipref_tab.h>\n\n#include <resource.h>\n\n#include <qwr/fb2k_config_ui"
},
{
"path": "foo_spotify/utils/abort_manager.cpp",
"chars": 2625,
"preview": "#include <stdafx.h>\n\n#include \"abort_manager.h\"\n\n#include <qwr/thread_helpers.h>\n\nnamespace sptf\n{\n\nAbortManager::AbortM"
},
{
"path": "foo_spotify/utils/abort_manager.h",
"chars": 2727,
"preview": "#pragma once\n\n#include <condition_variable>\n#include <functional>\n#include <mutex>\n\nnamespace sptf\n{\n\nclass AbortManager"
},
{
"path": "foo_spotify/utils/async_mutex.hpp",
"chars": 6227,
"preview": "///////////////////////////////////////////////////////////////////////////////\n// Copyright (c) Lewis Baker\n// Licenced"
},
{
"path": "foo_spotify/utils/cred_prompt.cpp",
"chars": 2718,
"preview": "#include <stdafx.h>\n\n#include \"cred_prompt.h\"\n\n#include <Ntsecapi.h>\n#include <WinCred.h>\n\nnamespace sptf\n{\n\nstd::unique"
},
{
"path": "foo_spotify/utils/cred_prompt.h",
"chars": 288,
"preview": "#pragma once\n\n#include <utils/secure_vector.h>\n\nnamespace sptf\n{\n\nstruct CredentialsResult\n{\n SecureVector<char> un;\n"
},
{
"path": "foo_spotify/utils/json_macro_fix.h",
"chars": 779,
"preview": "#pragma once\n\n// same as the stock one, but without `inline`\n#define SPTF_NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( Type, ... "
},
{
"path": "foo_spotify/utils/json_std_extenders.h",
"chars": 2076,
"preview": "#include <memory>\n\nnamespace nlohmann\n{\ntemplate <typename T>\nstruct adl_serializer<std::unique_ptr<T>>\n{\n static voi"
},
{
"path": "foo_spotify/utils/rps_limiter.cpp",
"chars": 2664,
"preview": "\n#include <stdafx.h>\n\n#include \"rps_limiter.h\"\n\n#include <backend/spotify_instance.h>\n#include <fb2k/advanced_config.h>\n"
},
{
"path": "foo_spotify/utils/rps_limiter.h",
"chars": 864,
"preview": "#pragma once\n\n#include <nonstd/ring_span.hpp>\n\n#include <chrono>\n#include <list>\n#include <mutex>\n#include <vector>\n\nnam"
},
{
"path": "foo_spotify/utils/secure_vector.h",
"chars": 1538,
"preview": "#pragma once\n\n#include <vector>\n\nnamespace sptf\n{\n\ntemplate <class T>\nclass SecureAllocator\n{\npublic:\n static_assert("
},
{
"path": "foo_spotify/utils/sleeper.cpp",
"chars": 765,
"preview": "#include <stdafx.h>\n\n#include \"sleeper.h\"\n\n#include <backend/spotify_instance.h>\n#include <utils/abort_manager.h>\n\n#incl"
},
{
"path": "foo_spotify/utils/sleeper.h",
"chars": 155,
"preview": "#pragma once\n\n#include <chrono>\n\nnamespace sptf\n{\n\nbool SleepFor( const std::chrono::milliseconds& duration, abort_callb"
},
{
"path": "libspotify/ChangeLog",
"chars": 18365,
"preview": "Version 12\n\n\tAdded Android support.\n\n\tAdded functionality for controlling scrobbling.\n\n\tAdded function sp_session_user_n"
},
{
"path": "libspotify/LICENSE",
"chars": 109,
"preview": "For the current terms and conditions, please read:\n\nhttp://developer.spotify.com/en/libspotify/terms-of-use/\n"
},
{
"path": "libspotify/README",
"chars": 2591,
"preview": "\n libspotify 12.1.51\n ----------------------------------------\n\n "
},
{
"path": "libspotify/docs/html/annotated.html",
"chars": 2668,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/api_8h.html",
"chars": 139719,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/browse_8c-example.html",
"chars": 23908,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/classes.html",
"chars": 2671,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/doxygen.css",
"chars": 6473,
"preview": "/* Copyright (c) 2009 Spotify Ltd */\nbody { margin:0; background:#fff; color:#444; padding:0; }\nbody,h1,h2,h3,h4,h5,p,td"
},
{
"path": "libspotify/docs/html/examples.html",
"chars": 1506,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/files.html",
"chars": 1596,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/functions.html",
"chars": 13217,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/functions_vars.html",
"chars": 13118,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/globals.html",
"chars": 2607,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/globals_0x69.html",
"chars": 2583,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/globals_0x73.html",
"chars": 53292,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/globals_0x74.html",
"chars": 2477,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/globals_defs.html",
"chars": 2069,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/globals_enum.html",
"chars": 4000,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/globals_eval.html",
"chars": 18652,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/globals_func.html",
"chars": 31234,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/globals_type.html",
"chars": 7151,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__album.html",
"chars": 19232,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__albumbrowse.html",
"chars": 28545,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__artist.html",
"chars": 9962,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__artistbrowse.html",
"chars": 39357,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__error.html",
"chars": 24383,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__image.html",
"chars": 25538,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__inbox.html",
"chars": 12831,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__link.html",
"chars": 47517,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__playlist.html",
"chars": 123691,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__search.html",
"chars": 47874,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__session.html",
"chars": 147145,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__toplist.html",
"chars": 33896,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__track.html",
"chars": 42503,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__types.html",
"chars": 9443,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/group__user.html",
"chars": 13229,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/index.html",
"chars": 11118,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/jukebox_8c-example.html",
"chars": 26320,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/modules.html",
"chars": 2295,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/search_8c-example.html",
"chars": 8236,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/structsp__audio__buffer__stats.html",
"chars": 3097,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/structsp__audioformat.html",
"chars": 3717,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/structsp__offline__sync__status.html",
"chars": 6992,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/structsp__playlist__callbacks.html",
"chars": 27266,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/structsp__playlistcontainer__callbacks.html",
"chars": 10873,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/structsp__session__callbacks.html",
"chars": 36529,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/structsp__session__config.html",
"chars": 15862,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/structsp__subscribers.html",
"chars": 3183,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/html/tabs.css",
"chars": 1095,
"preview": ".tabs, .tabs2, .tabs3 {\n background-image: url('tab_b.png');\n width: 100%;\n z-index: 101;\n font-size: 13px;\n"
},
{
"path": "libspotify/docs/html/toplist_8c-example.html",
"chars": 8189,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "libspotify/docs/images/spotify-core.txt",
"chars": 21,
"preview": "Uses SPOTIFY(R) CORE\n"
},
{
"path": "libspotify/examples/Makefile",
"chars": 215,
"preview": "EXAMPLES=jukebox spshell localfiles\n\n.PHONY: all clean\n\nifdef LIBSPOTIFY_PATH\nARG=LIBSPOTIFY_PATH=\"$(shell cd \"$(LIBSPOT"
},
{
"path": "libspotify/examples/Randomify/English.lproj/InfoPlist.strings",
"chars": 45,
"preview": "/* Localized versions of Info.plist keys */\n\n"
},
{
"path": "libspotify/examples/Randomify/English.lproj/MainMenu.xib",
"chars": 179104,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<archive type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"7.10\">\n\t<data>\n\t\t<"
},
{
"path": "libspotify/examples/Randomify/Randomify-Info.plist",
"chars": 974,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "libspotify/examples/Randomify/Randomify.xcodeproj/project.pbxproj",
"chars": 15309,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 45;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "libspotify/examples/Randomify/RandomifyAppDelegate.h",
"chars": 1871,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/Randomify/RandomifyAppDelegate.m",
"chars": 3692,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n * This file is part of the libspotify examples suite.\n * See RandomifyAppDel"
},
{
"path": "libspotify/examples/Randomify/SpotifyPlaylist.h",
"chars": 955,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n * This file is part of the libspotify examples suite.\n * See RandomifyAppDel"
},
{
"path": "libspotify/examples/Randomify/SpotifyPlaylist.m",
"chars": 1741,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n * This file is part of the libspotify examples suite.\n * See RandomifyAppDel"
},
{
"path": "libspotify/examples/Randomify/SpotifySession.h",
"chars": 1632,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n * This file is part of the libspotify examples suite.\n * See RandomifyAppDel"
},
{
"path": "libspotify/examples/Randomify/SpotifySession.m",
"chars": 8073,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n * This file is part of the libspotify examples suite.\n * See RandomifyAppDel"
},
{
"path": "libspotify/examples/Randomify/SpotifyTrack.h",
"chars": 560,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n * This file is part of the libspotify examples suite.\n * See RandomifyAppDel"
},
{
"path": "libspotify/examples/Randomify/SpotifyTrack.m",
"chars": 1102,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n * This file is part of the libspotify examples suite.\n * See RandomifyAppDel"
},
{
"path": "libspotify/examples/Randomify/main.m",
"chars": 253,
"preview": "//\n// main.m\n// Randomify\n//\n// Created by Joachim Bengtsson on 2010-03-30.\n// Copyright 2010 Spotify. All rights re"
},
{
"path": "libspotify/examples/appkey.c",
"chars": 172,
"preview": "#error\n#error \"You need to replace this file with a libspotify API key provided by Spotify. Please see https://developer"
},
{
"path": "libspotify/examples/common.mk",
"chars": 1208,
"preview": "# Copyright (c) 2010 Spotify Ltd\n\nall:\tcheck-libspotify $(TARGET)\n\n#\n# Direct path to libspotify\n#\nifdef LIBSPOTIFY_PATH"
},
{
"path": "libspotify/examples/jukebox/Makefile",
"chars": 1051,
"preview": "ifeq ($(shell uname),Darwin)\nifdef USE_AUDIOQUEUE\nAUDIO_DRIVER ?= osx\nLDFLAGS += -framework AudioToolbox\nelse\nAUDIO_DRIV"
},
{
"path": "libspotify/examples/jukebox/alsa-audio.c",
"chars": 5588,
"preview": "/*\n * Copyright (c) 2006-2009 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "libspotify/examples/jukebox/audio.c",
"chars": 1840,
"preview": "/*\n * Copyright (c) 2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "libspotify/examples/jukebox/audio.h",
"chars": 1828,
"preview": "/*\n * Copyright (c) 2006-2009 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "libspotify/examples/jukebox/dummy-audio.c",
"chars": 1718,
"preview": "/*\n * Copyright (c) 2006-2009 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "libspotify/examples/jukebox/jukebox.c",
"chars": 15156,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/jukebox/openal-audio.c",
"chars": 4469,
"preview": "/*\n * Copyright (c) 2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "libspotify/examples/jukebox/osx/jukebox.xcodeproj/project.pbxproj",
"chars": 9383,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 45;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "libspotify/examples/jukebox/osx-audio.c",
"chars": 3378,
"preview": "/*\n * Copyright (c) 2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "libspotify/examples/jukebox/playtrack.c",
"chars": 8860,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/jukebox/queue.h",
"chars": 18990,
"preview": "/*\n * Copyright (c) 1991, 1993\n *\tThe Regents of the University of California. All rights reserved.\n *\n * Redistributio"
},
{
"path": "libspotify/examples/localfiles/Makefile",
"chars": 296,
"preview": "TARGET=posix_stu\nCFLAGs += -Werror\n\ninclude ../common.mk\n\n$(TARGET): main.o appkey.o\n\t$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $"
},
{
"path": "libspotify/examples/localfiles/main.c",
"chars": 5510,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/localfiles/main.h",
"chars": 73,
"preview": "#ifndef __MAIN_H__\n#define __MAIN_H__\n#include <libspotify/api.h>\n#endif\n"
},
{
"path": "libspotify/examples/spshell/Makefile",
"chars": 510,
"preview": "TARGET=spshell\nLDLIBS += -lreadline\n\n\nifdef SP_LIBSPOTIFY_WITH_SCROBBLING\nCFLAGS += -DSP_LIBSPOTIFY_WITH_SCROBBLING=1\nOB"
},
{
"path": "libspotify/examples/spshell/browse.c",
"chars": 8201,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/cmd.c",
"chars": 4467,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/cmd.h",
"chars": 3162,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/inbox.c",
"chars": 3072,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/osx/spshell.xcodeproj/project.pbxproj",
"chars": 12287,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 45;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "libspotify/examples/spshell/playlist.c",
"chars": 10536,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/scrobbling.c",
"chars": 4660,
"preview": "/**\n * Copyright (c) 2006-2012 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/search.c",
"chars": 3723,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/spshell.c",
"chars": 8226,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/spshell.h",
"chars": 2148,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/spshell_posix.c",
"chars": 5451,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/spshell_win32.c",
"chars": 4413,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/star.c",
"chars": 2320,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/test.c",
"chars": 24127,
"preview": "/**\n * Copyright (c) 2006-2011 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/toplist.c",
"chars": 3544,
"preview": "/**\n * Copyright (c) 2006-2010 Spotify Ltd\n *\n * Permission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "libspotify/examples/spshell/win32/spshell.vcproj",
"chars": 4766,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<VisualStudioProject\n\tProjectType=\"Visual C++\"\n\tVersion=\"9,00\"\n\tName=\"spshell\"\n\tP"
},
{
"path": "libspotify/examples/spshell/win32/spshell.vcxproj",
"chars": 6205,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"4.0\" xmlns=\"http://schemas.microso"
}
]
// ... and 26 more files (download for full content)
About this extraction
This page contains the full source code of the TheQwertiest/foo_spotify GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 226 files (1.9 MB), approximately 644.1k tokens, and a symbol index with 452 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.