2 # -*- coding: utf-8 -*-
7 Copyright (c) 2012 Frédéric Massart - FMCorz.net
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 http://github.com/FMCorz/mdk
28 from .tools
import mkdir
, process
, stableBranch
29 from .exceptions
import CreateException
30 from .config
import Conf
37 class Workplace(object):
39 """The name of the directory that contains the PHP files"""
42 """The name of the directory that contains Moodle data"""
45 """The name of the directory that contains extra files"""
48 """The name of the directory that makes extraDir web accessible, see getMdkWebDir"""
51 """The path to the storage directory"""
54 """The path to MDK cache"""
57 """The path to the web accessible directory"""
60 def __init__(self
, path
=None, wwwDir
=None, dataDir
=None, extraDir
=None, mdkDir
=None):
62 path
= C
.get('dirs.storage')
64 wwwDir
= C
.get('wwwDir')
66 dataDir
= C
.get('dataDir')
68 extraDir
= C
.get('extraDir')
70 mdkDir
= C
.get('mdkDir')
73 self
.path
= os
.path
.abspath(os
.path
.realpath(os
.path
.expanduser(path
)))
74 self
.cache
= os
.path
.abspath(os
.path
.realpath(os
.path
.expanduser(C
.get('dirs.mdk'))))
75 self
.www
= os
.path
.abspath(os
.path
.realpath(os
.path
.expanduser(C
.get('dirs.www'))))
77 if not os
.path
.isdir(self
.path
):
78 raise Exception('Directory %s not found' % self
.path
)
82 self
.dataDir
= dataDir
83 self
.extraDir
= extraDir
86 def checkCachedClones(self
, stable
=True, integration
=True):
87 """Clone the official repository in a local cache"""
88 cacheStable
= self
.getCachedRemote(False)
89 cacheIntegration
= self
.getCachedRemote(True)
91 if not os
.path
.isdir(cacheStable
) and stable
:
92 logging
.info('Cloning stable repository into cache...')
94 # For faster clone, we will copy the integration clone if it exists.
95 if os
.path
.isdir(cacheIntegration
):
96 shutil
.copytree(cacheIntegration
, cacheStable
)
97 repo
= git
.Git(cacheStable
, C
.get('git'))
98 repo
.setRemote('origin', C
.get('remotes.stable'))
99 # The repository is not updated at this stage, it has to be done manually.
101 logging
.info('This is going to take a while...')
102 process('%s clone --mirror %s %s' %
(C
.get('git'), C
.get('remotes.stable'), cacheStable
))
104 if not os
.path
.isdir(cacheIntegration
) and integration
:
105 logging
.info('Cloning integration repository into cache...')
107 # For faster clone, we will copy the integration clone if it exists.
108 if os
.path
.isdir(cacheStable
):
109 shutil
.copytree(cacheStable
, cacheIntegration
)
110 repo
= git
.Git(cacheIntegration
, C
.get('git'))
111 repo
.setRemote('origin', C
.get('remotes.integration'))
112 # The repository is not updated at this stage, it has to be done manually.
114 logging
.info('Have a break, this operation is slow...')
115 process('%s clone --mirror %s %s' %
(C
.get('git'), C
.get('remotes.integration'), cacheIntegration
))
117 def create(self
, name
=None, version
='master', purpose
='stable', engine
=C
.get('defaultEngine'), useCacheAsRemote
=False):
118 """Creates a new instance of Moodle.
119 The parameter useCacheAsRemote has been deprecated.
124 name
= self
.generateInstanceName(version
, purpose
=purpose
)
126 if name
== self
.mdkDir
:
127 raise Exception('A Moodle instance cannot be called \'%s\', this is a reserved word.' % self
.mdkDir
)
129 if purpose
== 'integration':
132 installDir
= self
.getPath(name
)
133 wwwDir
= self
.getPath(name
, 'www')
134 dataDir
= self
.getPath(name
, 'data')
135 extraDir
= self
.getPath(name
, 'extra')
136 linkDir
= os
.path
.join(self
.www
, name
)
137 extraLinkDir
= os
.path
.join(self
.getMdkWebDir(), name
)
139 if self
.isMoodle(name
):
140 raise CreateException('The Moodle instance %s already exists' % name
)
141 elif os
.path
.isdir(installDir
):
142 raise CreateException('Installation path exists: %s' % installDir
)
144 self
.checkCachedClones(not integration
, integration
)
145 self
.updateCachedClones(stable
=not integration
, integration
=integration
, verbose
=False)
146 mkdir(installDir
, 0755)
149 mkdir(extraDir
, 0777)
151 repository
= self
.getCachedRemote(integration
)
153 # Clone the instances
154 logging
.info('Cloning repository...')
155 process('%s clone %s %s' %
(C
.get('git'), repository
, wwwDir
))
158 if os
.path
.islink(linkDir
):
160 if os
.path
.isfile(linkDir
) or os
.path
.isdir(linkDir
): # No elif!
161 logging
.warning('Could not create symbolic link. Please manually create: ln -s %s %s' %
(wwwDir
, linkDir
))
163 os
.symlink(wwwDir
, linkDir
)
166 if os
.path
.isfile(extraLinkDir
) or os
.path
.isdir(extraLinkDir
):
167 logging
.warning('Could not create symbolic link. Please manually create: ln -s %s %s' %
(extraDir
, extraLinkDir
))
169 os
.symlink(extraDir
, extraLinkDir
)
171 # Symlink to dataDir in wwwDir
172 if type(C
.get('symlinkToData')) == str:
173 linkDataDir
= os
.path
.join(wwwDir
, C
.get('symlinkToData'))
174 if not os
.path
.isfile(linkDataDir
) and not os
.path
.isdir(linkDataDir
) and not os
.path
.islink(linkDataDir
):
175 os
.symlink(dataDir
, linkDataDir
)
177 logging
.info('Checking out branch...')
178 repo
= git
.Git(wwwDir
, C
.get('git'))
180 # Removing the default remote origin coming from the clone
181 repo
.delRemote('origin')
183 # Setting up the correct remote names
184 repo
.setRemote(C
.get('myRemote'), C
.get('remotes.mine'))
185 repo
.setRemote(C
.get('upstreamRemote'), repository
)
187 # Creating, fetch, pulling branches
188 repo
.fetch(C
.get('upstreamRemote'))
189 branch
= stableBranch(version
)
190 track
= '%s/%s' %
(C
.get('upstreamRemote'), branch
)
191 if not repo
.hasBranch(branch
) and not repo
.createBranch(branch
, track
):
192 logging
.error('Could not create branch %s tracking %s' %
(branch
, track
))
194 repo
.checkout(branch
)
195 repo
.pull(remote
=C
.get('upstreamRemote'))
197 # Fixing up remote URLs if need be, this is done after pulling the cache one because we
198 # do not want to contact the real origin server from here, it is slow and pointless.
199 if not C
.get('useCacheAsUpstreamRemote'):
200 realupstream
= C
.get('remotes.integration') if integration
else C
.get('remotes.stable')
202 repo
.setRemote(C
.get('upstreamRemote'), realupstream
)
207 def delete(self
, name
):
208 """Completely remove an instance, database included"""
210 # Instantiating the object also checks if it exists
213 # Deleting the whole thing
214 shutil
.rmtree(os
.path
.join(self
.path
, name
))
216 # Deleting the possible symlink
217 link
= os
.path
.join(self
.www
, name
)
218 if os
.path
.islink(link
):
224 # Delete the extra dir symlink
225 link
= os
.path
.join(self
.getMdkWebDir(), name
)
226 if os
.path
.islink(link
):
234 dbname
= M
.get('dbname')
235 if DB
and dbname
and DB
.dbexists(dbname
):
238 def generateInstanceName(self
, version
, engine
=C
.get('defaultEngine'), purpose
='stable', suffix
='', identifier
=None):
239 """Creates a name (identifier) from arguments"""
241 if identifier
!= None:
242 # If an identifier is passed, we use it regardless of the other parameters.
244 name
= identifier
.replace(' ', '_')
247 if version
== 'master':
248 prefixVersion
= C
.get('wording.prefixMaster')
250 prefixVersion
= version
253 sep
= C
.get('wording.prefixSeparator');
254 if purpose
== 'integration':
255 name
= C
.get('wording.prefixIntegration') + sep
+ prefixVersion
257 if purpose
== 'review':
258 name
= C
.get('wording.prefixReview') + sep
+ prefixVersion
260 if purpose
== 'stable':
261 name
= C
.get('wording.prefixStable') + sep
+ prefixVersion
263 if C
.get('wording.appendEngine'):
267 if suffix
!= None and suffix
!= '':
268 name
+= C
.get('wording.suffixSeparator') + suffix
273 """Returns an instance defined by its name, or by path"""
274 # Extracts name from path
276 path
= os
.path
.abspath(os
.path
.realpath(name
))
277 if not path
.startswith(self
.path
):
278 raise Exception('Could not find Moodle instance at %s' % name
)
279 (head
, name
) = os
.path
.split(path
)
281 if not self
.isMoodle(name
):
282 raise Exception('Could not find Moodle instance %s' % name
)
283 return moodle
.Moodle(os
.path
.join(self
.path
, name
, self
.wwwDir
), identifier
=name
)
285 def getCachedRemote(self
, integration
=False):
286 """Return the path to the cached remote"""
288 return os
.path
.join(self
.cache
, 'integration.git')
290 return os
.path
.join(self
.cache
, 'moodle.git')
292 def getExtraDir(self
, name
, subdir
=None):
293 """Return the path to the extra directory of an instance
295 This also creates the directory if does not exist.
297 path
= self
.getPath(name
, 'extra')
299 path
= os
.path
.join(path
, subdir
)
300 if not os
.path
.exists(path
):
304 def getMdkWebDir(self
):
305 """Return (and create) the special MDK web directory."""
306 mdkExtra
= os
.path
.join(self
.www
, self
.mdkDir
)
307 if not os
.path
.exists(mdkExtra
):
308 mkdir(mdkExtra
, 0777)
312 def getPath(self
, name
, mode
=None):
313 """Returns the path of an instance base on its name"""
314 base
= os
.path
.join(self
.path
, name
)
316 return os
.path
.join(base
, self
.wwwDir
)
318 return os
.path
.join(base
, self
.dataDir
)
319 elif mode
== 'extra':
320 return os
.path
.join(base
, self
.extraDir
)
324 def getUrl(self
, name
, extra
=None):
325 """Return the URL to an instance, or to its extra directory if extra is passed"""
326 base
= '%s://%s' %
(C
.get('scheme'), C
.get('host'))
328 if C
.get('path') != '' and C
.get('path') != None:
329 base
= '%s/%s' %
(base
, C
.get('path'))
333 wwwroot
= '%s/%s' %
(base
, name
)
335 wwwroot
= '%s/%s/%s/%s' %
(base
, self
.mdkDir
, name
, extra
)
339 def isMoodle(self
, name
):
340 """Checks whether a Moodle instance exist under this name"""
341 d
= os
.path
.join(self
.path
, name
)
342 if not os
.path
.isdir(d
):
345 wwwDir
= os
.path
.join(d
, self
.wwwDir
)
346 dataDir
= os
.path
.join(d
, self
.dataDir
)
347 if not os
.path
.isdir(wwwDir
) or not os
.path
.isdir(dataDir
):
350 if not moodle
.Moodle
.isInstance(wwwDir
):
355 def list(self
, integration
=None, stable
=None):
356 """Return the list of Moodle instances"""
357 dirs
= os
.listdir(self
.path
)
360 if d
== '.' or d
== '..': continue
361 if not os
.path
.isdir(os
.path
.join(self
.path
, d
)): continue
362 if not self
.isMoodle(d
): continue
363 if integration
!= None or stable
!= None:
365 if not integration
and M
.isIntegration(): continue
366 if not stable
and M
.isStable(): continue
370 def resolve(self
, name
=None, path
=None):
371 """Try to find a Moodle instance based on its name, a path or the working directory"""
373 # A name was passed, is that a valid instance?
375 if self
.isMoodle(name
):
376 return self
.get(name
)
379 # If a path was not passed, let's use the current working directory.
382 path
= os
.path
.realpath(os
.path
.abspath(path
))
384 # Is this path in a Moodle instance?
385 if path
.startswith(self
.path
):
387 # Get the relative path identifier/some/other/path
388 relative
= os
.path
.relpath(path
, self
.path
)
390 # Isolating the identifier, it should be the first directory
391 (head
, tail
) = os
.path
.split(relative
)
393 (head
, tail
) = os
.path
.split(head
)
395 if self
.isMoodle(tail
):
396 return self
.get(tail
)
400 def resolveMultiple(self
, names
=[]):
401 """Return multiple instances"""
402 if type(names
) != list:
403 if type(names
) == str:
406 raise Exception('Unexpected variable type')
408 # Nothing has been passed, we use resolve()
416 # Try to resolve each instance
419 M
= self
.resolve(name
=name
)
423 logging
.info('Could not find instance called %s' % name
)
426 def updateCachedClones(self
, integration
=True, stable
=True, verbose
=True):
427 """Update the cached clone of the repositories"""
432 caches
.append(os
.path
.join(self
.cache
, 'integration.git'))
434 caches
.append(os
.path
.join(self
.cache
, 'moodle.git'))
437 if not os
.path
.isdir(cache
):
440 repo
= git
.Git(cache
, C
.get('git'))
443 logging
.info('Fetching cached repository %s...', os
.path
.basename(cache
))
445 logging
.debug('Fetching cached repository %s...', os
.path
.basename(cache
))
447 raise Exception('Could not fetch in repository %s' %
(cache
))