mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-05-08 17:59:50 +00:00
cocoa: remove iTunes support
It was an unmaintained feature that wasn't working well with recent OS X releases.
This commit is contained in:
parent
83d934fd4f
commit
3093a42553
@ -32,8 +32,6 @@ fileMenu.addItem("Export Results to XHTML", Action(owner.model, 'exportToXHTML')
|
|||||||
fileMenu.addItem("Export Results to CSV", Action(owner.model, 'exportToCSV'))
|
fileMenu.addItem("Export Results to CSV", Action(owner.model, 'exportToCSV'))
|
||||||
if edition == 'pe':
|
if edition == 'pe':
|
||||||
fileMenu.addItem("Clear Picture Cache", Action(owner, 'clearPictureCache'), 'cmd+shift+p')
|
fileMenu.addItem("Clear Picture Cache", Action(owner, 'clearPictureCache'), 'cmd+shift+p')
|
||||||
elif edition == 'me':
|
|
||||||
fileMenu.addItem("Remove Dead Tracks in iTunes", Action(owner, 'removeDeadTracks'))
|
|
||||||
|
|
||||||
editMenu.addItem("Mark All", Action(None, 'markAll'), 'cmd+a')
|
editMenu.addItem("Mark All", Action(None, 'markAll'), 'cmd+a')
|
||||||
editMenu.addItem("Mark None", Action(None, 'markNone'), 'cmd+shift+a')
|
editMenu.addItem("Mark None", Action(None, 'markNone'), 'cmd+shift+a')
|
||||||
|
@ -1,261 +1,21 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
# Created On: 2006/11/16
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
# which should be included with this package. The terms are also available at
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
import logging
|
|
||||||
import plistlib
|
|
||||||
import time
|
|
||||||
import os.path as op
|
|
||||||
from appscript import app, its, k, CommandError, ApplicationNotFoundError
|
|
||||||
from . import tunes
|
|
||||||
|
|
||||||
from cocoa import as_fetch, proxy
|
|
||||||
from hscommon.trans import trget
|
from hscommon.trans import trget
|
||||||
from hscommon.path import Path
|
|
||||||
from hscommon.util import remove_invalid_xml
|
|
||||||
|
|
||||||
from core import directories
|
|
||||||
from core.app import JobType, JOBID2TITLE
|
|
||||||
from core.scanner import ScanType
|
from core.scanner import ScanType
|
||||||
from core_me.app import DupeGuru as DupeGuruBase
|
from core_me.app import DupeGuru as DupeGuruME
|
||||||
from core_me import fs
|
|
||||||
from .app import PyDupeGuruBase
|
from .app import PyDupeGuruBase
|
||||||
|
|
||||||
tr = trget('ui')
|
tr = trget('ui')
|
||||||
|
|
||||||
JobType.RemoveDeadTracks = 'jobRemoveDeadTracks'
|
|
||||||
JobType.ScanDeadTracks = 'jobScanDeadTracks'
|
|
||||||
|
|
||||||
JOBID2TITLE.update({
|
|
||||||
JobType.RemoveDeadTracks: tr("Removing dead tracks from your iTunes Library"),
|
|
||||||
JobType.ScanDeadTracks: tr("Scanning the iTunes Library"),
|
|
||||||
})
|
|
||||||
|
|
||||||
ITUNES = 'iTunes'
|
|
||||||
ITUNES_PATH = Path('iTunes Library')
|
|
||||||
|
|
||||||
def get_itunes_library(a):
|
|
||||||
try:
|
|
||||||
[source] = [s for s in a.sources(timeout=0) if s.kind(timeout=0) == k.library]
|
|
||||||
[library] = source.library_playlists(timeout=0)
|
|
||||||
return library
|
|
||||||
except ValueError:
|
|
||||||
logging.warning('Some unexpected iTunes configuration encountered')
|
|
||||||
return None
|
|
||||||
|
|
||||||
class ITunesSong(fs.MusicFile):
|
|
||||||
def __init__(self, song_data):
|
|
||||||
path = Path(proxy.url2path_(song_data['Location']))
|
|
||||||
fs.MusicFile.__init__(self, path)
|
|
||||||
self.id = song_data['Track ID']
|
|
||||||
|
|
||||||
def remove_from_library(self):
|
|
||||||
try:
|
|
||||||
a = app(ITUNES, terms=tunes)
|
|
||||||
library = get_itunes_library(a)
|
|
||||||
if library is None:
|
|
||||||
return
|
|
||||||
[song] = library.file_tracks[its.database_ID == self.id]()
|
|
||||||
a.delete(song, timeout=0)
|
|
||||||
except ValueError:
|
|
||||||
msg = "Could not find song '{}' (trackid: {}) in iTunes Library".format(str(self.path), self.id)
|
|
||||||
raise EnvironmentError(msg)
|
|
||||||
except (CommandError, RuntimeError) as e:
|
|
||||||
raise EnvironmentError(str(e))
|
|
||||||
|
|
||||||
display_folder_path = ITUNES_PATH
|
|
||||||
|
|
||||||
def get_itunes_database_path():
|
|
||||||
plisturls = proxy.prefValue_inDomain_('iTunesRecentDatabases', 'com.apple.iApps')
|
|
||||||
if not plisturls:
|
|
||||||
raise directories.InvalidPathError()
|
|
||||||
plistpath = proxy.url2path_(plisturls[0])
|
|
||||||
return Path(plistpath)
|
|
||||||
|
|
||||||
def get_itunes_songs(plistpath):
|
|
||||||
if not plistpath.exists():
|
|
||||||
return []
|
|
||||||
s = plistpath.open('rt', encoding='utf-8').read()
|
|
||||||
# iTunes sometimes produces XML files with invalid characters in it.
|
|
||||||
s = remove_invalid_xml(s, replace_with='')
|
|
||||||
plist = plistlib.readPlistFromBytes(s.encode('utf-8'))
|
|
||||||
result = []
|
|
||||||
for song_data in plist['Tracks'].values():
|
|
||||||
try:
|
|
||||||
if song_data['Track Type'] != 'File':
|
|
||||||
continue
|
|
||||||
song = ITunesSong(song_data)
|
|
||||||
except KeyError: # No "Track Type", "Location" or "Track ID" key in track
|
|
||||||
continue
|
|
||||||
if song.path.exists():
|
|
||||||
result.append(song)
|
|
||||||
return result
|
|
||||||
|
|
||||||
class Directories(directories.Directories):
|
|
||||||
def __init__(self):
|
|
||||||
directories.Directories.__init__(self)
|
|
||||||
try:
|
|
||||||
self.itunes_libpath = get_itunes_database_path()
|
|
||||||
except directories.InvalidPathError:
|
|
||||||
self.itunes_libpath = None
|
|
||||||
|
|
||||||
def _get_files(self, from_path, fileclasses, j):
|
|
||||||
if from_path == ITUNES_PATH:
|
|
||||||
if self.itunes_libpath is None:
|
|
||||||
return []
|
|
||||||
is_ref = self.get_state(from_path) == directories.DirectoryState.Reference
|
|
||||||
songs = get_itunes_songs(self.itunes_libpath)
|
|
||||||
for song in songs:
|
|
||||||
song.is_ref = is_ref
|
|
||||||
return songs
|
|
||||||
else:
|
|
||||||
return directories.Directories._get_files(self, from_path, fileclasses, j)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_subfolders(path):
|
|
||||||
if path == ITUNES_PATH:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return directories.Directories.get_subfolders(path)
|
|
||||||
|
|
||||||
def add_path(self, path):
|
|
||||||
if path == ITUNES_PATH:
|
|
||||||
if path not in self:
|
|
||||||
self._dirs.append(path)
|
|
||||||
else:
|
|
||||||
directories.Directories.add_path(self, path)
|
|
||||||
|
|
||||||
def has_itunes_path(self):
|
|
||||||
return any(path == ITUNES_PATH for path in self._dirs)
|
|
||||||
|
|
||||||
def has_any_file(self):
|
|
||||||
# If we don't do that, it causes a hangup in the GUI when we click Start Scanning because
|
|
||||||
# checking if there's any file to scan involves reading the whole library. If we have the
|
|
||||||
# iTunes library, we assume we have at least one file.
|
|
||||||
if self.has_itunes_path():
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return directories.Directories.has_any_file(self)
|
|
||||||
|
|
||||||
|
|
||||||
class DupeGuruME(DupeGuruBase):
|
|
||||||
def __init__(self, view):
|
|
||||||
DupeGuruBase.__init__(self, view)
|
|
||||||
self.directories = Directories()
|
|
||||||
self.dead_tracks = []
|
|
||||||
|
|
||||||
def _do_delete(self, j, *args):
|
|
||||||
def op(dupe):
|
|
||||||
j.add_progress()
|
|
||||||
return self._do_delete_dupe(dupe, *args)
|
|
||||||
|
|
||||||
marked = [dupe for dupe in self.results.dupes if self.results.is_marked(dupe)]
|
|
||||||
j.start_job(self.results.mark_count, tr("Sending dupes to the Trash"))
|
|
||||||
if any(isinstance(dupe, ITunesSong) for dupe in marked):
|
|
||||||
j.add_progress(0, desc=tr("Talking to iTunes. Don't touch it!"))
|
|
||||||
try:
|
|
||||||
a = app(ITUNES, terms=tunes)
|
|
||||||
a.activate(timeout=0)
|
|
||||||
except (CommandError, RuntimeError, ApplicationNotFoundError):
|
|
||||||
pass
|
|
||||||
self.results.perform_on_marked(op, True)
|
|
||||||
|
|
||||||
def _do_delete_dupe(self, dupe, *args):
|
|
||||||
if isinstance(dupe, ITunesSong):
|
|
||||||
dupe.remove_from_library()
|
|
||||||
DupeGuruBase._do_delete_dupe(self, dupe, *args)
|
|
||||||
|
|
||||||
def _create_file(self, path):
|
|
||||||
if (self.directories.itunes_libpath is not None) and (path in self.directories.itunes_libpath.parent()):
|
|
||||||
if not hasattr(self, 'itunes_songs'):
|
|
||||||
songs = get_itunes_songs(self.directories.itunes_libpath)
|
|
||||||
self.itunes_songs = {song.path: song for song in songs}
|
|
||||||
if path in self.itunes_songs:
|
|
||||||
return self.itunes_songs[path]
|
|
||||||
else:
|
|
||||||
pass # We'll return the default file type, as per the last line of this method
|
|
||||||
return DupeGuruBase._create_file(self, path)
|
|
||||||
|
|
||||||
def _job_completed(self, jobid):
|
|
||||||
# XXX Just before release, I'm realizing that this piece of code below is why I was passing
|
|
||||||
# job exception as an argument to _job_completed(). I have to comment it for now. It's not
|
|
||||||
# the end of the world, but I should find an elegant solution to this at some point.
|
|
||||||
# if (jobid in {JobType.RemoveDeadTracks, JobType.ScanDeadTracks}) and (exc is not None):
|
|
||||||
# msg = tr("There were communication problems with iTunes. The operation couldn't be completed.")
|
|
||||||
# self.view.show_message(msg)
|
|
||||||
# return True
|
|
||||||
if jobid == JobType.ScanDeadTracks:
|
|
||||||
dead_tracks_count = len(self.dead_tracks)
|
|
||||||
if dead_tracks_count > 0:
|
|
||||||
msg = tr("Your iTunes Library contains %d dead tracks ready to be removed. Continue?")
|
|
||||||
if self.view.ask_yes_no(msg % dead_tracks_count):
|
|
||||||
self.remove_dead_tracks()
|
|
||||||
else:
|
|
||||||
msg = tr("You have no dead tracks in your iTunes Library")
|
|
||||||
self.view.show_message(msg)
|
|
||||||
if jobid == JobType.Load:
|
|
||||||
if hasattr(self, 'itunes_songs'):
|
|
||||||
# If we load another file, we want a refresh song list
|
|
||||||
del self.itunes_songs
|
|
||||||
DupeGuruBase._job_completed(self, jobid)
|
|
||||||
|
|
||||||
def copy_or_move(self, dupe, copy, destination, dest_type):
|
|
||||||
if isinstance(dupe, ITunesSong):
|
|
||||||
copy = True
|
|
||||||
return DupeGuruBase.copy_or_move(self, dupe, copy, destination, dest_type)
|
|
||||||
|
|
||||||
def start_scanning(self):
|
|
||||||
if self.directories.has_itunes_path():
|
|
||||||
try:
|
|
||||||
app(ITUNES, terms=tunes)
|
|
||||||
except ApplicationNotFoundError:
|
|
||||||
self.view.show_message(tr("The iTunes application couldn't be found."))
|
|
||||||
return
|
|
||||||
DupeGuruBase.start_scanning(self)
|
|
||||||
|
|
||||||
def remove_dead_tracks(self):
|
|
||||||
def do(j):
|
|
||||||
a = app(ITUNES, terms=tunes)
|
|
||||||
a.activate(timeout=0)
|
|
||||||
for index, track in enumerate(j.iter_with_progress(self.dead_tracks)):
|
|
||||||
if index % 100 == 0:
|
|
||||||
time.sleep(.1)
|
|
||||||
try:
|
|
||||||
track.delete(timeout=0)
|
|
||||||
except CommandError as e:
|
|
||||||
logging.warning('Error while trying to remove a track from iTunes: %s' % str(e))
|
|
||||||
|
|
||||||
self._start_job(JobType.RemoveDeadTracks, do)
|
|
||||||
|
|
||||||
def scan_dead_tracks(self):
|
|
||||||
def do(j):
|
|
||||||
a = app(ITUNES, terms=tunes)
|
|
||||||
a.activate(timeout=0)
|
|
||||||
library = get_itunes_library(a)
|
|
||||||
if library is None:
|
|
||||||
return
|
|
||||||
self.dead_tracks = []
|
|
||||||
tracks = as_fetch(library.file_tracks, k.file_track)
|
|
||||||
for index, track in enumerate(j.iter_with_progress(tracks)):
|
|
||||||
if index % 100 == 0:
|
|
||||||
time.sleep(.1)
|
|
||||||
if track.location(timeout=0) == k.missing_value:
|
|
||||||
self.dead_tracks.append(track)
|
|
||||||
logging.info('Found %d dead tracks' % len(self.dead_tracks))
|
|
||||||
|
|
||||||
self._start_job(JobType.ScanDeadTracks, do)
|
|
||||||
|
|
||||||
class PyDupeGuru(PyDupeGuruBase):
|
class PyDupeGuru(PyDupeGuruBase):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._init(DupeGuruME)
|
self._init(DupeGuruME)
|
||||||
|
|
||||||
def scanDeadTracks(self):
|
|
||||||
self.model.scan_dead_tracks()
|
|
||||||
|
|
||||||
#---Properties
|
#---Properties
|
||||||
def setMinMatchPercentage_(self, percentage: int):
|
def setMinMatchPercentage_(self, percentage: int):
|
||||||
self.model.options['min_match_percentage'] = percentage
|
self.model.options['min_match_percentage'] = percentage
|
||||||
|
@ -1,282 +0,0 @@
|
|||||||
# Taken from https://github.com/abarnert/itunesterms
|
|
||||||
|
|
||||||
version = 1.1
|
|
||||||
path = '/Applications/iTunes.app'
|
|
||||||
|
|
||||||
classes = \
|
|
||||||
[('print_settings', b'pset'),
|
|
||||||
('application', b'capp'),
|
|
||||||
('artwork', b'cArt'),
|
|
||||||
('audio_CD_playlist', b'cCDP'),
|
|
||||||
('audio_CD_track', b'cCDT'),
|
|
||||||
('browser_window', b'cBrW'),
|
|
||||||
('device_playlist', b'cDvP'),
|
|
||||||
('device_track', b'cDvT'),
|
|
||||||
('encoder', b'cEnc'),
|
|
||||||
('EQ_preset', b'cEQP'),
|
|
||||||
('EQ_window', b'cEQW'),
|
|
||||||
('file_track', b'cFlT'),
|
|
||||||
('folder_playlist', b'cFoP'),
|
|
||||||
('item', b'cobj'),
|
|
||||||
('library_playlist', b'cLiP'),
|
|
||||||
('playlist', b'cPly'),
|
|
||||||
('playlist_window', b'cPlW'),
|
|
||||||
('radio_tuner_playlist', b'cRTP'),
|
|
||||||
('shared_track', b'cShT'),
|
|
||||||
('source', b'cSrc'),
|
|
||||||
('track', b'cTrk'),
|
|
||||||
('URL_track', b'cURT'),
|
|
||||||
('user_playlist', b'cUsP'),
|
|
||||||
('visual', b'cVis'),
|
|
||||||
('window', b'cwin')]
|
|
||||||
|
|
||||||
enums = \
|
|
||||||
[('track_listing', b'kTrk'),
|
|
||||||
('album_listing', b'kAlb'),
|
|
||||||
('cd_insert', b'kCDi'),
|
|
||||||
('standard', b'lwst'),
|
|
||||||
('detailed', b'lwdt'),
|
|
||||||
('stopped', b'kPSS'),
|
|
||||||
('playing', b'kPSP'),
|
|
||||||
('paused', b'kPSp'),
|
|
||||||
('fast_forwarding', b'kPSF'),
|
|
||||||
('rewinding', b'kPSR'),
|
|
||||||
('off', b'kRpO'),
|
|
||||||
('one', b'kRp1'),
|
|
||||||
('all', b'kAll'),
|
|
||||||
('small', b'kVSS'),
|
|
||||||
('medium', b'kVSM'),
|
|
||||||
('large', b'kVSL'),
|
|
||||||
('library', b'kLib'),
|
|
||||||
('iPod', b'kPod'),
|
|
||||||
('audio_CD', b'kACD'),
|
|
||||||
('MP3_CD', b'kMCD'),
|
|
||||||
('device', b'kDev'),
|
|
||||||
('radio_tuner', b'kTun'),
|
|
||||||
('shared_library', b'kShd'),
|
|
||||||
('unknown', b'kUnk'),
|
|
||||||
('albums', b'kSrL'),
|
|
||||||
('artists', b'kSrR'),
|
|
||||||
('composers', b'kSrC'),
|
|
||||||
('displayed', b'kSrV'),
|
|
||||||
('songs', b'kSrS'),
|
|
||||||
('none', b'kNon'),
|
|
||||||
('Books', b'kSpA'),
|
|
||||||
('folder', b'kSpF'),
|
|
||||||
('Genius', b'kSpG'),
|
|
||||||
('iTunes_U', b'kSpU'),
|
|
||||||
('Library', b'kSpL'),
|
|
||||||
('Movies', b'kSpI'),
|
|
||||||
('Music', b'kSpZ'),
|
|
||||||
('Party_Shuffle', b'kSpS'),
|
|
||||||
('Podcasts', b'kSpP'),
|
|
||||||
('Purchased_Music', b'kSpM'),
|
|
||||||
('TV_Shows', b'kSpT'),
|
|
||||||
('movie', b'kVdM'),
|
|
||||||
('music_video', b'kVdV'),
|
|
||||||
('TV_show', b'kVdT'),
|
|
||||||
('user', b'kRtU'),
|
|
||||||
('computed', b'kRtC')]
|
|
||||||
|
|
||||||
properties = \
|
|
||||||
[('copies', b'lwcp'),
|
|
||||||
('collating', b'lwcl'),
|
|
||||||
('starting_page', b'lwfp'),
|
|
||||||
('ending_page', b'lwlp'),
|
|
||||||
('pages_across', b'lwla'),
|
|
||||||
('pages_down', b'lwld'),
|
|
||||||
('error_handling', b'lweh'),
|
|
||||||
('requested_print_time', b'lwqt'),
|
|
||||||
('printer_features', b'lwpf'),
|
|
||||||
('fax_number', b'faxn'),
|
|
||||||
('target_printer', b'trpr'),
|
|
||||||
('current_encoder', b'pEnc'),
|
|
||||||
('current_EQ_preset', b'pEQP'),
|
|
||||||
('current_playlist', b'pPla'),
|
|
||||||
('current_stream_title', b'pStT'),
|
|
||||||
('current_stream_URL', b'pStU'),
|
|
||||||
('current_track', b'pTrk'),
|
|
||||||
('current_visual', b'pVis'),
|
|
||||||
('EQ_enabled', b'pEQ '),
|
|
||||||
('fixed_indexing', b'pFix'),
|
|
||||||
('frontmost', b'pisf'),
|
|
||||||
('full_screen', b'pFSc'),
|
|
||||||
('name', b'pnam'),
|
|
||||||
('mute', b'pMut'),
|
|
||||||
('player_position', b'pPos'),
|
|
||||||
('player_state', b'pPlS'),
|
|
||||||
('selection', b'sele'),
|
|
||||||
('sound_volume', b'pVol'),
|
|
||||||
('version', b'vers'),
|
|
||||||
('visuals_enabled', b'pVsE'),
|
|
||||||
('visual_size', b'pVSz'),
|
|
||||||
('data', b'pPCT'),
|
|
||||||
('description', b'pDes'),
|
|
||||||
('downloaded', b'pDlA'),
|
|
||||||
('format', b'pFmt'),
|
|
||||||
('kind', b'pKnd'),
|
|
||||||
('raw_data', b'pRaw'),
|
|
||||||
('artist', b'pArt'),
|
|
||||||
('compilation', b'pAnt'),
|
|
||||||
('composer', b'pCmp'),
|
|
||||||
('disc_count', b'pDsC'),
|
|
||||||
('disc_number', b'pDsN'),
|
|
||||||
('genre', b'pGen'),
|
|
||||||
('year', b'pYr '),
|
|
||||||
('location', b'pLoc'),
|
|
||||||
('minimized', b'pMin'),
|
|
||||||
('view', b'pPly'),
|
|
||||||
('band_1', b'pEQ1'),
|
|
||||||
('band_2', b'pEQ2'),
|
|
||||||
('band_3', b'pEQ3'),
|
|
||||||
('band_4', b'pEQ4'),
|
|
||||||
('band_5', b'pEQ5'),
|
|
||||||
('band_6', b'pEQ6'),
|
|
||||||
('band_7', b'pEQ7'),
|
|
||||||
('band_8', b'pEQ8'),
|
|
||||||
('band_9', b'pEQ9'),
|
|
||||||
('band_10', b'pEQ0'),
|
|
||||||
('modifiable', b'pMod'),
|
|
||||||
('preamp', b'pEQA'),
|
|
||||||
('update_tracks', b'pUTC'),
|
|
||||||
('container', b'ctnr'),
|
|
||||||
('id', b'ID '),
|
|
||||||
('index', b'pidx'),
|
|
||||||
('persistent_ID', b'pPIS'),
|
|
||||||
('duration', b'pDur'),
|
|
||||||
('parent', b'pPlP'),
|
|
||||||
('shuffle', b'pShf'),
|
|
||||||
('size', b'pSiz'),
|
|
||||||
('song_repeat', b'pRpt'),
|
|
||||||
('special_kind', b'pSpK'),
|
|
||||||
('time', b'pTim'),
|
|
||||||
('visible', b'pvis'),
|
|
||||||
('capacity', b'capa'),
|
|
||||||
('free_space', b'frsp'),
|
|
||||||
('album', b'pAlb'),
|
|
||||||
('album_artist', b'pAlA'),
|
|
||||||
('album_rating', b'pAlR'),
|
|
||||||
('album_rating_kind', b'pARk'),
|
|
||||||
('bit_rate', b'pBRt'),
|
|
||||||
('bookmark', b'pBkt'),
|
|
||||||
('bookmarkable', b'pBkm'),
|
|
||||||
('bpm', b'pBPM'),
|
|
||||||
('category', b'pCat'),
|
|
||||||
('comment', b'pCmt'),
|
|
||||||
('database_ID', b'pDID'),
|
|
||||||
('date_added', b'pAdd'),
|
|
||||||
('enabled', b'enbl'),
|
|
||||||
('episode_ID', b'pEpD'),
|
|
||||||
('episode_number', b'pEpN'),
|
|
||||||
('EQ', b'pEQp'),
|
|
||||||
('finish', b'pStp'),
|
|
||||||
('gapless', b'pGpl'),
|
|
||||||
('grouping', b'pGrp'),
|
|
||||||
('long_description', b'pLds'),
|
|
||||||
('lyrics', b'pLyr'),
|
|
||||||
('modification_date', b'asmo'),
|
|
||||||
('played_count', b'pPlC'),
|
|
||||||
('played_date', b'pPlD'),
|
|
||||||
('podcast', b'pTPc'),
|
|
||||||
('rating', b'pRte'),
|
|
||||||
('rating_kind', b'pRtk'),
|
|
||||||
('release_date', b'pRlD'),
|
|
||||||
('sample_rate', b'pSRt'),
|
|
||||||
('season_number', b'pSeN'),
|
|
||||||
('shufflable', b'pSfa'),
|
|
||||||
('skipped_count', b'pSkC'),
|
|
||||||
('skipped_date', b'pSkD'),
|
|
||||||
('show', b'pShw'),
|
|
||||||
('sort_album', b'pSAl'),
|
|
||||||
('sort_artist', b'pSAr'),
|
|
||||||
('sort_album_artist', b'pSAA'),
|
|
||||||
('sort_name', b'pSNm'),
|
|
||||||
('sort_composer', b'pSCm'),
|
|
||||||
('sort_show', b'pSSN'),
|
|
||||||
('start', b'pStr'),
|
|
||||||
('track_count', b'pTrC'),
|
|
||||||
('track_number', b'pTrN'),
|
|
||||||
('unplayed', b'pUnp'),
|
|
||||||
('video_kind', b'pVdK'),
|
|
||||||
('volume_adjustment', b'pAdj'),
|
|
||||||
('address', b'pURL'),
|
|
||||||
('shared', b'pShr'),
|
|
||||||
('smart', b'pSmt'),
|
|
||||||
('bounds', b'pbnd'),
|
|
||||||
('closeable', b'hclb'),
|
|
||||||
('collapseable', b'pWSh'),
|
|
||||||
('collapsed', b'wshd'),
|
|
||||||
('position', b'ppos'),
|
|
||||||
('resizable', b'prsz'),
|
|
||||||
('zoomable', b'iszm'),
|
|
||||||
('zoomed', b'pzum')]
|
|
||||||
|
|
||||||
elements = \
|
|
||||||
[('artworks', b'cArt'),
|
|
||||||
('audio_CD_playlists', b'cCDP'),
|
|
||||||
('audio_CD_tracks', b'cCDT'),
|
|
||||||
('browser_windows', b'cBrW'),
|
|
||||||
('device_playlists', b'cDvP'),
|
|
||||||
('device_tracks', b'cDvT'),
|
|
||||||
('encoders', b'cEnc'),
|
|
||||||
('EQ_presets', b'cEQP'),
|
|
||||||
('EQ_windows', b'cEQW'),
|
|
||||||
('file_tracks', b'cFlT'),
|
|
||||||
('folder_playlists', b'cFoP'),
|
|
||||||
('items', b'cobj'),
|
|
||||||
('library_playlists', b'cLiP'),
|
|
||||||
('playlists', b'cPly'),
|
|
||||||
('playlist_windows', b'cPlW'),
|
|
||||||
('radio_tuner_playlists', b'cRTP'),
|
|
||||||
('shared_tracks', b'cShT'),
|
|
||||||
('sources', b'cSrc'),
|
|
||||||
('tracks', b'cTrk'),
|
|
||||||
('URL_tracks', b'cURT'),
|
|
||||||
('user_playlists', b'cUsP'),
|
|
||||||
('visuals', b'cVis'),
|
|
||||||
('windows', b'cwin'),
|
|
||||||
('application', b'capp'),
|
|
||||||
('print_settings', b'pset')]
|
|
||||||
|
|
||||||
commands = \
|
|
||||||
[('set', b'coresetd', [('to', b'data')]),
|
|
||||||
('exists', b'coredoex', []),
|
|
||||||
('move', b'coremove', [('to', b'insh')]),
|
|
||||||
('subscribe', b'hookpSub', []),
|
|
||||||
('playpause', b'hookPlPs', []),
|
|
||||||
('download', b'hookDwnl', []),
|
|
||||||
('close', b'coreclos', []),
|
|
||||||
('open', b'aevtodoc', []),
|
|
||||||
('open_location', b'GURLGURL', []),
|
|
||||||
('quit', b'aevtquit', []),
|
|
||||||
('pause', b'hookPaus', []),
|
|
||||||
('make',
|
|
||||||
'corecrel',
|
|
||||||
[('new', b'kocl'), ('at', b'insh'), ('with_properties', b'prdt')]),
|
|
||||||
('duplicate', b'coreclon', [('to', b'insh')]),
|
|
||||||
('print_',
|
|
||||||
'aevtpdoc',
|
|
||||||
[('print_dialog', b'pdlg'),
|
|
||||||
('with_properties', b'prdt'),
|
|
||||||
('kind', b'pKnd'),
|
|
||||||
('theme', b'pThm')]),
|
|
||||||
('add', b'hookAdd ', [('to', b'insh')]),
|
|
||||||
('rewind', b'hookRwnd', []),
|
|
||||||
('play', b'hookPlay', [('once', b'POne')]),
|
|
||||||
('run', b'aevtoapp', []),
|
|
||||||
('resume', b'hookResu', []),
|
|
||||||
('updatePodcast', b'hookUpd1', []),
|
|
||||||
('next_track', b'hookNext', []),
|
|
||||||
('stop', b'hookStop', []),
|
|
||||||
('search', b'hookSrch', [('for_', b'pTrm'), ('only', b'pAre')]),
|
|
||||||
('updateAllPodcasts', b'hookUpdp', []),
|
|
||||||
('update', b'hookUpdt', []),
|
|
||||||
('previous_track', b'hookPrev', []),
|
|
||||||
('fast_forward', b'hookFast', []),
|
|
||||||
('count', b'corecnte', [('each', b'kocl')]),
|
|
||||||
('reveal', b'hookRevl', []),
|
|
||||||
('convert', b'hookConv', []),
|
|
||||||
('eject', b'hookEjct', []),
|
|
||||||
('back_track', b'hookBack', []),
|
|
||||||
('refresh', b'hookRfrs', []),
|
|
||||||
('delete', b'coredelo', [])]
|
|
@ -12,5 +12,4 @@ http://www.gnu.org/licenses/gpl-3.0.html
|
|||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase {}
|
@interface AppDelegate : AppDelegateBase {}
|
||||||
- (void)removeDeadTracks;
|
|
||||||
@end
|
@end
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||||
which should be included with this package. The terms are also available at
|
which should be included with this package. The terms are also available at
|
||||||
@ -12,7 +12,6 @@ http://www.gnu.org/licenses/gpl-3.0.html
|
|||||||
#import "ValueTransformers.h"
|
#import "ValueTransformers.h"
|
||||||
#import "Dialogs.h"
|
#import "Dialogs.h"
|
||||||
#import "DetailsPanel.h"
|
#import "DetailsPanel.h"
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
#import "ResultWindow.h"
|
#import "ResultWindow.h"
|
||||||
#import "Consts.h"
|
#import "Consts.h"
|
||||||
|
|
||||||
@ -48,21 +47,11 @@ http://www.gnu.org/licenses/gpl-3.0.html
|
|||||||
|
|
||||||
- (NSString *)homepageURL
|
- (NSString *)homepageURL
|
||||||
{
|
{
|
||||||
return @"http://www.hardcoded.net/dupeguru_me/";
|
return @"https://www.hardcoded.net/dupeguru_me/";
|
||||||
}
|
}
|
||||||
|
|
||||||
- (ResultWindowBase *)createResultWindow
|
- (ResultWindowBase *)createResultWindow
|
||||||
{
|
{
|
||||||
return [[ResultWindow alloc] initWithParentApp:self];
|
return [[ResultWindow alloc] initWithParentApp:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (DirectoryPanel *)createDirectoryPanel
|
|
||||||
{
|
|
||||||
return [[DirectoryPanelME alloc] initWithParentApp:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeDeadTracks
|
|
||||||
{
|
|
||||||
[(ResultWindow *)[self resultWindow] removeDeadTracks];
|
|
||||||
}
|
|
||||||
@end
|
@end
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "../base/Consts.h"
|
|
||||||
|
|
||||||
#define jobScanDeadTracks @"jobScanDeadTracks"
|
|
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "../base/DirectoryPanel.h"
|
|
||||||
|
|
||||||
@interface DirectoryPanelME : DirectoryPanel
|
|
||||||
{
|
|
||||||
}
|
|
||||||
- (IBAction)addiTunes:(id)sender;
|
|
||||||
@end
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
|
|
||||||
@implementation DirectoryPanelME
|
|
||||||
- (id)initWithParentApp:(id)aParentApp
|
|
||||||
{
|
|
||||||
self = [super initWithParentApp:aParentApp];
|
|
||||||
_alwaysShowPopUp = YES;
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)fillPopUpMenu
|
|
||||||
{
|
|
||||||
[super fillPopUpMenu];
|
|
||||||
NSMenu *m = [addButtonPopUp menu];
|
|
||||||
NSMenuItem *mi = [m insertItemWithTitle:NSLocalizedString(@"Add iTunes Library", @"") action:@selector(addiTunes:)
|
|
||||||
keyEquivalent:@"" atIndex:1];
|
|
||||||
[mi setTarget:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)addiTunes:(id)sender
|
|
||||||
{
|
|
||||||
[self addDirectory:@"iTunes Library"];
|
|
||||||
}
|
|
||||||
@end
|
|
@ -10,5 +10,4 @@ http://www.gnu.org/licenses/gpl-3.0.html
|
|||||||
#import "ResultWindowBase.h"
|
#import "ResultWindowBase.h"
|
||||||
|
|
||||||
@interface ResultWindow : ResultWindowBase {}
|
@interface ResultWindow : ResultWindowBase {}
|
||||||
- (void)removeDeadTracks;
|
|
||||||
@end
|
@end
|
||||||
|
@ -68,10 +68,4 @@ http://www.gnu.org/licenses/gpl-3.0.html
|
|||||||
[[c dataCell] setAlignment:NSRightTextAlignment];
|
[[c dataCell] setAlignment:NSRightTextAlignment];
|
||||||
[[table columns] restoreColumns];
|
[[table columns] restoreColumns];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
- (void)removeDeadTracks
|
|
||||||
{
|
|
||||||
[model scanDeadTracks];
|
|
||||||
}
|
|
||||||
@end
|
@end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user