Add initial screen to add config information

This commit is contained in:
Jaisen Mathai 2015-11-19 02:31:32 -08:00
parent 42b481ea60
commit a7aee65ca0
15 changed files with 145 additions and 228 deletions

34
app/html/config.html Normal file
View File

@ -0,0 +1,34 @@
<html>
<head>
<script src="js/handlers.js"></script>
<link href='https://fonts.googleapis.com/css?family=Lato:400,100,300,100italic,300italic' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="css/bootstrap.css"></script>
<link rel="stylesheet" href="css/boilerplate.css"></script>
<link rel="stylesheet" href="css/styles.css"></script>
<link rel="stylesheet" href="css/fontello/css/animation.css"></script>
<link rel="stylesheet" href="css/fontello/css/elodie.css"></script>
</head>
<body>
<div class="titlebar">
<!--<a href="" class="left quit quitProgram"><i class="icon-cancel-circle"></i></a>
<a href="" class="left minus minimizeProgram"><i class="icon-minus-circle"></i></a>-->
How can I help you? <em>-- Elodie</em><i></i>
</div>
<form class="updateConfig" action="" method="post">
<div class="content">
<p>
Doesn't look like you have a MapQuest API key. Get one for free <a href="#" data-url="https://developer.mapquest.com/plan_purchase/steps/business_edition/business_edition_free">here</a>.
</p>
<div class="location">
<label for="mapquest-api-key-field"><i class="icon-map"></i>MapQuest API Key</label>
<input id="mapquest-api-key-field" type="text" placeholder="i.e. pzjNKTtTjLydLtxUBwdgKAIC8OQbGLUy">
<button type="submit" class="push">Get Started<i></i></button>
</div>
</div>
</form>
<script>
document.getElementById('location-field').focus();
</script>
</body>
</html>

View File

