Handle home directories with non-ASCII characters correctly. Ticket
[joel/kofoto.git] / src / lib / kofoto / clientenvironment.py
1 """Client environment module for Kofoto."""
2
3 __all__ = [
4     "BadConfigFileError",
5     "ClientEnvironment",
6     "ClientEnvironmentError",
7     "ConfigFileError",
8     "MissingConfigFileError",
9     "MissingShelfError",
10 ]
11
12 import locale
13 import os
14 import sys
15 from kofoto.config import *
16 from kofoto.shelf import Shelf, FailedWritingError
17 from kofoto.imagecache import ImageCache
18 from kofoto.version import version as kofotoVersion
19
20 ######################################################################
21 # Public classes.
22
23 class ClientEnvironmentError(Exception):
24     pass
25
26 class ConfigFileError(ClientEnvironmentError):
27     pass
28
29 class MissingConfigFileError(ConfigFileError):
30     pass
31
32 class BadConfigFileError(ConfigFileError):
33     pass
34
35 class ShelfError(ClientEnvironmentError):
36     pass
37
38 class MissingShelfError(ShelfError):
39     pass
40
41 class BadShelfError(ShelfError):
42     pass
43
44 class ClientEnvironment(object):
45     def __init__(self, localCodeset=None):
46         """Initialize the client environment instance.
47
48         If localCodeset is None, the preferred character set encoding
49         is read from the environment and used as the local codeset.
50         Otherwise, localCodeset is used."""
51
52         if localCodeset == None:
53             locale.setlocale(locale.LC_ALL, "")
54             self.__codeset = locale.getpreferredencoding()
55         else:
56             self.__codeset = localCodeset
57
58     def setup(self, configFileLocation=None, shelfLocation=None,
59               createMissingConfigFile=True, createMissingShelf=True):
60         """Set up the environment.
61
62         If configFileLocation is None, a per-system default value is
63         used.
64
65         If shelfLocation is None, a per-system default value is used.
66
67         A missing configuration file will be created iff
68         createMissingConfigFile is true.
69
70         A missing shelf will be created iff createMissingShelf is true.
71         """
72
73         if configFileLocation == None:
74             if sys.platform.startswith("win"):
75                 self.__configFileLocation = os.path.expanduser(
76                     os.path.join("~", "KofotoData", "config.ini"))
77             else:
78                 self.__configFileLocation = os.path.expanduser(
79                     os.path.join("~", ".kofoto", "config"))
80         else:
81             self.__configFileLocation = configFileLocation
82
83         if not os.path.exists(self.configFileLocation):
84             confdir = os.path.dirname(self.configFileLocation)
85             if confdir and not os.path.exists(confdir):
86                 os.mkdir(confdir)
87                 self._writeInfo("Created directory \"%s\".\n" % confdir)
88             if createMissingConfigFile:
89                 createConfigTemplate(self.configFileLocation)
90                 self._writeInfo("Created configuration file \"%s\".\n" %
91                                 self.configFileLocation)
92             else:
93                 raise MissingConfigFileError, \
94                     ("Missing configuration file: \"%s\"\n" %
95                          self.configFileLocation,
96                      self.configFileLocation)
97         self.__config = Config(self.configFileLocation, self.codeset)
98
99         try:
100             self.config.read()
101             self.config.verify()
102         except MissingSectionHeaderError, x:
103             raise BadConfigFileError, \
104                   ("Bad configuration (missing section headers).\n",
105                    self.configFileLocation)
106         except MissingConfigurationKeyError, (section, key):
107             raise BadConfigFileError, \
108                   ("Missing configuration key in %s section: %s.\n" % (
109                        section, key),
110                    self.configFileLocation)
111         except BadConfigurationValueError, (section, key, value):
112             raise BadConfigFileError, \
113                   ("Bad configuration value for %s in %s section: %s.\n" % (
114                     key, section, value),
115                    self.configFileLocation)
116
117         if shelfLocation == None:
118             self.__shelfLocation = \
119                 os.path.expanduser(
120                     self.unicodeToLocalizedString(
121                         self.config.get("database", "location")))
122         else:
123             self.__shelfLocation = shelfLocation
124
125         self.__shelf = Shelf(self.shelfLocation, self.codeset)
126
127         if not os.path.exists(self.shelfLocation):
128             if createMissingShelf:
129                 try:
130                     self.shelf.create()
131                 except FailedWritingError, self.shelfLocation:
132                     raise BadShelfError, \
133                         ("Could not create metadata database \"%s\".\n" % (
134                              self.shelfLocation),
135                          self.shelfLocation)
136                 self._writeInfo("Created metadata database \"%s\".\n" % self.shelfLocation)
137             else:
138                 raise MissingShelfError, \
139                     ("Could not open metadata database \"%s\"" % self.shelfLocation,
140                      self.shelfLocation)
141
142         self.__imageCache = ImageCache(
143             os.path.expanduser(
144                 self.unicodeToLocalizedString(
145                     self.config.get("image cache", "location"))),
146             self.config.getboolean("image cache", "use_orientation_attribute"))
147
148     def getCodeset(self):
149         return self.__codeset
150     codeset = property(getCodeset)
151
152     def getConfig(self):
153         return self.__config
154     config = property(getConfig)
155
156     def getConfigFileLocation(self):
157         return self.__configFileLocation
158     configFileLocation = property(getConfigFileLocation)
159
160     def getShelf(self):
161         return self.__shelf
162     shelf = property(getShelf)
163
164     def getShelfLocation(self):
165         return self.__shelfLocation
166     shelfLocation = property(getShelfLocation)
167
168     def getImageCache(self):
169         return self.__imageCache
170     imageCache = property(getImageCache)
171
172     def getVersion(self):
173         return kofotoVersion
174     version = property(getVersion)
175
176     def unicodeToLocalizedString(self, unicodeString):
177         """If unicodeString is a Unicode string, convert it to a
178         localized string. Otherwise, unicodeString is returned without
179         any conversion."""
180         if isinstance(unicodeString, unicode):
181             return unicodeString.encode(self.codeset)
182         else:
183             return unicodeString
184
185     def localizedStringToUnicode(self, localizedString):
186         """Convert a localized string to a Unicode string."""
187         return localizedString.decode(self.codeset)
188
189     def _writeInfo(self, infoString):
190         """Default implementation: Write to standard output."""
191         sys.stdout.write(self.unicodeToLocalizedString(infoString))
192         sys.stdout.flush()