CS Weekly 3: Моя музыка Postgres

Это мой третий еженедельный проект по CS. Прочтите Code Something Weekly: How and Why, чтобы получить вдохновение и идеи, лежащие в основе усилий:



Проект прошлой недели исследовал визуализатор волны с API веб-аудио:



Как диджей, я чувствовал необходимость управлять своей музыкой в ​​локальной базе данных. Конечно, на разных платформах есть такие списки, как избранное и списки пожеланий. Но ими быстро становится трудно управлять в масштабе.

В качестве примера посмотрите мой текущий список пожеланий bandcamp



который на момент написания этой статьи насчитывает колоссальные 1037 предметов.

тссс, у меня скоро день рождения, и я не буду возражать против анонимного "подарка" из этого списка

Проблема в том, что я использую этот список для:

  • материал, который я еще не слушал, но должен
  • релизы, которые можно загрузить бесплатно, но я обнаружил их на другом устройстве
  • товары, которые я еще не купил, но хочу
  • предметы, о которых я сомневаюсь
  • предметы других жанров, которые я не обязательно буду покупать

Кроме того, практически невозможно упорядочить и разобрать — и это просто Bandcamp. Не говоря уже о музыке, найденной на Deejay.de и других ресурсах.

Введите идею и вдохновение для простого инструмента: My Music Postgres, который доступен на Github (скачать zip здесь).



Управление музыкой может быть весьма обширным (просто посмотрите на все функции iTunes). Поэтому я выделил небольшой набор полезных связанных функций, предназначенных для управления покупками и прослушиванием DJ:

Этот простой инструмент командной строки делает следующее:

  • Добавить исполнителей, звукозаписывающие компании и релизы в базу данных
  • Список выпусков, которые вам понравились, но которых у вас еще нет
  • Запускает релизы, которые вы еще не слушали
  • Обновление релизов (прослушано, понравилось, куплено)

В оставшейся части этой статьи мы обсудим сценарии bash и продемонстрируем, как взаимодействовать с базой данных PostgreSQL.

Предполагается базовое знакомство с SQL. Для тех, кто совсем не знаком с SQL, существуют различные учебные пособия для изучения основ:

Схема таблицы

Дизайн намеренно минимален. У нас есть 3 таблицы:

  • Художники
  • Этикетки
  • Рекорды

Можно было бы добавить МНОГИЕ другие, особенно для тщательного применения. Некоторые примеры включают теги, RecordArtists (для многих ко многим) и жанры. Но это простой музыкальный менеджер, построенный вокруг основной потребности в управлении прослушиваниями и покупками.

Хотя это еще не реализовано (будущая версия), наличие ссылок Artist и Label позволяет нам создать простую опцию для их открытия из командной строки следующим образом:

$ my-music --open artist=ansome
>> start chrome https://soundcloud.com/myansome
$ my-music --open label=mord
>> start chrome https://mord.bandcamp.com/

Инструменты

Если вы раньше не использовали PostgreSQL или не знакомы с некоторыми инструментами, настройка будет довольно простой.

Скачать PostgreSQL можно здесь. Убедитесь, что вы включили клиентов, особенно PgAdmin. Если вы не скачивали PgAdmin, его можно скачать отдельно здесь. PgAdmin — очень мощный графический интерфейс для взаимодействия с базами данных, как показано ниже. Учитывая минималистичный дизайн и наличие ошибок в моей крошечной программе-оболочке, PgAdmin является запасным вариантом для внесения изменений в вашу базу данных. Просто было бы мучительно использовать PgAdmin каждый раз, когда вы хотите сделать что-то простое, например прослушать запись в своем списке прослушать.

Из терминала bash (если вы работаете в Windows, как и я, вы можете использовать git bash среди других опций [1][2]), вы можете легко проверить, правильно ли установлен postgres, запустив:

$ createdb --version

Затем убедитесь, что psql был установлен и добавлен к вашему пути:

$ psql --version

Написание Bash-скриптов

Хорошо, теперь, когда у нас есть инструменты, давайте обсудим сценарии bash. Я добавил несколько тем, связанных с Bash, которые я считаю полезными. Если вы полностью знакомы с Bash, перейдите к следующему основному разделу PostgreSQL в Bash.

