Skip to content

Commit

Permalink
More
Browse files Browse the repository at this point in the history
  • Loading branch information
Rami Chowdhury committed Dec 10, 2017
1 parent 44b656a commit ff73a05
Show file tree
Hide file tree
Showing 4 changed files with 372 additions and 155 deletions.
8 changes: 7 additions & 1 deletion imagepicker/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Modified from the PyQt5 imageviewer example application
'''
import sys
import logging
import typing as T

from PyQt5.QtWidgets import QApplication
Expand All @@ -24,8 +25,13 @@ def main(argv: T.List[str]=None) -> None:
print(__version__)
sys.exit(0)

logging.basicConfig(format='%(asctime)s %(levelname)s %(module)s %(funcName)s: %(message)s')
logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)

app = QApplication(argv)
picker = ImagePicker()
picker = ImagePicker(logger=logger)
app.installEventFilter(picker)
picker.show()
sys.exit(app.exec_())

Expand Down
162 changes: 100 additions & 62 deletions imagepicker/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,127 +3,165 @@
Holding the state for the application.
'''
import os
from os.path import join, abspath, relpath, exists, isdir, islink, isabs, basename
import typing as T

from ruamel.yaml import YAML
from imagepicker.utils import listImageFiles
from imagepicker.utils import listImageFiles, setPDBTrace


# TODO: we're going to need caching and such to see how many things are
# in each album, and so on, instead of O(n) len(os.listdir()) calls...


class PickerModel:
'''Maintain the state for the ImagePicker.'''

files: T.List[str]
picked: T.Set[str]
outfile: str
infile: str
albums: T.Dict[str, str]
inputDir: str
inputFiles: T.List[str]
settingsFile: str
current: int

def __init__(self, infile: str=None, outfile: str=None) -> None:
def __init__(self, settingsFile: str, inputDirectory: str) -> None:
'''Initialize the model.'''
if infile is not None:
self.load(infile)
self.current = 0

self.loadDirectory(inputDirectory)
self.loadSettings(settingsFile)

def loadSettings(self, settingsPath: str) -> None:
self.settingsFile = settingsPath

if exists(settingsPath):
yaml = YAML(typ='safe')
with open(settingsPath, 'r') as f:
contents = yaml.load(f)

if 'albums' not in contents:
raise AssertionError('Settings file not correctly formatted!')
else:
contents = {'albums': {}}

self.outfile = outfile
self.albums = {}
for name, path in contents['albums'].items():
self.addAlbum(name, path)

def loadDirectory(self, dirname: str) -> None:
'''Load image list from a directory tree.'''
self.inputDir = dirname
self.inputFiles = list(listImageFiles(dirname))

def addAlbum(self, name: str, dirname: str) -> None:
if not isabs(dirname):
dirname = abspath(join(self.inputDir, dirname))
if not isdir(dirname):
os.makedirs(dirname)
self.albums[name] = dirname
self.save()

def removeAlbum(self, name: str) -> None:
# TODO: Do we clear out the directory when we remove the album?
if name in self.albums:
del self.albums[name]
self.save()

def isPicked(self, album: str, filename: str=None):
if album not in self.albums:
raise KeyError('No such album: {}'.format(album))

def isPicked(self, filename: str=None) -> bool:
'''Is the given file (or current file) picked?'''
if not filename:
filename = self.currentFile
return filename in self.picked
return islink(join(self.albums[album], basename(filename)))

def pick(self, filename: str=None) -> None:
def pick(self, album: str, filename: str=None) -> None:
'''Select the given (or current) file.'''
if not filename:
filename = self.currentFile
self.picked.add(filename)

def unpick(self, filename: str=None) -> None:
try:
albumPath = self.albums[album]
filePath = filename if isabs(filename) else abspath(join(self.inputDir, filename))
baseFileName = basename(filename)
os.symlink(filePath, join(albumPath, baseFileName))
except FileExistsError:
pass

def unpick(self, album: str, filename: str=None) -> None:
'''Un-select the given (or current) file.'''
if not filename:
filename = self.currentFile

baseFileName = basename(filename)
filePath = abspath(join(self.albums[album], baseFileName))
if not exists(filePath):
return

try:
self.picked.remove(filename)
except KeyError:
os.remove(filePath)
except OSError:
pass

def toggle(self, filename: str=None) -> None:
def toggle(self, album: str, filename: str=None) -> None:
'''Select or un-select the given (or current) file.'''
if not filename:
filename = self.currentFile
if filename in self.picked:
self.unpick(filename)
if self.isPicked(album, filename):
self.unpick(album, filename)
else:
self.pick(filename)

def load(self, filename: str) -> None:
'''Initialize our state from the given filename or directory.'''
self.infile = filename

if os.path.isdir(filename):
self._loadDir(filename)
else:
self._loadFile(filename)

self.picked = set()
self.current = 0

def _loadFile(self, filename: str) -> None:
'''Load image list from a YAML file.'''
yaml = YAML(typ='safe')
with open(filename, 'r') as f:
contents = yaml.load(f)

if 'images' not in contents:
raise AssertionError('Input file not properly formatted!')

self.files = contents['images']

def _loadDir(self, dirname: str) -> None:
'''Load image list from a directory tree.'''
self.files = list(listImageFiles(dirname))
self.pick(album, filename)

def save(self) -> None:
'''Write the image list to a YAML file.'''
if not self.outfile:
'''Write the album list to a YAML file.'''
if not self.settingsFile:
return

yaml = YAML()
with open(self.outfile, 'w') as f:
yaml.dump({'images': sorted(self.picked)}, f)
with open(self.settingsFile, 'w') as f:
yaml.dump({'albums': self.albums}, f)

@property
def albumNames(self) -> T.List[str]:
return sorted(self.albums.keys())

def _fullPath(self, filename: str) -> None:
if not isabs(filename):
filename = abspath(join(self.inputDir, filename))

return filename

@property
def currentFile(self) -> str:
'''Return the filename of the current file.'''
return self.files[self.current]
return self._fullPath(self.inputFiles[self.current])

@property
def nextFile(self) -> str:
'''What's the upcoming file?'''
return self.files[(self.current + 1) % len(self.files)]
return self._fullPath(self.inputFiles[(self.current + 1) % len(self.inputFiles)])

@property
def prevFile(self) -> str:
'''What's the last file?'''
return self.files[(self.current - 1) % len(self.files)]
return self._fullPath(self.inputFiles[(self.current - 1) % len(self.inputFiles)])

@property
def count(self) -> int:
'''How many files do we have in total?'''
return len(self.files)
return len(self.inputFiles)

@property
def pickedCount(self) -> int:
'''How many files have been picked?'''
return len(self.picked)
def albumCount(self, name) -> int:
albumPath = self.albums[name]
return len(os.listdir(albumPath))

def advance(self) -> None:
'''Step to the next file, wrapping around if we go over the end.'''
self.current += 1
if self.current >= len(self.files):
if self.current >= len(self.inputFiles):
self.current = 0

def retreat(self) -> None:
'''Step to previous file, wrapping around if we go past the start.'''
self.current -= 1
if self.current < 0:
self.current = len(self.files) - 1
self.current = len(self.inputFiles) - 1
Loading

0 comments on commit ff73a05

Please sign in to comment.