Compare commits
28 Commits
f446120184
...
next_featu
| Author | SHA1 | Date | |
|---|---|---|---|
| 8db8deee79 | |||
| fb13941756 | |||
| c6057ff8f7 | |||
| 6129b70f8a | |||
| f343e9d87b | |||
| 50c8bb5d72 | |||
| e4fd116250 | |||
| eabe518b10 | |||
| 15fd7900de | |||
| 127e2294e5 | |||
| f42857eb56 | |||
| 327aae5130 | |||
| 2e9fc3655a | |||
| d549b98a82 | |||
| 76d44e0dfe | |||
| 7352af038b | |||
| 69d701d600 | |||
| b5041dbc54 | |||
| 7f9e806c23 | |||
| ee23e0f0e9 | |||
| b6dc9bd87b | |||
| 83c1d82387 | |||
| 488a97f3f6 | |||
| 626dfc25fc | |||
| 986eb94add | |||
| 8c311ea739 | |||
| a75a241f30 | |||
| 10894187b2 |
41
README.md
41
README.md
@@ -9,16 +9,41 @@ 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
|
||||
### Application and usage, Roadmap
|
||||
|
||||
- Create new users
|
||||
- Set an active user
|
||||
- (planned) Show the pieces stored in the database
|
||||
- (planned) Mark pieces as masterd for the active user
|
||||
- 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 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`)
|
||||
- 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 funtionality 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
|
||||
- Enough disk space to hold the database
|
||||
- run `python builddb.py` to build the SQLite-database-file before running `python rep_cli.py`
|
||||
|
||||
## Known Bugs
|
||||
|
||||
- Trying to mark works as mastered that are not present crashes the program
|
||||
- Providing alphabetic chars where numbers are expected crashes the program
|
||||
338
rep_cli.py
338
rep_cli.py
@@ -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,31 +53,135 @@ class Session:
|
||||
|
||||
def invoke_command(self, command, arguments):
|
||||
self.resultstring = ''
|
||||
if command == 'a':
|
||||
self.resultstring += self.set_user(arguments[0])
|
||||
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 == '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_users(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
|
||||
ORDER BY name
|
||||
'''
|
||||
result_list = self.db_agent.execute(sql_command).fetchall()
|
||||
fun_resultstring = ''
|
||||
for item in result_list:
|
||||
fun_resultstring += f'{item[0]}: {item[2]}, {item[1]}\n'
|
||||
return fun_resultstring
|
||||
|
||||
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, _):
|
||||
sql_command = '''
|
||||
SELECT * FROM pianist;
|
||||
'''
|
||||
@@ -61,19 +191,107 @@ class Session:
|
||||
fun_resultstring += f'{item[0]}: {item[1]} {item[2]}\n'
|
||||
return fun_resultstring
|
||||
|
||||
def show_works(self):
|
||||
sql_command = '''
|
||||
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
|
||||
WHERE {restraint}
|
||||
'''
|
||||
list_of_work_ids = self.db_agent.execute(sql_command).fetchall()
|
||||
# GET WORKS OUT!
|
||||
return ''
|
||||
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 = ''
|
||||
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):
|
||||
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, _):
|
||||
return 'Bye bye!'
|
||||
|
||||
# End session
|
||||
|
||||
class Werk_unter_id:
|
||||
@total_ordering
|
||||
class work_under_id:
|
||||
# con = sqlite3.connect('repertoire.db')
|
||||
# reader = con.cursor()
|
||||
|
||||
@@ -108,12 +326,12 @@ class Werk_unter_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:
|
||||
nummern.add(satz[2])
|
||||
self.values['Sätze'] = saetze
|
||||
self.values['movements'] = saetze
|
||||
if len(nummern) == 1:
|
||||
self.sätze_unter_nummer = True
|
||||
self.values['numb'] = nummern.pop()
|
||||
@@ -127,6 +345,67 @@ class Werk_unter_id:
|
||||
else:
|
||||
return sammlung
|
||||
|
||||
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',
|
||||
'title', 'opus',
|
||||
'main_key', 'alias',
|
||||
'work_directory','wd_number']:
|
||||
if not self.values[key] is None:
|
||||
if key == 'opus':
|
||||
ret_str += f'op. {self.values[key]} '
|
||||
else:
|
||||
ret_str += f'{self.values[key]} '
|
||||
return ret_str
|
||||
|
||||
def pretty_mov(self, mov_number):
|
||||
ret_str = ''
|
||||
for key in ['first_name', 'name',
|
||||
'title', 'mov_title', 'opus', 'numb',
|
||||
'main_key', 'alias',
|
||||
'movements',
|
||||
'work_directory','wd_number']:
|
||||
if key in self.values.keys() and not self.values[key] is None:
|
||||
if key == 'opus':
|
||||
ret_str += f'op. {self.values[key]} '
|
||||
elif key == 'name':
|
||||
ret_str += f'{self.values[key]}, '
|
||||
elif key == 'numb':
|
||||
ret_str += f'Nr. {self.values[key]} '
|
||||
elif key == 'movements':
|
||||
if self.values[key][mov_number][1] is None:
|
||||
ret_str += ''
|
||||
else:
|
||||
if len(self.values[key]) > 1:
|
||||
ret_str += f'{mov_number}. {self.values[key][mov_number][1]} '
|
||||
else:
|
||||
ret_str += f'{self.values[key][mov_number][1]} '
|
||||
elif key == 'title':
|
||||
if self.values['mov_title'] is None:
|
||||
ret_str += f'{self.values[key]} '
|
||||
else:
|
||||
ret_str += f'{self.values[key]} '
|
||||
return ret_str
|
||||
|
||||
def __str__(self):
|
||||
ret_str = ''
|
||||
for key, value in self.values.items():
|
||||
@@ -134,12 +413,15 @@ class Werk_unter_id:
|
||||
ret_str += f'{key}: {value}\n'
|
||||
return ret_str
|
||||
|
||||
# End Werk_unter_id
|
||||
def __lt__(self, other):
|
||||
return (self.values['name'] < other.values['name'])
|
||||
|
||||
def parse_user_input(user_in):
|
||||
# End work_under_id
|
||||
|
||||
def parse_user_input(user_in, session):
|
||||
split_user_in = user_in.split()
|
||||
command = split_user_in[0]
|
||||
if command in ('a', 'n', 'q', 's', 'su'):
|
||||
if command in session.commands.keys():
|
||||
arguments = split_user_in[1:]
|
||||
return (command, arguments)
|
||||
else:
|
||||
@@ -149,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()
|
||||
@@ -164,6 +447,5 @@ def main():
|
||||
con.close()
|
||||
print('db-connection closed. Bye-bye!')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -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
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user