diff --git a/app/html/config.html b/app/html/config.html new file mode 100644 index 0000000..66e9c5d --- /dev/null +++ b/app/html/config.html @@ -0,0 +1,34 @@ + +
+ + + + + + + + + + + + + + + diff --git a/app/html/js/handlers.js b/app/html/js/handlers.js index f073529..4fe1c82 100644 --- a/app/html/js/handlers.js +++ b/app/html/js/handlers.js @@ -22,6 +22,15 @@ if(typeof(require) === 'function') { //var response = JSON.parse(args['stdout']); handlers.removeProgressIcons(); }); + ipc.on('update-config-status', function(args) { + if(args) { + // @TODO: We should really handle this in the nodejs code. + handlers.removeProgressIcons(); + location.href = 'index.html'; + } else { + + } + }); ipc.on('update-photos-success', function(args) { var response = JSON.parse(args['stdout']); handlers.setSuccessTitle(); @@ -62,6 +71,12 @@ function Handlers() { ev.preventDefault(); broadcast.send('launch-finder', tgt); }; + this.click.launchUrl = function(ev) { + var el = ev.target, + tgt = el.dataset.url; + ev.preventDefault(); + broadcast.send('launch-url', tgt); + }; this.click.quitProgram = function(ev) { //ev.preventDefault(); console.log('quit'); @@ -84,6 +99,23 @@ function Handlers() { document.querySelector('button.push i').className = 'icon-spin animate-spin'; broadcast.send('import-photos', params); }; + this.submit.updateConfig = function(ev) { + var el = ev.target, + cls = el.className, + params; + + ev.preventDefault(); + document.querySelector('button.push i').className = 'icon-spin animate-spin'; + + params = {}; + params['mapQuestKey'] = document.querySelector('input[id="mapquest-api-key-field"]').value; + + if(params['mapQuestKey'].length === 0) { + return; + } + + broadcast.send('update-config', params); + }; this.submit.updatePhotos = function(ev) { var el = ev.target, cls = el.className, @@ -97,8 +129,9 @@ function Handlers() { params['album'] = document.querySelector('input[id="album-field"]').value; params['title'] = document.querySelector('input[id="title-field"]').value; - if(params['location'].length === 0 && params['datetime'].length === 0 && params['album'].length === 0 && params['title'].length === 0) + if(params['location'].length === 0 && params['datetime'].length === 0 && params['album'].length === 0 && params['title'].length === 0) { return; + } params['files'] = __process__.files; broadcast.send('update-photos', params); diff --git a/app/index.js b/app/index.js index a21203b..787921b 100644 --- a/app/index.js +++ b/app/index.js @@ -1,6 +1,6 @@ -var ipc = require('ipc'); -var toolbarUi = require('./modules/toolbar-ui.js'); -var broadcast = require('./modules/broadcast.js'); +var ipc = require('ipc'), + toolbarUi = require('./modules/toolbar-ui.js'), + broadcast = require('./modules/broadcast.js'); toolbarUi.app.on('ready', toolbarUi.ready); toolbarUi.app.on('create-window', toolbarUi.createWindow); @@ -11,6 +11,8 @@ toolbarUi.app.on('hide', toolbarUi.show); toolbarUi.app.on('after-hide', toolbarUi.afterHide); ipc.on('import-photos', broadcast.importPhotos); +ipc.on('update-config', broadcast.updateConfig); ipc.on('update-photos', broadcast.updatePhotos); ipc.on('launch-finder', broadcast.launchFinder); +ipc.on('launch-url', broadcast.launchUrl); ipc.on('program-quit', broadcast.programQuit); diff --git a/app/modules/broadcast.js b/app/modules/broadcast.js index b067ac3..3f8ff72 100644 --- a/app/modules/broadcast.js +++ b/app/modules/broadcast.js @@ -1,6 +1,7 @@ var exports = module.exports = {}; -var exec = require('child_process').exec; +var exec = require('child_process').exec, + config = require('./config.js'); // The main process listens for events from the web renderer. // When photos are dragged onto the toolbar and photos are requested to be updated it will fire an 'update-photos' ipc event. @@ -39,6 +40,16 @@ exports.importPhotos = function(event, args) { }); }; +exports.updateConfig = function(event, args) { + var params = args, + status; + status = config.writeConfig(params); + if(status) { + event.sender.send('update-config-status', true); + } else { + event.sender.send('update-config-status', false); + } +}; // When photos are dragged onto the toolbar and photos are requested to be updated it will fire an 'update-photos' ipc event. // The web renderer will send the list of photos, type of update and new value to apply @@ -99,6 +110,12 @@ exports.launchFinder = function(event, path) { shell.showItemInFolder(path); }; +exports.launchUrl = function(event, url) { + console.log(url); + var shell = require('shell'); + shell.openExternal(url); +}; + exports.programQuit = function(event, path) { console.log('program-quit'); //mb.tray.destroy(); diff --git a/app/modules/config.js b/app/modules/config.js new file mode 100644 index 0000000..c4150b7 --- /dev/null +++ b/app/modules/config.js @@ -0,0 +1,37 @@ +var exports = module.exports = {}; +var fs = require('fs'), + defaultConfigFile = (function() { + var f = __dirname; + for(var i=0; i<2; i++) { + f = f.substr(0, f.lastIndexOf('/')); + } + return f + '/config.ini-sample'; + })(), + configFile = (process.env.HOME || process.env.USERPROFILE) + '/.elodie/config.ini', + hasConfig, + setConfig; + +exports.hasConfig = function() { + console.log(defaultConfigFile); + console.log(configFile); + return fs.existsSync(configFile); +}; + +exports.writeConfig = function(params) { + var contents; + try { + if(exports.hasConfig()) { + contents = fs.readFileSync(configFile).toString(); + } else { + contents = fs.readFileSync(defaultConfigFile).toString(); + } + + console.log(contents); + contents = contents.replace(/key=[\s\S]+$/,'key='+params['mapQuestKey']); + fs.writeFileSync(configFile, contents); + return true; + } catch(e) { + console.log(e); + return false; + } +}; diff --git a/app/modules/toolbar-ui.js b/app/modules/toolbar-ui.js index 45adea5..f32852b 100644 --- a/app/modules/toolbar-ui.js +++ b/app/modules/toolbar-ui.js @@ -3,6 +3,7 @@ var exports = module.exports = {}; var menubar = require('menubar'), menu = require('menu'), tray = require('tray'), + config = require('./config.js'), loadUrl = null; exports.app = app = menubar( @@ -12,6 +13,7 @@ exports.app = app = menubar( index: 'index.html', pages: { 'blank': 'blank.html', + 'config': 'config.html', 'location': 'location.html' }, width: 400, @@ -21,7 +23,8 @@ exports.app = app = menubar( ); exports.ready = function() { - console.log('app is ready') + console.log('app is ready'); + var template = [{ label: "Application", submenu: [ @@ -65,9 +68,12 @@ exports.afterCreateWindow = function() { }; exports.show = function() { - if(loadUrl === null) { + if(!config.hasConfig()) { + loadUrl = this.getOption('pages')['config']; + } else if(loadUrl === null) { loadUrl = this.getOption('index'); } + this.window.loadUrl('file://' + this.getOption('dir') + '/' + loadUrl); loadUrl = null; //app.window.openDevTools(); diff --git a/elodie.py b/elodie.py index ced1572..0222842 100755 --- a/elodie.py +++ b/elodie.py @@ -38,7 +38,7 @@ def _import(params): if(media is None): continue - if(type(media).__name__ == 'Video'): + if(media.__name__ == 'Video'): filesystem.set_date_from_path_video(media) dest_path = filesystem.process_file(current_file, destination, media, allowDuplicate=False, move=False) @@ -46,7 +46,7 @@ def _import(params): print '%s -> %s' % (current_file, dest_path) elif(params['--file'] is not None): current_file = params['--file'] - media = Media.get_class_by_file(current_file) + media = Media.get_class_by_file(current_file, [Photo, Video]) if(media.__name__ == 'Video'): filesystem.set_date_from_path_video(media) diff --git a/elodie.spec b/elodie.spec index 83daf12..5488e3d 100644 --- a/elodie.spec +++ b/elodie.spec @@ -6,7 +6,7 @@ block_cipher = None a = Analysis(['elodie.py'], pathex=['/Users/jaisenmathai/dev/tools/elodie'], binaries=None, - datas=[('config.ini',''),('configs/ExifTool_config', 'configs')], + datas=[('configs/ExifTool_config', 'configs')], hiddenimports=[], hookspath=None, runtime_hooks=None, diff --git a/elodie/filesystem.py b/elodie/filesystem.py index 6083aab..495cbb9 100644 --- a/elodie/filesystem.py +++ b/elodie/filesystem.py @@ -122,6 +122,7 @@ class FileSystem: if(len(path) < 2): path.append('Unknown Location') + #return '/'.join(path[::-1]) return '/'.join(path) def process_file(self, _file, destination, media, **kwargs): diff --git a/elodie/geolocation.py b/elodie/geolocation.py index 3f787de..1616b44 100644 --- a/elodie/geolocation.py +++ b/elodie/geolocation.py @@ -74,7 +74,7 @@ def dms_to_decimal(degrees, minutes, seconds, sign=' '): ) def get_key(): - config_file = '%s/config.ini' % path.dirname(path.dirname(path.abspath(__file__))) + config_file = '%s/config.ini' % constants.application_directory if not path.exists(config_file): return None diff --git a/elodie/media/media.py b/elodie/media/media.py index b52e645..6627c1d 100644 --- a/elodie/media/media.py +++ b/elodie/media/media.py @@ -24,7 +24,7 @@ Media class for general video operations class Media(object): # class / static variable accessible through get_valid_extensions() video_extensions = ('avi','m4v','mov','mp4','3gp') - photo_extensions = ('jpg', 'jpeg', 'nef', 'dng') + photo_extensions = ('jpg', 'jpeg', 'nef', 'dng', 'gif') """ @param, source, string, The fully qualified path to the video file @@ -335,6 +335,7 @@ class Media(object): name = 'Video' for i in classes: + print i.__name__ if(name == i.__name__): return i(_file) diff --git a/elodie/media/photo.py b/elodie/media/photo.py index f6f267b..6ed46a6 100644 --- a/elodie/media/photo.py +++ b/elodie/media/photo.py @@ -21,6 +21,7 @@ from elodie import geolocation Photo class for general photo operations """ class Photo(Media): + __name__ = 'Photo' """ @param, source, string, The fully qualified path to the photo file diff --git a/elodie/media/video.py b/elodie/media/video.py index d7e29e3..6e04f18 100644 --- a/elodie/media/video.py +++ b/elodie/media/video.py @@ -24,8 +24,7 @@ from media import Media Video class for general video operations """ class Video(Media): - # class / static variable accessible through get_valid_extensions() - + __name__ = 'Video' """ @param, source, string, The fully qualified path to the video file diff --git a/import.py b/import.py deleted file mode 100755 index 7392620..0000000 --- a/import.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python - -import os -import shutil -import sys - -from elodie import arguments -from elodie.media.photo import Photo -from elodie.media.video import Video -from elodie.filesystem import FileSystem -from elodie.localstorage import Db - -def help(): - return """ - usage: ./import.py --type=photo --source=/path/to/photos --destination=/path/to/destination - - --type Valid values are 'photo' or 'video'. Only files of *type* are imported. - --file Full path to a photo or video to be imported. The --type argument should match the file type of the file. - @TODO: Automatically determine *type* from *file* - --source Full path to a directory which will be recursively crawled for files of *type*. - --destination Full path to a directory where organized photos will be placed. - """ - -def parse_arguments(args): - config = { - 'type': 'photo', - 'file': None, - 'source': None, - 'destination': None - } - - if('destination' not in args): - help() - sys.exit(2) - - config.update(args) - return config - -def main(argv): - - destination = config['destination'] - if(config['type'] == 'photo'): - media_type = Photo - else: - media_type = Video - - if(config['source'] is not None): - source = config['source'] - - for current_file in filesystem.get_all_files(source, media_type.get_valid_extensions()): - media = media_type(current_file) - - if(media_type.__name__ == 'Video'): - filesystem.set_date_from_path_video(media) - - dest_path = filesystem.process_file(current_file, destination, media, allowDuplicate=False, move=False) - if(dest_path is not None): - print '%s -> %s' % (current_file, dest_path) - elif(config['file'] is not None): - media = media_type(config['file']) - if(media_type.__name__ == 'Video'): - filesystem.set_date_from_path_video(media) - - dest_path = filesystem.process_file(config['file'], destination, media, allowDuplicate=False, move=False) - if(dest_path is not None): - print '%s -> %s' % (current_file, dest_path) - else: - help() - -db = Db() -filesystem = FileSystem() -args = arguments.parse(sys.argv[1:], None, ['file=','type=','source=','destination='], help()) -config = parse_arguments(args) - -if __name__ == '__main__': - main(config) - sys.exit(0) diff --git a/update.py b/update.py deleted file mode 100755 index 25844a0..0000000 --- a/update.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python - -import os -import pyexiv2 -import re -import shutil -import sys -import time - -from datetime import datetime - -from elodie import arguments -from elodie import constants -from elodie import geolocation -from elodie.media.photo import Media -from elodie.media.photo import Photo -from elodie.media.video import Video -from elodie.filesystem import FileSystem -from elodie.localstorage import Db - -def parse_arguments(args): - config = { - 'title': None, - 'time': None, - 'location': None, - 'album': None, - 'process': 'yes' - } - - config.update(args) - return config - -def main(config, args): - location_coords = None - for arg in args: - file_path = arg - if(arg[:2] == '--'): - continue - elif(not os.path.exists(arg)): - if(constants.debug == True): - print 'Could not find %s' % arg - print '{"source":"%s", "error_msg":"Could not find %s"}' % (file_path, arg) - continue - - destination = os.path.dirname(os.path.dirname(os.path.dirname(file_path))) - - _class = None - extension = os.path.splitext(file_path)[1][1:].lower() - if(extension in Photo.get_valid_extensions()): - _class = Photo - elif(extension in Video.get_valid_extensions()): - _class = Video - - if(_class is None): - continue - - media = _class(file_path) - - updated = False - if(config['location'] is not None): - if(location_coords is None): - location_coords = geolocation.coordinates_by_name(config['location']) - - if(location_coords is not None and 'latitude' in location_coords and 'longitude' in location_coords): - location_status = media.set_location(location_coords['latitude'], location_coords['longitude']) - if(location_status != True): - if(constants.debug == True): - print 'Failed to update location' - print '{"source":"%s", "error_msg":"Failed to update location"}' % file_path - sys.exit(1) - updated = True - - - if(config['time'] is not None): - time_string = config['time'] - time_format = '%Y-%m-%d %H:%M:%S' - if(re.match('^\d{4}-\d{2}-\d{2}$', time_string)): - time_string = '%s 00:00:00' % time_string - - if(re.match('^\d{4}-\d{2}-\d{2}$', time_string) is None and re.match('^\d{4}-\d{2}-\d{2} \d{2}:\d{2}\d{2}$', time_string)): - if(constants.debug == True): - print 'Invalid time format. Use YYYY-mm-dd hh:ii:ss or YYYY-mm-dd' - print '{"source":"%s", "error_msg":"Invalid time format. Use YYYY-mm-dd hh:ii:ss or YYYY-mm-dd"}' % file_path - sys.exit(1) - - if(time_format is not None): - time = datetime.strptime(time_string, time_format) - media.set_datetime(time) - updated = True - - if(config['album'] is not None): - media.set_album(config['album']) - updated = True - - # Updating a title can be problematic when doing it 2+ times on a file. - # You would end up with img_001.jpg -> img_001-first-title.jpg -> img_001-first-title-second-title.jpg. - # To resolve that we have to track the prior title (if there was one. - # Then we massage the updated_media's metadata['base_name'] to remove the old title. - # Since FileSystem.get_file_name() relies on base_name it will properly rename the file by updating the title - # instead of appending it. - remove_old_title_from_name = False - if(config['title'] is not None): - # We call get_metadata() to cache it before making any changes - metadata = media.get_metadata() - title_update_status = media.set_title(config['title']) - original_title = metadata['title'] - if(title_update_status and original_title is not None): - # @TODO: We should move this to a shared method since FileSystem.get_file_name() does it too. - original_title = re.sub('\W+', '-', original_title.lower()) - original_base_name = metadata['base_name'] - remove_old_title_from_name = True - updated = True - - if(updated == True): - updated_media = _class(file_path) - # See comments above on why we have to do this when titles get updated. - if(remove_old_title_from_name is True and len(original_title) > 0): - updated_media.get_metadata() - updated_media.set_metadata_basename(original_base_name.replace('-%s' % original_title, '')) - - dest_path = filesystem.process_file(file_path, destination, updated_media, move=True, allowDuplicate=True) - if(constants.debug == True): - print '%s -> %s' % (file_path, dest_path) - - print '{"source":"%s", "destination":"%s"}' % (file_path, dest_path) - # If the folder we moved the file out of or its parent are empty we delete it. - filesystem.delete_directory_if_empty(os.path.dirname(file_path)) - filesystem.delete_directory_if_empty(os.path.dirname(os.path.dirname(file_path))) - -db = Db() -filesystem = FileSystem() -args = arguments.parse(sys.argv[1:], None, ['title=','album=','time=','location=','process='], './update.py --time=