mirror of
				https://github.com/bspeice/Melodia
				synced 2025-11-03 18:00:50 -05:00 
			
		
		
		
	Restructure relationship b/w Songs and Archives
Songs now have a ForeignKey to Archive - one song can not be part of many Archives.
This commit is contained in:
		@ -1,7 +1,5 @@
 | 
				
			|||||||
from django.db import models
 | 
					from django.db import models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from song import Song
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
This is the archive model for the archiving backend of Melodia.
 | 
					This is the archive model for the archiving backend of Melodia.
 | 
				
			||||||
It's purpose is to control the high-level functionality of managing
 | 
					It's purpose is to control the high-level functionality of managing
 | 
				
			||||||
@ -31,8 +29,8 @@ class Archive (models.Model):
 | 
				
			|||||||
	#Note that we're not using FilePathField since this is actually a folder
 | 
						#Note that we're not using FilePathField since this is actually a folder
 | 
				
			||||||
	root_folder = models.CharField(max_length = 255)
 | 
						root_folder = models.CharField(max_length = 255)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	#And a reference to the songs in this archive
 | 
						#We've removed the reference to "songs" - instead define it as a ForeignKey,
 | 
				
			||||||
	songs       = models.ManyToManyField(Song)
 | 
						#and do lookups via song_set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	#Backup settings
 | 
						#Backup settings
 | 
				
			||||||
	backup_location  = models.CharField(max_length = 255, default = "/dev/null")
 | 
						backup_location  = models.CharField(max_length = 255, default = "/dev/null")
 | 
				
			||||||
