12 Commits

3 changed files with 181 additions and 34 deletions

View File

@@ -9,26 +9,35 @@ At the moment, an alpha version is undertaken, with these features:
### Database
- The sqlite-database holds a growing number of piano-solo pieces, together with their exact catalogue-numbers and composers.
- Prepared in this database is a table for users (pianists), that marks which of the pieces can be played.
- Enclosed in this database is a table for users (pianists), in which is marked which of the works and movements can be played.
### Application and usage
### Application and usage, Roadmap
- Create new users (`n username`)
- Show users (`su`)
- Set an active user (`a number_of_user`)
- Show the pieces stored in the database (`s`)
- Mark pieces as mastered for the active user (`m number_of_work`)
- Mark work as mastered for the active user (`m number_of_work`)
- Show pieces by composer (`c` shows composers, `s number_of_composer` shows pieces)
- Show movements through works (`sw work_number`)
- Mark movement as mastered for active user (`m number_of_work number_of_movement`)
- Show possible commands (`h`)
- Show mastered movements for activated user (`sm`)
- (planned) display, if work or movement is mastered from active user when viewing works and movements
- (planned) Save recordings per movement in db
- (planned) Listen to saved recordings
- (planned) Works are shown sorted (opus, collection e.g. "Walzer", "Sonaten")
- display, if work or movement is mastered from active user when viewing works and movements
- Works are shown sorted by composer and opus/work_directory-number (`s`, `smo`) or by composer and collection (`sm`)
- Remove work or movement from being mastered for active user (`r number_of_work` or `r number_of_work number_of_movement`)
- Remove user (`delete_my_user_record_including_all_mastered_works`)
- Fixed bug: double marking crashes the program
- Fixed bug: check for user before setting
## Requirements
_(planned) Release piano_repertoire_cli V 0.0.1 alpha (learn about versioning before)_
_(planned) Port functionality to a kivy-GUI_
- (planned) Save recordings per movement in userspace
- (planned) Listen to saved recordings
## Requirements and how to run the application
- Python 3.9 or higher
- Enough disk space to hold the database
@@ -36,4 +45,5 @@ At the moment, an alpha version is undertaken, with these features:
## Known Bugs
- Marking a piece as mastered the second time, the database wont insert the entry and the program will crash
- Trying to mark works as mastered that are not present crashes the program
- Providing alphabetic chars where numbers are expected crashes the program

View File

