Skip to content

Commit

Permalink
Merge pull request #22 from JOJ0/sell
Browse files Browse the repository at this point in the history
DiscoDOS 3.0 - Sales, Import, Collection, Massive Refactor
  • Loading branch information
JOJ0 authored Dec 30, 2024
2 parents 8a1a710 + aad2100 commit e537d1e
Show file tree
Hide file tree
Showing 61 changed files with 6,780 additions and 3,489 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ build_app/
dist_app/
DiscoDOS-*.dmg
.python-version
wip/
50 changes: 24 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,45 @@
/ ___ / ___ / _____/
/ / / / / / \____ \
/ /__/ / /__/ _____/ /
D i s c o / / / - The geekiest DJ tool on the planet
D i s c o / / / - The record collector's toolbox
/_______/\_______/________/
```
DiscoDOS helps a DJ remember and analyze what they played in their sets, or what they could possibly play in the future. It's based on data pulled from a users [Discogs](https://www.discogs.com) record [collection](https://support.discogs.com/hc/en-us/articles/360007331534-How-Does-The-Collection-Feature-Work-). Tracks can be organized into playlists and mix-transitions rated. Additionally the collection can be linked to the online music information services [MusicBrainz](https://musicbrainz.org) ~~and [AcousticBrainz](https://acousticbrainz.org), thus further information (like musical key and BPM) is made available to the user~~.
DiscoDOS is a set of command line tools for DJ's and record collectors.

DiscoDOS currently is available as a command line tool only, though prototypes of a mobile and a desktop app exist already. Despite of what the name implies, it's just the look that is reminiscent of the 80s/90s operating system, its usability follows most standards of a typical [shell](https://en.wikipedia.org/wiki/Shell_(computing)#Unix-like_systems) utility you would find on a [UNIX-like operating system](https://en.wikipedia.org/wiki/Unix-like).
- It's based on data pulled from a user's [Discogs](https://www.discogs.com) record [collection](https://support.discogs.com/hc/en-us/articles/360007331534-How-Does-The-Collection-Feature-Work-).
- [List records for sale](https://discodos.readthedocs.io/en/latest/MANUAL.html#the-sell-command) on the Discogs Marketplace.
- [Edit existing Marketplace listings](https://discodos.readthedocs.io/en/latest/MANUAL.html#the-ls-tui-command) using a self-explanatory _TUI_ interface.
- Metadata of DJ sets can be recorded and analyzed to feed a simple ["suggestion" mechanism](https://discodos.readthedocs.io/en/latest/MANUAL.html#the-suggest-command).
- [Organize tracks into playlists](https://discodos.readthedocs.io/en/latest/MANUAL.html#the-mix-command) and rate mix-transitions.
- Link the collection to [MusicBrainz](https://musicbrainz.org) and [AcousticBrainz](https://acousticbrainz.org), to provide key and BPM data of tracks. These features are deprecated but still work partly - [read more about it here](https://discodos.readthedocs.io/en/latest/MANUAL.html#acousticbrainz-support-is-deprecated).

DiscoDOS runs on current Linux, macOS and Windows systems.
DiscoDOS is a command line tool, though [parts of it provide a TUI - a _text user interface_](https://discodos.readthedocs.io/en/latest/MANUAL.html#the-ls-tui-command). DiscoDOS is written in Python and runs on Linux, macOS and Windows.

DiscoDOS primarily aims at the Vinyl DJ but feature ideas for DJ's playing both, digital media and vinyl, are existing. Also thoughts around GUI programming and alternative approaches to finding out key and BPM are present. Have a look on the DiscoDOS website's [roadmap](https://discodos.jojotodos.net#roadmap) chapter.
### Viewing Marketplace stats, editing listings, fetching videos
![dsc ls full screen](sphinx/source/_static/ls-default-full-screen.png)

The following animated GIFs should give you an idea on how DiscoDOS looks and feels:
### Selling a record
![dsc sell](sphinx/source/_static/sell.gif)


_**Note: As of February 2022 unfortunately [the AcousticBrainz project was shut down](https://blog.metabrainz.org/2022/02/16/acousticbrainz-making-a-hard-decision-to-end-the-project/), as a consequence DiscoDOS can't fetch key and BPM anymore. Below videos are outdated and show some of those features.**_

_**Note: Find some ideas on how DiscoDOS could solve this problem [in the website's roadmap chapter](https://discodos.jojotodos.net/#roadmap).**_


<!-- omit in toc -->
##### Viewing mix details, searching and adding track:
### Viewing mix details, searching and adding track:
![demo gif 1](assets/intro_gif_v0.4_1-580_16col_960x581.gif)
<!-- omit in toc -->
##### Updating track information from Discogs and MusicBrainz/AcousticBrainz:
![demo gif 2](assets/intro_gif_v0.4_580-end_16col_960x581.gif)


Head over to the documentation pages and learn how to install and
use DiscoDOS:

- [Quickstart Guide](https://discodos.readthedocs.io/en/latest/QUICKSTART.html)
- [Installaton Guide](https://discodos.readthedocs.io/en/latest/INSTALLATION.html)
- [User's Manual](https://discodos.readthedocs.io/en/latest/MANUAL.html)
### Updating track information from Discogs and MusicBrainz/AcousticBrainz:
![demo gif 2](assets/intro_gif_v0.4_580-end_16col_960x581.gif)

Watch some video tutorials on Youtube:
_The latter two videos are slightly outdated. DiscoDOS' main command now is called `dsc` and AcousticBrainz as an actual website not existing anymore (API-only, which DiscoDOS makes use of)._
### Video Tutorials

- [How to use DiscoDOS #1 - Mixes, Suggestions, Collection, AcousticBrainz](https://www.youtube.com/watch?v=c9lqKuGSCVk&list=PLcHqk0rpp8bprmYlaXdrs6pbOpPoJwW-T)
- [How to use DiscoDOS #2 - Finding key & BPM compatible tracks](https://www.youtube.com/watch?v=agp9OrYC66I&list=PLcHqk0rpp8bprmYlaXdrs6pbOpPoJwW-T&index=3)
- [How to use DiscoDOS #3 - Fetch key & BPM from AcousticBrainz](https://www.youtube.com/watch?v=4lungDgdJ2w&list=PLcHqk0rpp8bprmYlaXdrs6pbOpPoJwW-T&index=4)

### Docs

- [Quickstart Guide](https://discodos.readthedocs.io/en/latest/QUICKSTART.html)
- [Setup Guide](https://discodos.readthedocs.io/en/latest/INSTALLATION.html)
- [User's Manual](https://discodos.readthedocs.io/en/latest/MANUAL.html)

View the DiscoDOS website:
### Website

- [discodos.jojotodos.net](https://discodos.jojotodos.net)
20 changes: 10 additions & 10 deletions discodos/cmd/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from discodos.utils import print_help # , ask user
from discodos.args_helper import User_int
from discodos.ctrls import Mix_ctrl_cli, Coll_ctrl_cli
from discodos.ctrls import MixControlCommandline, CollectionControlCommandline
from discodos.config import Db_setup, Config
import logging
import argparse
Expand Down Expand Up @@ -409,9 +409,9 @@ def _main():
# log.info("dir(args): %s", dir(args))
# USER INTERACTION OBJECT ##################################################
user = User_int(args)
log.info("user.WANTS_ONLINE: %s", user.WANTS_ONLINE)
log.debug("user.WANTS_ONLINE: %s", user.WANTS_ONLINE)
# INIT COLLECTION CONTROLLER (DISCOGS API CONNECTION) ######################
coll_ctrl = Coll_ctrl_cli(
coll_ctrl = CollectionControlCommandline(
False, user,
conf.discogs_token, conf.discogs_appid, conf.discobase,
conf.musicbrainz_user, conf.musicbrainz_password
Expand All @@ -436,7 +436,7 @@ def _main():
if coll_ctrl.ONLINE:
discogs_rel_found = coll_ctrl.search_release(searchterm)
if user.WANTS_TO_ADD_TO_MIX or user.WANTS_TO_ADD_AT_POSITION:
mix_ctrl = Mix_ctrl_cli(
mix_ctrl = MixControlCommandline(
False, args.add_to_mix, user, conf.discobase
)
mix_ctrl.add_discogs_track(
Expand Down Expand Up @@ -472,7 +472,7 @@ def _main():
if not database_rel_found:
return
if user.WANTS_TO_ADD_TO_MIX or user.WANTS_TO_ADD_AT_POSITION:
mix_ctrl = Mix_ctrl_cli(
mix_ctrl = MixControlCommandline(
False, args.add_to_mix, user, conf.discobase
)
mix_ctrl.add_offline_track(
Expand All @@ -494,7 +494,7 @@ def _main():
### NO MIX ID GIVEN ########################################################
if user.WANTS_TO_SHOW_MIX_OVERVIEW:
# we instantiate a mix controller object
mix_ctrl = Mix_ctrl_cli(False, args.mix_name, user, conf.discobase)
mix_ctrl = MixControlCommandline(False, args.mix_name, user, conf.discobase)
if user.WANTS_TO_PULL_TRACK_INFO_IN_MIX_MODE:
mix_ctrl.pull_track_info_from_discogs(
coll_ctrl,
Expand All @@ -512,11 +512,11 @@ def _main():
### MIX ID GIVEN ###########################################################
### SHOW MIX DETAILS #######################################################
elif user.WANTS_TO_SHOW_MIX_TRACKLIST:
log.info("A mix_name or ID was given. Instantiating Mix_ctrl_cli class.\n")
mix_ctrl = Mix_ctrl_cli(
log.info("A mix_name or ID was given. Instantiating MixControlCommandline class.\n")
mix_ctrl = MixControlCommandline(
False, args.mix_name, user, conf.discobase
)
# coll_ctrl = Coll_ctrl_cli(conn, user)
# coll_ctrl = CollectionControlCommandline(conn, user)
### CREATE A NEW MIX ###################################################
if user.WANTS_TO_CREATE_MIX:
mix_ctrl.create()
Expand Down Expand Up @@ -628,7 +628,7 @@ def _main():

##### STATS MODE ###########################################################
if user.WANTS_TO_SHOW_STATS:
# mix_ctrl = Mix_ctrl_cli(False, args.mix_name, user, conf.discobase)
# mix_ctrl = MixControlCommandline(False, args.mix_name, user, conf.discobase)
coll_ctrl.view_stats()

##### SETUP MODE ###########################################################
Expand Down
44 changes: 33 additions & 11 deletions discodos/cmd23/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
#!/usr/bin/env python
from discodos.config import Config
from discodos.cmd23 import helper, import_, mix, search, setup, stats, suggest
import logging
import click

from discodos.config import Config
from discodos.cmd23 import (
helper,
import_,
mix,
search,
setup,
stats,
suggest,
ls,
sell,
clean,
links,
)


# globals we use for logging, argparser and user interaction object
log = logging.getLogger('discodos')
args = None
user = None


@click.group(
cls=helper.AbbreviationGroup,
invoke_without_command=True,
context_settings=dict(help_option_names=["-h", "--help"]))
@click.option(
Expand All @@ -20,21 +31,32 @@
the hood (-v is INFO level, -vv is DEBUG level).""")
@click.option(
"-o", "--offline", "offline_mode", is_flag=True,
help="""DiscoDOS checks for connectivity to online services
(Discogs, MusicBrainz, AcousticBrainz) itself. This option
forces offline mode. A lot of options work in on- and
offline mode. Some behave differently, depending on connection state.""")
help="""Enabling this flag prevents DiscoDOS to check for connectivity to
online services (Discogs, MusicBrainz) and forces offline mode. A lot of
DiscoDOS' functionality works well in on- and offline mode but might behave
differently, depending on connection state.""")
@click.option(
"-t/-x", "--tui/--no-tui", default=None, show_default=True,
help="""Use a TUI (Textual framework) version if available. Currently only affects
"dsc ls" command. Overrides "enable_tui" config option.
""")
@click.pass_context
def main_cmd(context, verbose_count, offline_mode):
def main_cmd(context, verbose_count, offline_mode, tui):
conf = Config()
if tui is not None:
conf.enable_tui = tui
log.handlers[0].setLevel(conf.log_level) # set configured console log lvl
context.obj = helper.User(conf, verbose_count, offline_mode)


# Add commands
main_cmd.add_command(mix.mix_cmd)
main_cmd.add_command(search.search_cmd)
main_cmd.add_command(import_.import_cmd)
main_cmd.add_command(import_.import_group)
main_cmd.add_command(suggest.suggest_cmd)
main_cmd.add_command(stats.stats_cmd)
main_cmd.add_command(setup.setup_cmd)
main_cmd.add_command(ls.ls_cmd)
main_cmd.add_command(links.links_cmd)
main_cmd.add_command(sell.sell_cmd)
main_cmd.add_command(clean.clean_group)
70 changes: 70 additions & 0 deletions discodos/cmd23/clean.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import logging
import click

from discodos.cmd23.helper import AbbreviationGroup
from discodos.ctrl import CollectionControlCommandline

log = logging.getLogger('discodos')


@click.group(cls=AbbreviationGroup, name='clean')
def clean_group():
"""Clean up orphaned DiscoBASE entries."""

@clean_group.command(name='sales')
@click.option(
"--resume", "-r", "offset", metavar='OFFSET',
type=int, default=0,
help='''Resumes at the given offset position (expects a number).''')
@click.pass_obj
def clean_sales_cmd(helper, offset):
"""Clean up the DiscoBASE sales inventory.
Remove entries from the DiscoBASE sales inventory when they have been removed from
the online Discogs Marketplace inventory.
"""
def update_user_interaction_helper(user):
log.debug("Entered clean up DiscoBASE sales inventory mode.")
return user

user = update_user_interaction_helper(helper)
log.debug("user.WANTS_ONLINE: %s", user.WANTS_ONLINE)
coll_ctrl = CollectionControlCommandline(
False, user, user.conf.discogs_token, user.conf.discogs_appid,
user.conf.discobase, user.conf.musicbrainz_user,
user.conf.musicbrainz_password)

coll_ctrl.cleanup_sales_inventory(offset=offset)


@clean_group.command(name='collection')
@click.option(
"--resume", "-r", "offset", metavar='OFFSET',
type=int, default=0,
help='''Resumes at the given offset position (expects a number).''')
@click.pass_obj
def clean_collection_cmd(helper, offset):
"""
Clean up the DiscoBASE collection.
Marks items orphaned in the release table if non-existent in the online Discogs
collection anymore.
TLDR, it uses the the in_d_collection flag in the release table to keep track of the
online collection state. It does not delete rows in the releases table ever (use the
"import release -d" flag to fix such issues). The reason for that is that release
data might still be required for DiscoDOS' sales inventory or mixes related
features.
"""
def update_user_interaction_helper(user):
log.debug("Entered clean up DiscoBASE collection inventory mode.")
return user

user = update_user_interaction_helper(helper)
log.debug("user.WANTS_ONLINE: %s", user.WANTS_ONLINE)
coll_ctrl = CollectionControlCommandline(
False, user, user.conf.discogs_token, user.conf.discogs_appid,
user.conf.discobase, user.conf.musicbrainz_user,
user.conf.musicbrainz_password)

coll_ctrl.cleanup_collection(offset=offset)
43 changes: 39 additions & 4 deletions discodos/cmd23/helper.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
import logging
from click import Group

log = logging.getLogger('discodos')


class User(object):
class AbbreviationGroup(Group):
"""A version of click.Group allowing abbreviated commands."""
def get_command(self, ctx, cmd_name):
# Match full command name first
if command := super().get_command(ctx, cmd_name):
return command

# Allow abbreviated commands
matches = [
command
for command in self.list_commands(ctx)
if command.startswith(cmd_name)
]
if not matches:
return None # No match
if len(matches) == 1:
return super().get_command(ctx, matches[0]) # Unique match
ctx.fail(f"Ambiguous command '{cmd_name}'. Matches: {', '.join(matches)}")


class User():
""" CLI user interaction class - holds info about what user wants to do,
"""
def __init__(self, conf, verbose, offline):
def __init__(self, conf, verbose, offline): # pylint: disable=too-many-statements
self.conf = conf
self.verbose = verbose
self.set_console_log_level()
# General
self.WANTS_ONLINE = False if offline else True
self.DID_NOT_PROVIDE_COMMAND = False
# Search
self.WANTS_TO_LIST_ALL_RELEASES = False
self.WANTS_TO_SEARCH_FOR_RELEASE = False
# Mix
self.WANTS_TO_ADD_TO_MIX = False
self.WANTS_TO_SHOW_MIX_OVERVIEW = False
self.WANTS_TO_SHOW_MIX_TRACKLIST = False
Expand All @@ -28,33 +53,43 @@ def __init__(self, conf, verbose, offline):
self.WANTS_TO_ADD_AT_POS_IN_MIX_MODE = False
self.WANTS_TO_COPY_MIX = False
self.WANTS_TO_DELETE_MIX = False
self.WANTS_TO_EDIT_MIX = False
# Suggest & Bulk Edit
self.WANTS_SUGGEST_TRACK_REPORT = False
self.WANTS_TO_BULK_EDIT = False
self.WANTS_SUGGEST_BPM_REPORT = False
self.WANTS_SUGGEST_KEY_REPORT = False
self.WANTS_SUGGEST_KEY_AND_BPM_REPORT = False
# Brainz
self.WANTS_TO_PULL_BRAINZ_INFO = False
self.WANTS_TO_PULL_BRAINZ_INFO_IN_MIX_MODE = False
self.BRAINZ_SEARCH_DETAIL = 1
self.BRAINZ_FORCE_UPDATE = False
self.BRAINZ_SKIP_UNMATCHED = False
self.WANTS_MUSICBRAINZ_MIX_TRACKLIST = False
self.WANTS_TO_EDIT_MIX = False
self.DID_NOT_PROVIDE_COMMAND = False
# Search & Update
self.WANTS_TO_SEARCH_AND_UPDATE_DISCOGS = False
self.WANTS_TO_SEARCH_AND_UPDATE_BRAINZ = False
# Import & Add
self.WANTS_TO_IMPORT_COLLECTION = False
self.WANTS_TO_IMPORT_RELEASE = False
self.WANTS_TO_ADD_AND_IMPORT_RELEASE = False
self.WANTS_TO_IMPORT_RELEASE_WITH_TRACKS = False
self.WANTS_TO_ADD_AND_IMPORT_RELEASE_WITH_TRACKS = False
self.WANTS_TO_REMOVE_AND_DELETE_RELEASE = False
self.WANTS_TO_IMPORT_COLLECTION_WITH_TRACKS = False
self.WANTS_TO_IMPORT_COLLECTION_WITH_BRAINZ = False
# Misc
self.WANTS_TO_SEARCH_AND_EDIT_TRACK = False
self.RESUME_OFFSET = 0
self.WANTS_TO_LAUNCH_SETUP = False
self.WANTS_TO_FORCE_UPGRADE_SCHEMA = False
self.MIX_SORT = False
self.WANTS_TO_SHOW_STATS = False
self.TABLE_FORMAT_OVERRIDE = False
# Sales
self.WANTS_TO_ADD_SALES_LISTING = False
self.WANTS_TO_EDIT_SALES_LISTING = False

def set_console_log_level(self):
""" Handles console log level setting.
Expand Down
Loading

0 comments on commit e537d1e

Please sign in to comment.