Краткое напоминание

  • без пробелов вокруг «=» myVar=3
  • получить значение переменной с помощью «$» $myVar
  • вычислить операции, используя `` files=`ls` или $(( )) $(( myVar + 1 ))
  • Если формат блока: if..then..else..fi
  • Пока формат блока: while..do..done
  • Формат блока дел: case a);; b);; esac
  • Условия внутри [ ], помните о пробелах: [ $myVar != 3 ];

Анатомия интерактивного сценария Bash

Bash-скрипты являются процедурными, что означает, что если вы писали приложения в декларативной и/или функциональной парадигме в течение нескольких месяцев, все может показаться немного шатким.

Процедурное программирование сосредоточено вокруг набора процедур (также называемых подпрограммами, функциями), которые по сути являются вычислительными шагами. Сравните это с ключевым словом function в JavaScript, которые являются объектами первого класса (т.е. имеют замыкание и свойства). Напротив, процедуры изменяют состояние программы (это означает, что функции имеют побочные эффекты в глобальной области видимости).

Для того, чтобы сохранить наш скрипт в сопровождении, мы должны позаботиться о базовом наборе процедур и структуре самой программы.

Я разбил следующую архитектуру:

# Variables
user=postgres
dbname=MyMusic
password=postgres
# PROCEDURES
addArtist () {}
addLabel () {}
addRecord () {}
listLiked () {}
listenToRecord () {}
purchasedRecord () {}
createDatabase () {}
deleteDatabase () {}
mainLoop() {}
myMusicUsage () {}
# MAIN PROGRAM
case $1 in
...
esac
exit 0

Программа читается сверху вниз, что означает, что к тому времени, когда мы доберемся до основной программы, переменные и ссылки на процедуры уже установлены, и теперь мы просто вызываем процедуры в ответ на ввод.

Чтобы продемонстрировать, чем это отличается от скомпилированных языков и языков с подъемом, рассмотрим следующий скрипт:

#!/bin/bash
printHello
printHello ()
{
  echo "hello"
}

что дает хороший:

line 3: printHello: command not found

Другими словами, объявление процедуры необходимо проанализировать, прежде чем его можно будет вызвать.

Основной цикл

Основной цикл имеет простую цель: проанализировать параметры командной строки и вызвать соответствующие процедуры.

На аргументы командной строки ссылаются с помощью $1 $2 $3... и т. д. Для обработки нескольких аргументов можно использовать цикл, в котором каждый аргумент shift преобразуется в $1 и обрабатывается один за другим. К счастью, наша программа не принимает несколько аргументов, и это сделано для простоты.

Давайте посмотрим на варианты (my-music --help):

-c, --create - creates database
-d, --delete - deletes database.
  dbname [default=MyMusic] - Name of database
  user [default=postgres] - Name of db user
-h, --help - displays command line options
-i, --interactive - interactively add artists...

Чтобы настроить цикл для анализа этих параметров, мы можем использовать простой оператор case:

case $1 in
  -i | --interactive)
    mainLoop
    ;;
  -c | --create)
    createDatabase $2 $3
    ;;
  -d | --delete)
    deleteDatabase $2 $3
    ;;
  -h | --help)
    myMusicUsage
    exit 0
    ;;
  *)
    myMusicUsage
    exit 1
    ;;
esac

Синтаксис немного уродлив, но он не слишком отличается от других языков. Вот перевод JavaScript:

switch (arguments[0]) {
  case "-i":
  case "--interactive":
    mainLoop()
  case "-c":
  case "--create":
    createDatabase(arguments[1], arguments[2])
  case "-d":
  case "--delete":
    deleteDatabase(arguments[1], arguments[2])
  case "-h":
  case "--help":
    return myMusicUsage()
  default: return myMusicUsage()
}

Интерактивный цикл

В предыдущих разделах мы видели, что существует процедура с именем mainLoop (оглядываясь назад, я должен был назвать ее interactiveLoop). Давайте быстро рассмотрим, как настроить интерактивный цикл, так как это основная функция многих скриптов bash.

Идея состоит в том, чтобы взять то, что мы узнали в предыдущем разделе, и сделать это в цикле while:

while [ true ]; do
  echo -n "a to add artist (q|quit to quit) "
  read action
  case $action in
    a )
      addArtist
      ;;
    q | quit)
      exit 0
      ;;
  esac
