Compare commits

21 Commits

Author SHA1 Message Date
8db8deee79 fixes bug: setting user who is not present in pianist-table is no longer allowed 2023-11-16 11:22:03 +01:00
fb13941756 fixes bug: crash by marking work as mastered twice, updates README with more known bugs 2023-11-16 11:15:10 +01:00
c6057ff8f7 new feature: delete user including all other related records 2023-11-16 10:56:29 +01:00
6129b70f8a new feature: remove works or movements from being mastered 2023-11-15 18:18:42 +01:00
f343e9d87b new feature: output is sorted by composer/opus or composer/collection 2023-11-15 16:10:09 +01:00
50c8bb5d72 updates README, includes sort of a roadmap 2023-11-14 09:58:58 +01:00
e4fd116250 Sort of a feature: Show mastered is now sorted: composer and collection 2023-11-10 22:14:52 +01:00
eabe518b10 Merge branch 'next_feature' 2023-11-10 21:48:29 +01:00
15fd7900de updates README 2023-11-10 21:48:10 +01:00
127e2294e5 New feature: if work is mastered is shown for active user by checkbox 2023-11-10 21:47:36 +01:00
f42857eb56 displays mastered per movement (not yet per work) 2023-11-10 08:54:11 +01:00
327aae5130 changes recording datatype from BLOB to VARCHAR, as I made up my mind and think, that storing the recordings as files somewhere is better than putting them in the database. recording should point to the path where the file can be found. (Maybe is copied and renamed to a nice place within the userspace) 2023-11-10 08:24:00 +01:00
2e9fc3655a updates README 2023-11-09 10:30:14 +01:00
d549b98a82 New feature: sm shows movements that are mastered by the active user 2023-11-09 10:28:28 +01:00
76d44e0dfe updates README 2023-11-09 09:46:40 +01:00
7352af038b moves recording BLOB from table movement to table is_able_to_play, so that recordings can be stored per user, not per movement 2023-11-09 09:40:10 +01:00
69d701d600 updates README with newly planned features 2023-11-09 09:34:11 +01:00
b5041dbc54 updates README 2023-11-08 18:44:46 +01:00
7f9e806c23 New feature: h displays help 2023-11-08 18:43:10 +01:00
ee23e0f0e9 simplifies adding of commands, as those can be added in commands-dictionary. Prepares help-texts as well. 2023-11-08 17:58:29 +01:00
b6dc9bd87b updates README 2023-11-08 12:50:26 +01:00
3 changed files with 259 additions and 63 deletions

View File