@@ -1,4 +1,5 @@
import sqlite3
from functools import total_ordering
class Session:
@@ -8,20 +9,33 @@ class Session:
self.resultstring = 'no results yet'
self.commands = {'a': (self.set_user, 'Set an active user\n Usage: a number_of_user'),
'c': (self.show_composers, 'Shows composers'),
'delete_my_user_record_including_all_mastered_works': (self.delete_user, 'DangerZone: deletes user and all marks of mastered works'),
'h': (self.show_help, 'Displays this help document'),
'm': (self.mark_work_as_mastered, 'Marks work or movement as mastered for the active user\n Usage: m number_of_work, m number_of_work number_of_movement'),
'n': (self.create_new_user, 'Creates new user\n Usage: n firstname name'),
'r': (self.remove_work_as_mastered, 'Unmarks work or movement as mastered for the active user\n Usage: r number_of_work, r number_of_work number_of_movement'),
's': (self.show_works, 'Show the works stored in the database\n Usage: s, s number_of_composer'),
'sm': (self.show_mastered, 'Show mastered movements for activated user'),
'smo': (self.show_mastered_opus, 'Show mastered movements for activated user, sorted by opus.'),
'sw': (self.show_movements, 'Show movements of a work\n Usage: sw work_number'),
'su': (self.show_users, 'Show all users'),
'q': (self.quit, 'Quits the program')
}
def set_user(self, arguments):
resultstring = ''
db_index = arguments[0]
self.user = db_index
return 'User set'
sql_command = f'''
SELECT *
FROM pianist
WHERE id = {db_index}
'''
if self.db_agent.execute(sql_command).fetchone():
self.user = db_index
resultstring += 'User set'
else:
resultstring += 'No such user'
return resultstring
def get_active_user(self):
sql_command = f'''
@@ -46,7 +60,29 @@ class Session:
def result(self):
return self.resultstring
def movement_is_mastered(self, work_id, mov_id):
is_mastered = False
if self.user != -1:
sql_command = f'''
SELECT *
FROM is_able_to_play
WHERE work_id = {work_id}
AND mov_id = {mov_id}
AND pianist_id = {self.user}
'''
if self.db_agent.execute(sql_command).fetchone():
is_mastered = True
return is_mastered
def percentage_of_work_is_mastered(self, work): # work, not work_id, as a work-object is created anyway when there's need to check
number_of_movements = len(work.values['movements'])
count_mastered = 0
for key in work.values['movements']:
if self.movement_is_mastered(work.id(), key):
count_mastered += 1
return int(count_mastered / number_of_movements * 100)
def create_new_user(self, arguments):
first_name, name = arguments[0], arguments[1]
sql_command = f'''
@@ -56,6 +92,21 @@ class Session:
self.db_agent.execute(sql_command)
return f'Created new user {first_name} {name}.'
def delete_user(self, _):
resultstring = ''
if self.user == -1:
resultstring += 'Please activate user'
else:
user_name = self.get_active_user()
sql_command = f'''
DELETE FROM pianist
WHERE id = {self.user}
'''
self.db_agent.execute(sql_command)
resultstring += f'Deleted user {user_name}'
return resultstring
def show_composers(self, _):
sql_command = '''
SELECT id, first_name, name
@@ -81,7 +132,7 @@ class Session:
fun_resultstring += f'{key}: {value[1]}\n'
return fun_resultstring
def show_mastered(self, _):
def show_mastered(self, _, key='standard'):
fun_resultstring = 'Please activate user'
if not self.user == -1:
sql_command = f'''
@@ -95,6 +146,11 @@ class Session:
for item in list_of_work_ids:
list_of_works.append(work_under_id(item[0], self.db_agent))
fun_resultstring = 'All mastered movements:\n'
if key == 'opus':
list_of_works.sort(key=lambda work_s: work_s.opus_and_wd_for_comparison())
else:
list_of_works.sort(key=lambda work_s: work_s.sammlung())
list_of_works.sort()
for work in list_of_works:
for mov_number in work.values['movements'].keys():
# check if movement is mastered
@@ -110,11 +166,18 @@ class Session:
fun_resultstring += f'{work.id()} {mov_number}. {work.pretty_mov(mov_number)}\n'
return fun_resultstring
def show_mastered_opus(self, arguments):
return self.show_mastered(arguments, key='opus')
def show_movements(self, arguments):
work_id = arguments[0]
work = work_under_id(work_id, self.db_agent)
fun_resultstring = ''
for mov_number in work.values['movements'].keys():
if self.movement_is_mastered(work_id, mov_number):
fun_resultstring += '[x] '
else:
fun_resultstring += '[ ] '
fun_resultstring += f'{mov_number}. {work.pretty_mov(mov_number)}\n'
return fun_resultstring
@@ -144,30 +207,82 @@ class Session:
for item in list_of_work_ids:
list_of_works.append(work_under_id(item[0], self.db_agent))
fun_resultstring = ''
for work in list_of_works:
list_of_works.sort(key=lambda wo: wo.opus_and_wd_for_comparison())
for work in sorted(list_of_works):
mastered = self.percentage_of_work_is_mastered(work)
if mastered == 100:
fun_resultstring += '[x] '
elif mastered > 0:
fun_resultstring += '[/] '
else:
fun_resultstring += '[ ] '
fun_resultstring += f'{work.id()}: {work.pretty_string()}\n'
return fun_resultstring
def mark_work_as_mastered(self, arguments):
work_id = arguments[0]
resultstring = 'adding:\n'
work = work_under_id(work_id, self.db_agent)
if len(arguments) > 1: # in case there is a movement number given
mov_number = int(arguments[1])
sql_command = f'''
INSERT INTO is_able_to_play (work_id, mov_id, pianist_id)
VALUES ({work_id}, {mov_number}, {self.user})
'''
self.db_agent.execute(sql_command)
resultstring += f'{work.pretty_mov(mov_number)}\n'
else: # mark all movements of the work as mastered
for mov_number in work.values['movements'].keys():
sql_command = f'''
INSERT INTO is_able_to_play (work_id, mov_id, pianist_id)
VALUES ({work_id}, {mov_number}, {self.user})
'''
self.db_agent.execute(sql_command)
resultstring += f'{work.pretty_mov(mov_number)}\n'
if self.user == -1:
resultstring = 'Please activate user'
else:
work_id = arguments[0]
resultstring = 'adding:\n'
work = work_under_id(work_id, self.db_agent)
if len(arguments) > 1: # in case there is a movement number given
mov_number = int(arguments[1])
if self.movement_is_mastered(work_id, mov_number):
resultstring += f'Already marked as mastered: {work.pretty_mov(mov_number)}\n'
else:
sql_command = f'''
INSERT INTO is_able_to_play (work_id, mov_id, pianist_id)
VALUES ({work_id}, {mov_number}, {self.user})
'''
self.db_agent.execute(sql_command)
resultstring += f'{work.pretty_mov(mov_number)}\n'
else: # mark all movements of the work as mastered
for mov_number in work.values['movements'].keys():
if self.movement_is_mastered(work_id, mov_number):
resultstring += f'Already marked as mastered: {work.pretty_mov(mov_number)}\n'
else:
sql_command = f'''
INSERT INTO is_able_to_play (work_id, mov_id, pianist_id)
VALUES ({work_id}, {mov_number}, {self.user})
'''
self.db_agent.execute(sql_command)
resultstring += f'{work.pretty_mov(mov_number)}\n'
return resultstring
def remove_work_as_mastered(self, arguments):
if self.user == -1:
resultstring = 'Please activate user'
else:
work_id = arguments[0]
resultstring = 'removing:\n'
work = work_under_id(work_id, self.db_agent)
if len(arguments) > 1: # in case there is a movment number given
mov_number = int(arguments[1])
if self.movement_is_mastered(work_id, mov_number):
sql_command = f'''
DELETE FROM is_able_to_play
WHERE work_id = {work_id}
AND mov_id = {mov_number}
AND pianist_id = {self.user}
'''
self.db_agent.execute(sql_command)
resultstring += f'{work.pretty_mov(mov_number)}\n'
else:
resultstring += f'Movement was not marked as mastered: {work.pretty_mov(mov_number)}\n'
else:
for mov_number in work.values['movements'].keys():
if self.movement_is_mastered(work_id, mov_number):
sql_command = f'''
DELETE FROM is_able_to_play
WHERE work_id = {work_id}
AND mov_id = {mov_number}
AND pianist_id = {self.user}
'''
self.db_agent.execute(sql_command)
resultstring += f'{work.pretty_mov(mov_number)}\n'
else:
resultstring += f'Movement was not marked as mastered: {work.pretty_mov(mov_number)}\n'
return resultstring
def quit(self, _):
@@ -175,6 +290,7 @@ class Session:
# End session
@total_ordering
class work_under_id:
# con = sqlite3.connect('repertoire.db')
# reader = con.cursor()
@@ -232,6 +348,22 @@ class work_under_id:
def id(self):
return self.values['id']
def just_num(self, st):
num_str = ''
for c in st:
if c.isdigit():
num_str += c
return int(num_str)
def opus_and_wd_for_comparison(self):
op = 9999 # no composer has that many!
if not self.values['opus'] is None:
op = self.just_num(self.values['opus'])
wd = 0 # no directory starts with 0
if not self.values['wd_number'] is None:
wd = self.just_num(self.values['wd_number'])
return (op, wd)
def pretty_string(self):
ret_str = ''
for key in ['first_name', 'name',
@@ -281,6 +413,9 @@ class work_under_id:
ret_str += f'{key}: {value}\n'
return ret_str
def __lt__(self, other):
return (self.values['name'] < other.values['name'])
# End work_under_id
def parse_user_input(user_in, session):
@@ -303,6 +438,8 @@ def command_line_loop(session):
def main():
con = sqlite3.connect('repertoire.db')
db_agent = con.cursor()
sql_command = 'PRAGMA foreign_keys = ON'
db_agent.execute(sql_command)
session = Session(db_agent)
command_line_loop(session)
con.commit()

View File

@@ -52,7 +52,7 @@ CREATE TABLE is_able_to_play(
recording VARCHAR(255),
PRIMARY KEY(work_id, mov_id, pianist_id),
FOREIGN KEY(work_id, mov_id) REFERENCES movement(work_id, mov_number),
FOREIGN KEY(pianist_id) REFERENCES pianist(id)
FOREIGN KEY(pianist_id) REFERENCES pianist(id) ON DELETE CASCADE
);
CREATE TABLE plays_in(
@@ -63,5 +63,5 @@ CREATE TABLE plays_in(
PRIMARY KEY(concert_id, pianist_id, work_id, mov_id),
FOREIGN KEY(concert_id) REFERENCES concert(id),
FOREIGN KEY(work_id, mov_id) REFERENCES movement(work_id, mov_number),
FOREIGN KEY(pianist_id) REFERENCES pianist(id)
FOREIGN KEY(pianist_id) REFERENCES pianist(id) ON DELETE CASCADE
);