done

Команда read приостанавливает выполнение, ожидая ввода данных пользователем (заканчивается клавишей [enter]). Как только пользователь введет «q» или «quit», а затем [enter], программа будет успешно завершена через exit 0. Точно так же, чтобы вернуться к предыдущему вызову стека, вы можете использовать return вместо exit.

В качестве примера следующая программа:

#!/bin/bash
read nothing
exit

завершится только после того, как вы нажмете [enter] или завершите процесс другим способом. nothing будет содержать значение того, что набрал пользователь, если что.

Процедуры

Есть два одинаковых способа написания процедур в bash, но оба они могут стать «подводным камнем» для программистов, привыкших к другим языкам.

Используя ключевое слово функции:

function function_name {
  # commands
}

Используя пустые круглые скобки:

function_name () {
  # commands
}

Вы вызываете процедуры по имени: function_name (без скобок).

С аргументами все сложнее, и это очень похоже на то, что происходит с языками ассемблера: вы помещаете их в стек вызовов:

createDatabase ()
{
  name=$1  # first arg on stack
  echo $name
  user=$2  # second arg on stack
  echo $user
}
createDatabase MyMusic postgres
# $1 will be MyMusic
# $2 will be postgres

Суть в том, чтобы не позволять скобкам обмануть вас. Вы НЕ МОЖЕТЕ сделать следующее:

createDatabase (dbname, username) {...}

PostgreSQL в Bash

Давайте посмотрим, как написать bash-процедуру, которая добавляет Release в базу данных. Это основная, содержательная процедура, включающая в себя все важные детали, используемые в других процедурах. Не забывайте, что исходники всех процедур находятся на Github, если вы хотите проверить, как они были написаны.

PSQL

Клиент, который мы будем использовать из bash, — это psql, который упакован в виде бинарного файла со стандартной загрузкой Postgres (документацию можно найти здесь).

Основное использование psql состоит в том, чтобы вызвать его собственную интерактивную программу или открыть специальную оболочку PSQL. Однако есть варианты, которые позволяют нам передавать всю необходимую нам информацию через команды, избегая дополнительного интерактивного уровня. Особенно полезны следующие:

--echo-all — печатает что выполнено и ошибки и т.д.

--quiet — не выводит никаких выполненных инструкций, ошибок или предупреждений

--command — хлеб с маслом: строка, представляющая оператор SQL

--dbname — имя базы данных. В нашем случае MyMusic

--file — считывает SQL, хранящийся в указанном файле. Я использовал это в процедуре createDatabase.

--tuples-only вместо того, чтобы печатать таблицу, он просто печатает значения (мы скоро это увидим)

и наконец

--username — имя пользователя для подключения к базе данных.

Примечание о паролях
Когда мы вызываем psql, все операции будут запрашивать пароль, что может сильно раздражать в текущей интерактивной программе. Существует способ передать пароль, установив переменную среды, как показано в потоке переполнения стека https://stackoverflow.com/a/6405296/8954866.

Чтобы убедиться, что у меня всегда была установлена ​​эта переменная, я просто добавил к своим вызовам psql эту переменную:

PGPASSWORD=$password psql ...

Добавление выпуска

Напомним, релиз имеет следующее:

  • id
  • художник
  • метка
  • заглавие
  • ссылка на сайт
  • слушал
  • понравилось
  • купил

Таким образом, мы должны сделать несколько вещей:

  1. Проверьте, что художник существует, и если нет, создайте его
  2. Убедитесь, что метка существует, и если нет, создайте ее.
  3. Получить название, ссылку, прослушать, понравиться и купить
  4. Вставьте все в таблицу Records

Запрос существования и сохранение результата
1 и 2 идентичны, поэтому давайте посмотрим на исполнителя.

Подскажите пользователю исполнителя:

echo -n "Artist: "
read artist

Теперь нам нужно проверить, существует ли этот художник.

В SQL мы знаем, что это будет запрос SELECT:

SELECT id FROM Artists WHERE name = lower('${artist}');

lower — это операция PostgreSQL, которая преобразует нашу строку в нижний регистр.

'${ }' просто преобразует переменную artist в строку.

Однако нам нужно, чтобы из нашего оператора возвращалось id, так как же нам это сделать? Установите его в переменную, конечно.