@@ -9,23 +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`)
- Show pieces by composer (`sc` shows composers, `c number_of_composer` shows pieces)
- Show movements through pieces (`sp piece_number`)
- 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`)
- (planned) Show mastered works/movements for activated user
- (planned) Save recordings per movement in db
- Show possible commands (`h`)
- Show mastered movements for activated user (`sm`)
- 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
_(planned) Release piano_repertoire_cli V 0.0.1 alpha (learn about versioning before)_
_(planned) Port funtionality to a kivy-GUI_
- (planned) Save recordings per movement in userspace
- (planned) Listen to saved recordings
## Requirements
## Requirements and how to run the application
- Python 3.9 or higher
- Enough disk space to hold the database
@@ -33,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:
@@ -6,10 +7,35 @@ class Session:
self.user = -1
self.db_agent = db_agent
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, db_index):
self.user = db_index
return 'User set'
def set_user(self, arguments):
resultstring = ''
db_index = arguments[0]
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'''
@@ -27,39 +53,61 @@ class Session:
def invoke_command(self, command, arguments):
self.resultstring = ''
if command == 'a':
self.resultstring += self.set_user(arguments[0])
elif command == 'c':
self.resultstring += self.show_composers()
elif command == 'm':
self.resultstring += self.mark_work_as_mastered(arguments)
elif command == 'n':
self.create_new_user(arguments[0], arguments[1])
self.resultstring += f'Created new user {arguments[0]} {arguments[1]}.'
elif command == 's':
self.resultstring += self.show_works()
elif command == 'sc':
self.resultstring += self.show_works(restraint=f'comp_id = {arguments[0]}')
elif command == 'sp':
self.resultstring += self.show_movements(arguments[0])
elif command == 'su':
self.resultstring += self.show_users()
elif command == 'q':
self.resultstring += 'Bye-bye'
if command in self.commands.keys():
self.resultstring += self.commands[command][0](arguments)
else:
self.resultstring += 'Could not understand command.'
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 create_new_user(self, first_name, name):
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'''
INSERT INTO pianist (first_name, sec_name)
VALUES ("{first_name}", "{name}");
'''
self.db_agent.execute(sql_command)
return f'Created new user {first_name} {name}.'
def show_composers(self):
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
FROM composer
@@ -71,14 +119,69 @@ class Session:
fun_resultstring += f'{item[0]}: {item[2]}, {item[1]}\n'
return fun_resultstring
def show_movements(self, work_id):
def show_help(self, arguments):
fun_resultstring = ''
if len(arguments) > 0:
qu_com = arguments[0]
if qu_com in self.commands:
fun_resultstring += f'{qu_com}: {self.commands[qu_com][1]}'
else:
fun_resultstring += f'Command {qu_com} not known, for help press h'
else:
for key, value in self.commands.items():
fun_resultstring += f'{key}: {value[1]}\n'
return fun_resultstring
def show_mastered(self, _, key='standard'):
fun_resultstring = 'Please activate user'
if not self.user == -1:
sql_command = f'''
SELECT work_id
FROM is_able_to_play
WHERE pianist_id = {self.user}
GROUP BY work_id
'''
list_of_work_ids = self.db_agent.execute(sql_command).fetchall()
list_of_works = list()
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
sql_command = f'''
SELECT *
FROM is_able_to_play
WHERE work_id = {work.id()}
AND mov_id = {mov_number}
AND pianist_id = {self.user}
'''
is_mastered = len(self.db_agent.execute(sql_command).fetchall()) > 0
if is_mastered:
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
def show_users(self):
def show_users(self, _):
sql_command = '''
SELECT * FROM pianist;
'''
@@ -88,7 +191,11 @@ class Session:
fun_resultstring += f'{item[0]}: {item[1]} {item[2]}\n'
return fun_resultstring
def show_works(self, restraint='1=1'):
def show_works(self, arguments):
if len(arguments) > 0:
restraint = f'comp_id = {arguments[0]}'
else:
restraint = '1 = 1'
sql_command = f'''
SELECT id
FROM work
@@ -100,34 +207,90 @@ 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:
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'
if self.user == -1:
resultstring = 'Please activate user'
else:
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'
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, _):
return 'Bye bye!'
# End session
@total_ordering
class work_under_id:
# con = sqlite3.connect('repertoire.db')
# reader = con.cursor()
@@ -163,7 +326,7 @@ class work_under_id:
saetze = dict()
nummern = set()
suw_liste = self.reader.execute(sql_suw).fetchall()
# 0 work_id, 1 mov_number, 2 numb, 3 designation, 4 mus_key, 5 recording
# 0 work_id, 1 mov_number, 2 numb, 3 designation, 4 mus_key
for satz in suw_liste:
saetze[satz[1]] = satz[2], satz[3], satz[4]
if not satz[2] is None:
@@ -185,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',
@@ -234,12 +413,15 @@ 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):
def parse_user_input(user_in, session):
split_user_in = user_in.split()
command = split_user_in[0]
if command in ('a', 'c', 'm', 'n', 'q', 's', 'sc', 'sp', 'su'):
if command in session.commands.keys():
arguments = split_user_in[1:]
return (command, arguments)
else:
@@ -249,14 +431,15 @@ def command_line_loop(session):
user_in = ''
while not user_in == 'q':
user_in = input(f'Piano-Repertoire: {session.get_active_user()} >>> ')
command, arguments = parse_user_input(user_in)
command, arguments = parse_user_input(user_in, session)
session.invoke_command(command, arguments)
# print(f'command: {command}, arguments: {arguments}')
print(session.result())
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

@@ -27,7 +27,6 @@ CREATE TABLE movement (
numb VARCHAR(15),
designation VARCHAR(63),
mus_key VARCHAR(15),
recording BLOB,
PRIMARY KEY(work_id, mov_number),
FOREIGN KEY(work_id) REFERENCES work(id)
);
@@ -50,9 +49,10 @@ CREATE TABLE is_able_to_play(
mov_id INTEGER NOT NULL,
pianist_id INTEGER NOT NULL,
days_to_practice INTEGER DEFAULT 7,
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
);