@ -53,7 +51,7 @@ class Archive (models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		#It's hackish, but far fewer transactions to delete everything first, and add it all back.
 | 
							#It's hackish, but far fewer transactions to delete everything first, and add it all back.
 | 
				
			||||||
		#If we get interrupted, just re-run it.
 | 
							#If we get interrupted, just re-run it.
 | 
				
			||||||
		self.songs.all().delete()
 | 
							song_set.all().delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		#Add new songs
 | 
							#Add new songs
 | 
				
			||||||
		for dirname, dirnames, filenames in os.walk(self.root_folder):
 | 
							for dirname, dirnames, filenames in os.walk(self.root_folder):
 | 
				
			||||||
@ -63,16 +61,16 @@ class Archive (models.Model):
 | 
				
			|||||||
				full_url = os.path.abspath(rel_url)
 | 
									full_url = os.path.abspath(rel_url)
 | 
				
			||||||
				new_song = Song(url = full_url)
 | 
									new_song = Song(url = full_url)
 | 
				
			||||||
				new_song.save()
 | 
									new_song.save()
 | 
				
			||||||
				self.songs.add(new_song)
 | 
									song_set.add(new_song)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def _update_song_metadata(self, use_echonest = False, progress_callback = lambda x, y: None):
 | 
						def _update_song_metadata(self, use_echonest = False, progress_callback = lambda x, y: None):
 | 
				
			||||||
		"""Scan every song in this archive (database only) and make sure all songs are correct
 | 
							"""Scan every song in this archive (database only) and make sure all songs are correct
 | 
				
			||||||
		The progress_callback function is called with the current song being operated on first, and the total songs second."""
 | 
							The progress_callback function is called with the current song being operated on first, and the total songs second."""
 | 
				
			||||||
		#This method operates only on the songs that are in the database - if you need to make
 | 
							#This method operates only on the songs that are in the database - if you need to make
 | 
				
			||||||
		#sure that new songs are added, use the _scan_filesystem() method in addition
 | 
							#sure that new songs are added, use the _scan_filesystem() method in addition
 | 
				
			||||||
		total_songs  = self.songs.count()
 | 
							total_songs  = song_set.count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for index, song in enumerate(self.songs.all()):
 | 
							for index, song in enumerate(song_set.all()):
 | 
				
			||||||
			song.populate_metadata(use_echonest = use_echonest)
 | 
								song.populate_metadata(use_echonest = use_echonest)
 | 
				
			||||||
			song.save()
 | 
								song.save()
 | 
				
			||||||
			progress_callback(index + 1, total_songs)
 | 
								progress_callback(index + 1, total_songs)
 | 
				
			||||||
@ -133,9 +131,9 @@ class Archive (models.Model):
 | 
				
			|||||||
		"""
 | 
							"""
 | 
				
			||||||
		import os, shutil, errno
 | 
							import os, shutil, errno
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		total_songs = self.songs.count()
 | 
							total_songs = song_set.count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for index, song in enumerate(self.songs.all()):
 | 
							for index, song in enumerate(song_set.all()):
 | 
				
			||||||
			_current_filename              = os.path.basename(song.url)
 | 
								_current_filename              = os.path.basename(song.url)
 | 
				
			||||||
			_current_filename_no_extension = os.path.splitext(_current_filename)[0]
 | 
								_current_filename_no_extension = os.path.splitext(_current_filename)[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,10 @@
 | 
				
			|||||||
from django.db import models
 | 
					from django.db import models
 | 
				
			||||||
from Melodia import melodia_settings
 | 
					from Melodia import melodia_settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from archive import Archive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
 | 
					import os.path
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
The Song model
 | 
					The Song model
 | 
				
			||||||
Each instance of a Song represents a single music file.
 | 
					Each instance of a Song represents a single music file.
 | 
				
			||||||
@ -63,6 +66,9 @@ class Song (models.Model):
 | 
				
			|||||||
	skip_count = models.IntegerField(default = _default_int)
 | 
						skip_count = models.IntegerField(default = _default_int)
 | 
				
			||||||
	rating     = models.IntegerField(default = _default_int, choices = _default_rating_choices)
 | 
						rating     = models.IntegerField(default = _default_int, choices = _default_rating_choices)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#Link back to the archive this comes from
 | 
				
			||||||
 | 
						parent_archive = models.ForeignKey(Archive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	#Set a static reference to the rating options
 | 
						#Set a static reference to the rating options
 | 
				
			||||||
	RATING_DEFAULT   = _default_rating
 | 
						RATING_DEFAULT   = _default_rating
 | 
				
			||||||
	RATING_BAD       = _default_rating_bad
 | 
						RATING_BAD       = _default_rating_bad
 | 
				
			||||||
@ -71,6 +77,10 @@ class Song (models.Model):
 | 
				
			|||||||
	RATING_GOOD      = _default_rating_good
 | 
						RATING_GOOD      = _default_rating_good
 | 
				
			||||||
	RATING_EXCELLENT = _default_rating_excellent
 | 
						RATING_EXCELLENT = _default_rating_excellent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def _get_full_url(self):
 | 
				
			||||||
 | 
							"Combine this song's URL with the URL of its parent"
 | 
				
			||||||
 | 
							return os.path.join(parent_archive.root_folder, self.url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def _file_not_changed(self):
 | 
						def _file_not_changed(self):
 | 
				
			||||||
		"Make sure the hash for this file is valid - return True if it has not changed."
 | 
							"Make sure the hash for this file is valid - return True if it has not changed."
 | 
				
			||||||
		#Overload the hash function with whatever Melodia as a whole is using
 | 
							#Overload the hash function with whatever Melodia as a whole is using
 | 
				
			||||||
@ -79,7 +89,7 @@ class Song (models.Model):
 | 
				
			|||||||
		#Check if there's a hash entry - if there is, the song may not have changed,
 | 
							#Check if there's a hash entry - if there is, the song may not have changed,
 | 
				
			||||||
		#and we can go ahead and return
 | 
							#and we can go ahead and return
 | 
				
			||||||
		if self.file_hash != None:
 | 
							if self.file_hash != None:
 | 
				
			||||||
			song_file = open(self.url, 'rb')
 | 
								song_file = open(self._get_full_url, 'rb')
 | 
				
			||||||
			current_file_hash = hash(song_file.read())
 | 
								current_file_hash = hash(song_file.read())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if current_file_hash == self.file_hash:
 | 
								if current_file_hash == self.file_hash:
 | 
				
			||||||
@ -94,10 +104,10 @@ class Song (models.Model):
 | 
				
			|||||||
		#Overload the hash function with whatever Melodia as a whole is using
 | 
							#Overload the hash function with whatever Melodia as a whole is using
 | 
				
			||||||
		from Melodia.melodia_settings import HASH_FUNCTION as hash
 | 
							from Melodia.melodia_settings import HASH_FUNCTION as hash
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		file_handle = open(self.url, 'rb')
 | 
							file_handle = open(self._get_full_url, 'rb')
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		self.file_hash = hash(file_handle.read())
 | 
							self.file_hash = hash(file_handle.read())
 | 
				
			||||||
		self.file_size = os.stat(self.url).st_size
 | 
							self.file_size = os.stat(self._get_full_url).st_size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def _grab_metadata_echonest(self):
 | 
						def _grab_metadata_echonest(self):
 | 
				
			||||||
		"Populate this song's metadata using EchoNest"
 | 
							"Populate this song's metadata using EchoNest"
 | 
				
			||||||
@ -110,8 +120,8 @@ class Song (models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		try:
 | 
							try:
 | 
				
			||||||
			#Use mutagen to scan local metadata - don't update anything else (i.e. play_count)
 | 
								#Use mutagen to scan local metadata - don't update anything else (i.e. play_count)
 | 
				
			||||||
			track             = mutagen.File(self.url)
 | 
								track             = mutagen.File(self._get_full_url)
 | 
				
			||||||
			track_easy        = mutagen.File(self.url, easy=True)
 | 
								track_easy        = mutagen.File(self._get_full_url, easy=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			self.title        = track_easy['title'][0]  or _default_string
 | 
								self.title        = track_easy['title'][0]  or _default_string
 | 
				
			||||||
			self.artist       = track_easy['artist'][0] or _default_string
 | 
								self.artist       = track_easy['artist'][0] or _default_string
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user