Compare commits

14 Commits

Author SHA1 Message Date
cc12eed30b fixes typo in README 2023-11-16 11:30:42 +01:00
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
3 changed files with 183 additions and 36 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`)
- (planned) Show mastered movements for activated user
- (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")
- 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
## 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'''
@@ -47,6 +61,28 @@ 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

@@ -49,10 +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 BLOB,
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
);