Но подождите, вывод этого оператора выглядит следующим образом:

id
----
  3
(1 row)

Как, черт возьми, мы можем получить «3» в переменной bash без какого-то действительно хакерского разбора текста?

Здесь мы используем --tuples-only вместе с --quiet.

Я разобью команду на строки, чтобы ее было легче читать, но обратите внимание, что при использовании терминала все должно быть в одной строке:

PGPASSWORD=$password
psql
--dbname=$dbname
--username=$user
--quiet
--tuples-only
command="
  SELECT id
  FROM Artists
  WHERE name = lower('${artist}');
"

который возвращает простую «3».

Нам нужно сохранить это в переменной:

artistid=`<entire statement here>`

Получив результат, мы можем отреагировать соответствующим образом:

if [[ $artistid == "" ]]; then
  addArtist # procedure call
  artistid=`...` # reuse the above statement to get the id
fi

Используйте ту же логику для «метки». Кроме того, я также использовал ту же логику с «заголовком», чтобы проверить, существует ли уже запись. Просто измените SELECT и используйте другие (читай: подходящие) переменные.

Получение всего остального и его вставка

После получения artistid, labelid и записи title получить link довольно просто:

echo -n "Record link: [enter if none] "
read link

Получение логических значений listened, liked и purchased выполняется по одному и тому же шаблону. По сути, реагируйте на значение «y» и считайте все остальное ложным:

echo -n "Did you listen? (y|n) "
read listened
if [ "$listened" == "y" ]; then
  listened=true
else
  listened=false
fi

Если вы хотите, чтобы буква «y» была нечувствительна к регистру, просто добавьте еще одно условие:

if [ "$listened" == "y" ] || [ "$listened" == "Y" ];

В качестве альтернативы можно использовать регулярное выражение для соответствия «y», «Y», «yes» и «Yes»:

yesReg="^(y|Y)(es)?$"
if [[ "$listened" =~ $yesReg ]];

На этом этапе я решил запросить у пользователя подтверждение перед вставкой выпуска:

echo "please confirm:"
echo "artist: $artist"
echo "label: $label"
echo "title: $title"
echo "link: $link"
echo "listened: $listened"
echo "liked: $liked"
echo "purchased: $purchased"
echo -n "ok? (y|n) "
read confirm
if [ "$confirm" != "y" ]; then
  return
fi

Наконец, мы вставим все, используя psql с набором — echo-all, вместе со всем остальным (PGPASSWORD, dbname и username):

psql --echo-all ... --command="<SQL COMMAND>"

SQL должен быть:

INSERT into Records
  (title, artist, label, link, listened, liked, purchased)
VALUES (
  lower('${title}'),
  $artistid,
  $labelid,
  trim('${link}'),
  $listened,
  $liked,
  $purchased
);  

Это массивная однострочная строка, поэтому я еще раз покажу полную команду со вставленными символами новой строки и пробелами. Но имейте в виду, что все это должно быть в одной строке вашего скрипта:

PGPASSWORD=$password
psql
--echo-all
--dbname=$dbname
--username=$username
--command="
  INSERT into Records
    (title, artist, label, link, listened, liked, purchased)
  VALUES(
    lower('${title}'),
    $artistid,
    $labelid,
    trim('${link}'),
    $listened
    $liked
    $purchased
  );
"

Заворачивать

В этой статье было рассмотрено, как создать и изменить локальную базу данных PostgreSQL для управления музыкой с помощью интерактивного сценария bash. Были рассмотрены основы написания сценариев Bash, включая: чтение ввода, процедурное программирование, использование условных выражений, операторы case и циклы while. psql — это основной интерфейс командной строки для взаимодействия с базой данных PostgreSQL из скрипта bash. В частности, мы можем отправлять информацию в качестве аргументов через psql, используя
--command,
--dbname и
--username.

Спасибо, что ознакомились с моим проектом CS Weekly.

P.S. Если вам интересно читать о диджейских статьях с точки зрения новичков и любителей, я начал серию под названием Brainwaves. Первая (и пока единственная) статья посвящена выбору треков:



P.P.S. Я руководитель индустриального техно. Не стесняйтесь послушать один из моих миксов или подпишитесь на меня:



Удачного кодирования :)