text: # -*- coding: utf-8 -*-
  
  from Rola import Rola
  from Miner import Miner
  from DAO import DAO
  from SearchQueries import SearchQueries
  import mutagen.mp3
  import threading, sys
  import eyed3
  import re
  import os
  import gi
  gi.require_version('Gtk', '3.0')
  gi.require_version('Gst', '1.0')
  from gi.repository import Gtk, GdkPixbuf, GLib, Gst
  
  class Handler:
      """
      Clase manejadora del controlador entre el modelo y la vista del proyecto.
      """
      def __init__(self):
          self.music_player = ''
          self.dao = DAO.make_DAO()
          self.obj_search = SearchQueries.make_search()
          self.builder = Gtk.Builder()
          self.builder.add_from_file("glade/UI.glade")
          self.window = self.builder.get_object("MainWindow")
          self.window.connect("destroy", self.on_Destroy)
          self.search_entry = self.builder.get_object("entry_search")
          self.miner_button = self.builder.get_object("btn_Miner")
          self.miner_button.connect("clicked", self.on_Mine_Button)
          self.person_button = self.builder.get_object("btn_Person")
          self.person_button.connect("clicked", self.on_Person_Button)
          self.group_button = self.builder.get_object("btn_Group")
          self.group_button.connect("clicked", self.on_Group_Button)
          self.changes_rola_button = self.builder.get_object("btn_Changes_rola")
          self.changes_rola_button.connect("clicked", self.on_Changes_rola_Button)
          self.changes_album_button = self.builder.get_object("btn_Changes_album")
          self.changes_album_button.connect("clicked", self.on_Changes_album_Button)
          self.pause_button = self.builder.get_object("btn_Play")
          self.pause_button.connect("clicked", self.play)
          self.pause_button = self.builder.get_object("btn_Pause")
          self.pause_button.connect("clicked", self.pause)
          self.search_btn = self.builder.get_object("btn_search")
          self.search_btn.connect("clicked", self.search)
          self.clean_search_btn = self.builder.get_object("btn_cancel_search")
          self.clean_search_btn.connect("clicked", self.clean_search)
          self.tree_view = self.builder.get_object("song_treeView")
          self.list_store = self.builder.get_object("songs_liststore")
  
      def fill_tree_view(self):
          """
          Método que crea las columnas del tree view y cacha el evento cuando se
          selecciona una canción
          """
          for i, column_title in enumerate(["Title","Artist","Album","Genre"]):
              renderer = Gtk.CellRendererText()
              column = Gtk.TreeViewColumn(column_title,renderer, text = i)
              self.tree_view.append_column(column)
          for i, column_title in enumerate(["Foto","Year","Play","Track"]):
              renderer = Gtk.CellRendererText()
              column = Gtk.TreeViewColumn(column_title,renderer, text = i)
              column.set_visible(False)
              self.tree_view.append_column(column)
          #Obtiene la selección de una de las rolas
          selected_row = self.tree_view.get_selection()
          selected_row.connect("changed", self.item_selected)
  
      def fill_data(self):
          """
          Método que llena la list_store del tree_view con la información de la
          base de datos
          """
          self.list_store.clear()
          list_tuples = self.dao.select_for_treeView()
          for tuple in list_tuples:
              self.list_store.append(list(tuple[0]))
  
      def item_selected(self, selection):
          """
          Método que llena la list_store del tree_view con la información de la
          base de datos
          :param selection: El evento selection que llama a la función
          """
          self.lbl_song = self.builder.get_object("lbl_Song")
          self.lbl_album = self.builder.get_object("lbl_Album")
          self.lbl_artist = self.builder.get_object("lbl_Artist")
          self.lbl_genre = self.builder.get_object("lbl_Genre")
          self.lbl_year = self.builder.get_object("lbl_Year")
          self.image = self.builder.get_object("song_image")
          #Obtenemos los atributos de las columnas del seleccionado
          model, row = selection.get_selected()
          if row is not None:
              self.song_name = model[row][0]
              self.lbl_song.set_text(self.song_name)
              self.lbl_artist.set_text(model[row][1])
              self.lbl_album.set_text(model[row][2])
              self.lbl_genre.set_text(model[row][3])
              self.lbl_year.set_text(str(model[row][5]))
              #Regresamos los paths a su versión original en caso que tengan '
              path_album = model[row][4]
              self.path = re.sub("´´", "\'", str(path_album))
              path_first = model[row][6]
              self.path_play = re.sub("´´", "\'", str(path_first))
              self.track = model[row][7]
              self.id_rola = model[row][8]
              self.id_album = model[row][9]
              #Revisamos el tag 'APIC' de la rola para obtener su imagen
              audio= eyed3.load(self.path_play)
              try:
                  image = audio.tag.images
                  if(len(image) == 0):
                      self.valid_images = [".jpg",".gif",".png",".tga"]
                      album_art = False
                      for f in os.listdir(self.path):
                          ext = os.path.splitext(f)[1]
                          if(ext.lower() in self.valid_images):
                              self.rola_image_tag = (os.path.join(self.path,f))
                              if(self.rola_image_tag.endswith(tuple(self.valid_images))):
                                  self.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                                  self.rola_image_tag,200,200)
                                  self.image.set_from_pixbuf(self.pixbuf)
                                  album_art = True
                      if (not album_art):
                          self.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                          'glade/resources/unknow_song.jpg',200,200)
                          self.image.set_from_pixbuf(self.pixbuf)
                  if(not(len(image) == 0)):
                          tags = mutagen.mp3.Open(self.path_play)
                          for i in tags:
                              if i.startswith("APIC"):
                                  data = tags[i].data
                                  break
                          name_artwork = 'artwork/image%s.jpeg'%(self.song_name)
                          if(not(os.path.exists(name_artwork))):
                              with open(name_artwork, 'wb') as img:
                                  img.write(data)
                          self.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                              name_artwork,200,200)
                          self.image.set_from_pixbuf(self.pixbuf)
              except Exception:
                  self.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                  'glade/resources/unknow_song.jpg',200,200)
                  self.image.set_from_pixbuf(self.pixbuf)
  
      def play(self, button):
          """
          Método que reproduce la música de la rola seleccionada
          :param button: El botón que llama a la función
          """
          Gst.init()
          self.music_player = Gst.ElementFactory.make("playbin", "player")
          self.music_player.set_property('uri','file://'+ self.path_play)
          self.music_player.set_property('volume', 0.2)
          self.music_player.set_state(Gst.State.PLAYING)
  
      def pause(self, button):
          """
          Método que pausa la música de la rola seleccionada
          :param button: El botón que llama a la función
          """
          self.music_player.set_state(Gst.State.PAUSED)
  
      def on_Changes_rola_Button(self, button):
          """
          Método que cambia los valores de la rola seleccionada
          :param button: El botón que llama a la función
          """
          self.builder_changes_rola = Gtk.Builder()
          self.builder_changes_rola.add_from_file("glade/Changes_rola.glade")
          self.window_changes_rola = self.builder_changes_rola.get_object("win_changes_rola")
          self.window_changes_rola.show_all()
          self.path_rola_entry = self.builder_changes_rola.get_object("path_rola_entry")
          self.path_rola_entry.set_text(self.path_play)
          self.title_entry = self.builder_changes_rola.get_object("title_entry")
          self.title_entry.set_text(self.lbl_song.get_text())
          self.track_entry = self.builder_changes_rola.get_object("track_entry")
          self.track_entry.set_text(str(self.track))
          self.year_entry = self.builder_changes_rola.get_object("year_entry")
          self.year_entry.set_text(self.lbl_year.get_text())
          self.genre_entry = self.builder_changes_rola.get_object("genre_entry")
          self.genre_entry.set_text(self.lbl_genre.get_text())
          self.builder_changes_rola.get_object("btn_save_rola_changes").connect("clicked",
          self.save_changes_rola)
          self.builder_changes_rola.get_object("btn_no_save_rola_changes").connect("clicked",
          self.dont_save_changes_rola)
  
      def save_changes_rola(self, button):
          """
          Método que guarda los valores cambiados de la rola seleccionada en la base de datos
          :param button: El botón que llama a la función
          """
          new_path = re.sub("\'", "´", str(self.path_rola_entry.get_text()))
          new_title = re.sub("\'", "´", str(self.title_entry.get_text()))
          new_track = int(self.track_entry.get_text())
          new_year = int(self.year_entry.get_text())
          new_genre = str(self.genre_entry.get_text())
          self.window_changes_rola.destroy()
          self.dao.update_rola(int(self.id_rola),new_path , new_title,
          new_track, new_year, new_genre)
          self.fill_data()
  
      def dont_save_changes_rola(self, button):
          """
          Método que no guarda los valores cambiados de la rola seleccionada en la base de datos
          :param button: El botón que llama a la función
          """
          self.window_changes_rola.destroy()
  
      def on_Changes_album_Button(self, button):
          """
          Método que guarda los valores cambiados del album la rola seleccionada
          :param button: El botón que llama a la función
          """
          self.builder_changes_album = Gtk.Builder()
          self.builder_changes_album.add_from_file("glade/Changes_album.glade")
          self.window_changes_album = self.builder_changes_album.get_object("win_changes_album")
          self.window_changes_album.show_all()
          self.path_album_entry = self.builder_changes_album.get_object("entry_album_path")
          self.path_album_entry.set_text(self.path)
          self.name_entry = self.builder_changes_album.get_object("entry_album_name")
          self.name_entry.set_text(self.lbl_album.get_text())
          self.year_album_entry = self.builder_changes_album.get_object("entry_album_year")
          self.year_album_entry.set_text(self.lbl_year.get_text())
          self.builder_changes_album.get_object("btn_save_album_changes").connect("clicked",
          self.save_changes_album)
          self.builder_changes_album.get_object("btn_no_save_album_changes").connect("clicked",
          self.dont_save_changes_album)
  
      def save_changes_album(self, button):
          """
          Método que guarda los valores cambiados del album la rola seleccionada en la base de datos
          :param button: El botón que llama a la función
          """
          new_path_album = re.sub("\'", "´", str(self.path_album_entry.get_text()))
          new_name = re.sub("\'", "´", str(self.name_entry.get_text()))
          new_year_album = int(self.year_album_entry.get_text())
          self.window_changes_album.destroy()
          self.dao.update_album(int(self.id_album), new_path_album, new_name,
          new_year_album)
          self.fill_data()
  
      def dont_save_changes_album(self, button):
          """
          Método que no guarda los valores cambiados del album de la rola seleccionada en la base de datos
          :param button: El botón que llama a la función
          """
          self.window_changes_album.destroy()
  
      def on_Person_Button(self, button):
          """
          Método que muestra la información a da de alta una persona
          :param button: El botón que llama a la función
          """
          self.builder_person = Gtk.Builder()
          self.builder_person.add_from_file("glade/Person.glade")
          self.window_person = self.builder_person.get_object("win_person")
          self.builder_person.get_object("lbl_pperformer").set_text(self.lbl_artist.get_text())
          self.window_person.show_all()
          self.real_name = self.builder_person.get_object("entry_real_name")
          self.birth_date = self.builder_person.get_object("entry_birth_date")
          self.death_date = self.builder_person.get_object("entry_death_date")
          self.builder_person.get_object("btn_save_person").connect("clicked",
          self.save_persons)
          self.builder_person.get_object("btn_no_save_person").connect("clicked",
          self.dont_save_persons)
  
      def save_persons(self, button):
          """
          Método que guarda y da de alta una persona
          :param button: El botón que llama a la función
          """
          self.dao.insert_person(self.lbl_artist.get_text(), self.real_name.get_text(),
          self.birth_date.get_text(), self.death_date.get_text())
          self.window_person.destroy()
  
      def dont_save_persons(self, button):
          """
          Método que no guarda a una persona
          :param button: El botón que llama a la función
          """
          self.window_person.destroy()
  
      def on_Group_Button(self,button):
          """
          Método que muestra la información a da de alta un grupo
          :param button: El botón que llama a la función
          """
          self.builder_group = Gtk.Builder()
          self.builder_group.add_from_file("glade/Group.glade")
          self.window_group = self.builder_group.get_object("win_group")
          self.builder_group.get_object("lbl_group_name").set_text(self.lbl_artist.get_text())
          self.window_group.show_all()
          self.start_date = self.builder_group.get_object("entry_start_date")
          self.end_date = self.builder_group.get_object("entry_end_date")
          self.builder_group.get_object("btn_save_group").connect("clicked",
          self.save_group)
          self.builder_group.get_object("btn_no_save_group").connect("clicked",
          self.dont_save_group)
          self.builder_group.get_object("btn_add_person_in_group").connect("clicked",
          self.add_persons)
          group_info = self.dao.get_group_info(self.lbl_artist.get_text())
          if (len(group_info) > 0):
              for info in group_info:
                  self.group_id = info[0]
                  self.start_date.set_text(info[1])
                  self.end_date.set_text(info[2])
  
      def save_group(self, button):
          """
          Método que guarda y da de alta un grupo
          :param button: El botón que llama a la función
          """
          self.dao.insert_group(self.lbl_artist.get_text(), self.start_date.get_text(),
          self.end_date.get_text())
          self.window_group.destroy()
  
      def dont_save_group(self, button):
          """
          Método que no guarda al grupo
          :param button: El botón que llama a la función
          """
          self.window_group.destroy()
  
      def add_persons(self, button):
          """
          Método que muestra las posible personas a agregar en la banda
          :param button: El botón que llama a la función
          """
          self.builder_add_persons = Gtk.Builder()
          self.builder_add_persons.add_from_file("glade/Persons_to_add.glade")
          self.window_add_persons = self.builder_add_persons.get_object("win_persons_to_add")
          self.window_add_persons.show_all()
          self.person_tree_view = self.builder_add_persons.get_object("persons_tree_view")
          self.list_store_persons = self.builder_add_persons.get_object("list_persons")
          self.builder_add_persons.get_object("btn_add_person").connect("clicked",
          self.add_person_button)
          renderer = Gtk.CellRendererText()
          column_id = Gtk.TreeViewColumn('Id_Person',renderer, text = 0)
          column_id.set_visible(False)
          self.person_tree_view.append_column(column_id)
          column_name = Gtk.TreeViewColumn('Person',renderer, text = 1)
          self.person_tree_view.append_column(column_name)
          list_tuples = self.dao.select_persons()
          for tuple in list_tuples:
              self.list_store_persons.append(list(tuple[0]))
          selected_row = self.person_tree_view.get_selection()
          selected_row.connect("changed", self.person_selected)
  
      def person_selected(self, selection):
          """
          Método que obtiene el id de la persona seleccionada
          :param selection: El evento donde se selecciona la persona
          """
          model, row = selection.get_selected()
          if row is not None:
              self.id_person_dao = model[row][0]
  
      def add_person_button(self, button):
          """
          Método que agrega a una persona en un grupo
          :param button: El botón que llama a la función
          """
          self.dao.add_persons(self.id_person_dao, self.group_id)
          self.window_add_persons.destroy()
  
      def on_Mine_Button(self, button):
          """
          Método que obtiene el directorio donde se minará
          :param button: El botón que llama a la función
          """
          dialog = Gtk.FileChooserDialog("Select a directory to mine data", None,
          Gtk.FileChooserAction.SELECT_FOLDER,("Cancel", Gtk.ResponseType.CANCEL,
          "Open", Gtk.ResponseType.OK))
          response = dialog.run()
          if(response == Gtk.ResponseType.OK):
              directory = dialog.get_filename()
              self.directory_path = directory
              dialog.destroy()
          elif(response == Gtk.ResponseType.CANCEL):
              dialog.destroy()
              self.directory_path = os.path.expanduser('~/Music')
          self.progress_mining()
  
      def progress_mining(self):
          """
          Método que actualiza el progreso del minero en la vista
          """
          self.builder_progress = Gtk.Builder()
          self.builder_progress.add_from_file("glade/Mining.glade")
          self.window_progress = self.builder_progress.get_object("mining_window")
          self.bar = self.builder_progress.get_object("level_bar")
          self.lbl_files = self.builder_progress.get_object("files_yet")
          self.window_progress.show_all()
  
          def thread_run():
              """
              Método que añade el método mine al GLib
              """
              mine = self.mine()
              GLib.idle_add(cleanup, mine)
  
          def cleanup(mine):
              """
              Método que cierra la ventana de progreso
              :param mine: Un escucha de cuando el método mine termina
              """
              self.window_progress.hide()
              thread.join()
              self.fill_data()
          #Iniciamos el hilo
          thread = threading.Thread(target = thread_run)
          thread.start()
  
      def mine(self):
          """
          Método que actualiza el progreso del minero y utiliza sus metodos
          para insertar en la base de datos
          """
          def set_lbl(text):
              """
              Método que actualiza la etiqueta
              :param button: El texto del progreso
              """
              self.lbl_files.set_text(text)
  
          def set_progress(progress):
              """
              Método que regresa el valor del progreso actual con 2 decimales
              :param progress: El valor del progreso
              """
              frac_progress = round(progress, 2)
              self.bar.set_value(frac_progress)
          #Iniciamos el minero
          self.miner = Miner()
          self.files = self.miner.get_files(self.directory_path)
          for path in self.files:
              self.obj_rola = self.miner.set_mp3_tags(path)
              self.dao.use_rola(self.obj_rola)
              GLib.idle_add(set_progress,
              (self.miner.get_file_mined() / len(self.files)))
              GLib.idle_add(set_lbl, str(self.miner.get_file_mined()) + "/" +
              str(len(self.files)))
  
      def search(self, button):
          """
          Método que realiza una consulta a la base de datos
          :param button: El botón que llama a la función
          """
          self.searching = self.search_entry.get_text()
          self.query = self.obj_search.compile(self.searching)
          self.list_search = self.dao.get_search(self.query)
          if(len(self.list_search) == 0):
              self.search_entry.set_text("||NOT A VALID SEARCH||")
          else:
              self.list_store.clear()
              for tuple in self.list_search:
                  self.list_store.append(list(tuple[0]))
  
      def clean_search(self, button):
          """
          Método que limpia la consulta
          :param button: El botón que llama a la función
          """
          self.fill_data()
          self.search_entry.set_text("")
  
      def start_window(self):
          """
          Método que inicia la ventana principal
          """
          self.window.show_all()
  
      def on_Destroy(self, *args):
          """
          Método que cierra el programa
          """
          Gtk.main_quit()
  
  if __name__ == '__main__':
      handler = Handler()
      handler.start_window()
      handler.fill_tree_view()
      if ((os.path.exists('rolas.db'))):
          handler.fill_data()
      Gtk.main()
  