@ -22,6 +22,15 @@ if(typeof(require) === 'function') {
//var response = JSON.parse(args['stdout']); //var response = JSON.parse(args['stdout']);
handlers.removeProgressIcons(); 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) { ipc.on('update-photos-success', function(args) {
var response = JSON.parse(args['stdout']); var response = JSON.parse(args['stdout']);
handlers.setSuccessTitle(); handlers.setSuccessTitle();
@ -62,6 +71,12 @@ function Handlers() {
ev.preventDefault(); ev.preventDefault();
broadcast.send('launch-finder', tgt); 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) { this.click.quitProgram = function(ev) {
//ev.preventDefault(); //ev.preventDefault();
console.log('quit'); console.log('quit');
@ -84,6 +99,23 @@ function Handlers() {
document.querySelector('button.push i').className = 'icon-spin animate-spin'; document.querySelector('button.push i').className = 'icon-spin animate-spin';
broadcast.send('import-photos', params); 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) { this.submit.updatePhotos = function(ev) {
var el = ev.target, var el = ev.target,
cls = el.className, cls = el.className,
@ -97,8 +129,9 @@ function Handlers() {
params['album'] = document.querySelector('input[id="album-field"]').value; params['album'] = document.querySelector('input[id="album-field"]').value;
params['title'] = document.querySelector('input[id="title-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; return;
}
params['files'] = __process__.files; params['files'] = __process__.files;
broadcast.send('update-photos', params); broadcast.send('update-photos', params);

View File

@ -1,6 +1,6 @@
var ipc = require('ipc'); var ipc = require('ipc'),
var toolbarUi = require('./modules/toolbar-ui.js'); toolbarUi = require('./modules/toolbar-ui.js'),
var broadcast = require('./modules/broadcast.js'); broadcast = require('./modules/broadcast.js');
toolbarUi.app.on('ready', toolbarUi.ready); toolbarUi.app.on('ready', toolbarUi.ready);
toolbarUi.app.on('create-window', toolbarUi.createWindow); toolbarUi.app.on('create-window', toolbarUi.createWindow);
@ -11,6 +11,8 @@ toolbarUi.app.on('hide', toolbarUi.show);
toolbarUi.app.on('after-hide', toolbarUi.afterHide); toolbarUi.app.on('after-hide', toolbarUi.afterHide);
ipc.on('import-photos', broadcast.importPhotos); ipc.on('import-photos', broadcast.importPhotos);
ipc.on('update-config', broadcast.updateConfig);
ipc.on('update-photos', broadcast.updatePhotos); ipc.on('update-photos', broadcast.updatePhotos);
ipc.on('launch-finder', broadcast.launchFinder); ipc.on('launch-finder', broadcast.launchFinder);
ipc.on('launch-url', broadcast.launchUrl);
ipc.on('program-quit', broadcast.programQuit); ipc.on('program-quit', broadcast.programQuit);

View File

@ -1,6 +1,7 @@
var exports = module.exports = {}; 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. // 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. // 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. // 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 // 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); shell.showItemInFolder(path);
}; };
exports.launchUrl = function(event, url) {
console.log(url);
var shell = require('shell');
shell.openExternal(url);
};
exports.programQuit = function(event, path) { exports.programQuit = function(event, path) {
console.log('program-quit'); console.log('program-quit');
//mb.tray.destroy(); //mb.tray.destroy();

37
app/modules/config.js Normal file
View File

@ -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;
}
};

View File

@ -3,6 +3,7 @@ var exports = module.exports = {};
var menubar = require('menubar'), var menubar = require('menubar'),
menu = require('menu'), menu = require('menu'),
tray = require('tray'), tray = require('tray'),
config = require('./config.js'),
loadUrl = null; loadUrl = null;
exports.app = app = menubar( exports.app = app = menubar(
@ -12,6 +13,7 @@ exports.app = app = menubar(
index: 'index.html', index: 'index.html',
pages: { pages: {
'blank': 'blank.html', 'blank': 'blank.html',
'config': 'config.html',
'location': 'location.html' 'location': 'location.html'
}, },
width: 400, width: 400,
@ -21,7 +23,8 @@ exports.app = app = menubar(
); );
exports.ready = function() { exports.ready = function() {
console.log('app is ready') console.log('app is ready');
var template = [{ var template = [{
label: "Application", label: "Application",
submenu: [ submenu: [
@ -65,9 +68,12 @@ exports.afterCreateWindow = function() {
}; };
exports.show = function() { exports.show = function() {
if(loadUrl === null) { if(!config.hasConfig()) {
loadUrl = this.getOption('pages')['config'];
} else if(loadUrl === null) {
loadUrl = this.getOption('index'); loadUrl = this.getOption('index');
} }
this.window.loadUrl('file://' + this.getOption('dir') + '/' + loadUrl); this.window.loadUrl('file://' + this.getOption('dir') + '/' + loadUrl);
loadUrl = null; loadUrl = null;
//app.window.openDevTools(); //app.window.openDevTools();

View File

@ -38,7 +38,7 @@ def _import(params):
if(media is None): if(media is None):
continue continue
if(type(media).__name__ == 'Video'): if(media.__name__ == 'Video'):
filesystem.set_date_from_path_video(media) filesystem.set_date_from_path_video(media)
dest_path = filesystem.process_file(current_file, destination, media, allowDuplicate=False, move=False) 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) print '%s -> %s' % (current_file, dest_path)
elif(params['--file'] is not None): elif(params['--file'] is not None):
current_file = params['--file'] 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'): if(media.__name__ == 'Video'):
filesystem.set_date_from_path_video(media) filesystem.set_date_from_path_video(media)

View File

@ -6,7 +6,7 @@ block_cipher = None
a = Analysis(['elodie.py'], a = Analysis(['elodie.py'],
pathex=['/Users/jaisenmathai/dev/tools/elodie'], pathex=['/Users/jaisenmathai/dev/tools/elodie'],
binaries=None, binaries=None,
datas=[('config.ini',''),('configs/ExifTool_config', 'configs')], datas=[('configs/ExifTool_config', 'configs')],
hiddenimports=[], hiddenimports=[],
hookspath=None, hookspath=None,
runtime_hooks=None, runtime_hooks=None,

View File

@ -122,6 +122,7 @@ class FileSystem:
if(len(path) < 2): if(len(path) < 2):
path.append('Unknown Location') path.append('Unknown Location')
#return '/'.join(path[::-1])
return '/'.join(path) return '/'.join(path)
def process_file(self, _file, destination, media, **kwargs): def process_file(self, _file, destination, media, **kwargs):

View File

@ -74,7 +74,7 @@ def dms_to_decimal(degrees, minutes, seconds, sign=' '):
) )
def get_key(): 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): if not path.exists(config_file):
return None return None

View File

@ -24,7 +24,7 @@ Media class for general video operations
class Media(object): class Media(object):
# class / static variable accessible through get_valid_extensions() # class / static variable accessible through get_valid_extensions()
video_extensions = ('avi','m4v','mov','mp4','3gp') 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 @param, source, string, The fully qualified path to the video file
@ -335,6 +335,7 @@ class Media(object):
name = 'Video' name = 'Video'
for i in classes: for i in classes:
print i.__name__
if(name == i.__name__): if(name == i.__name__):
return i(_file) return i(_file)

View File

@ -21,6 +21,7 @@ from elodie import geolocation
Photo class for general photo operations Photo class for general photo operations
""" """
class Photo(Media): class Photo(Media):
__name__ = 'Photo'
""" """
@param, source, string, The fully qualified path to the photo file @param, source, string, The fully qualified path to the photo file

View File

@ -24,8 +24,7 @@ from media import Media
Video class for general video operations Video class for general video operations
""" """
class Video(Media): class Video(Media):
# class / static variable accessible through get_valid_extensions() __name__ = 'Video'
""" """
@param, source, string, The fully qualified path to the video file @param, source, string, The fully qualified path to the video file

View File

@ -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)

137
update.py
View File

@ -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=<string time> --location=<string location> --process=no file1 file2...fileN')
config = parse_arguments(args)
if __name__ == '__main__':
main(config, sys.argv)
sys.exit(0)