1
0
Fork 0
fgmeta/maintain_catalog.py

341 lines
10 KiB
Python
Raw Normal View History

2015-06-04 21:09:46 +00:00
#!/usr/bin/python
import os, sys, re, glob
import hashlib # for MD5
import subprocess
import catalogTags
import sgprops
import svn_catalog_repository
import git_catalog_repository
import git_discrete_repository
# TODO
# uploading / rsyncing
2015-06-04 21:09:46 +00:00
2015-07-15 02:37:39 +00:00
thumbnailNames = ["thumbnail.png", "thumbnail.jpg"]
2015-06-04 21:09:46 +00:00
class VariantData:
2015-07-15 02:37:39 +00:00
def __init__(self, path, node):
#self._primary = primary
2015-06-04 21:09:46 +00:00
self._path = path
self._name = node.getValue("sim/description")
2015-06-04 21:09:46 +00:00
# ratings
2015-06-04 21:09:46 +00:00
# seperate thumbnails
2015-06-04 21:09:46 +00:00
@property
def catalogNode(self):
n = Node("variant")
n.addChild("id").value = path
n.addChild("name").value = self._name
2015-06-04 21:09:46 +00:00
class PackageData:
def __init__(self, path):
2015-06-04 21:09:46 +00:00
self._path = path
self._previousSCMRevision = None
self._previousRevision = 0
self._thumbnails = []
self._variants = {}
self._revision = 0
self._md5 = None
self._fileSize = 0
self._node = sgprops.Node("package")
2015-06-04 21:09:46 +00:00
self._node.addChild("id").value = self.id
def setPreviousData(self, node):
2015-06-04 21:09:46 +00:00
self._previousRevision = node.getValue("revision")
self._previousMD5 = node.getValue("md5")
self._previousSCMRevision = node.getValue("scm-revision")
self._fileSize = int(node.getValue("file-size-bytes"))
2015-06-04 21:09:46 +00:00
@property
def id(self):
return os.path.basename(self._path)
2015-06-04 21:09:46 +00:00
@property
def thumbnails(self):
return self._thumbnails
@property
def path(self):
return self._path
2015-07-15 02:37:39 +00:00
@property
def variants(self):
return self._variants
@property
def scmRevision(self):
2015-06-04 21:09:46 +00:00
currentRev = scmRepo.scmRevisionForPath(self._path)
if (currentRev is None):
raise RuntimeError("Unable to query SCM revision of files")
return currentRev
def isSourceModified(self, scmRepo):
if (self._previousSCMRevision == None):
return True
if (self._previousSCMRevision == self.scmRevision):
2015-06-04 21:09:46 +00:00
return False
2015-06-04 21:09:46 +00:00
return True
2015-06-04 21:09:46 +00:00
def scanSetXmlFiles(self):
foundPrimary = False
2015-06-04 21:09:46 +00:00
for f in os.listdir(self._path):
if not f.endswith("-set.xml"):
2015-06-04 21:09:46 +00:00
continue
2015-06-04 21:09:46 +00:00
p = os.path.join(self._path, f)
2015-07-15 02:37:39 +00:00
node = sgprops.readProps(p)
2015-06-04 21:09:46 +00:00
simNode = node.getChild("sim")
2015-07-15 02:37:39 +00:00
if (simNode.getValue("exclude", False)):
2015-06-04 21:09:46 +00:00
continue
primary = simNode.getValue("variant-of", None)
if primary:
2015-07-15 02:37:39 +00:00
if not primary in self.variants:
2015-06-04 21:09:46 +00:00
self._variants[primary] = []
self._variants[primary].append(VariantData(self, node))
continue
2015-06-04 21:09:46 +00:00
if foundPrimary:
print "Multiple primary -set.xml files at:" + self._path
continue
else:
foundPrimary = True;
2015-07-15 02:37:39 +00:00
self.parsePrimarySetNode(simNode)
2015-07-15 02:37:39 +00:00
for n in thumbnailNames:
if os.path.exists(os.path.join(self._path, n)):
self._thumbnails.append(n)
2015-06-04 21:09:46 +00:00
if not foundPrimary:
raise RuntimeError("No primary -set.xml found at:" + self._path)
2015-06-04 21:09:46 +00:00
def parsePrimarySetNode(self, sim):
2015-06-04 21:09:46 +00:00
# basic / mandatory values
self._node.addChild('name').value = sim.getValue('description')
2015-06-04 21:09:46 +00:00
longDesc = sim.getValue('long-description')
if longDesc is not None:
self._node.addChild('description').value = longDesc
2015-06-04 21:09:46 +00:00
# copy all the standard values
for p in ['status', 'author', 'license']:
v = sim.getValue(p)
if v is not None:
self._node.addChild(p).value = v
2015-06-04 21:09:46 +00:00
# ratings
if sim.hasChild('rating'):
pkgRatings = self._node.addChild('rating')
for r in ['FDM', 'systems', 'cockpit', 'model']:
pkgRatings.addChild(r).value = sim.getValue('rating/' + r, 0)
2015-06-04 21:09:46 +00:00
# copy tags
if sim.hasChild('tags'):
for c in sim.getChild('tags').getChildren('tag'):
if isNonstandardTag(c.value):
print "Skipping non-standard tag:", c.value
else:
self._node.addChild('tag').value = c.value
2015-07-15 02:37:39 +00:00
self._thumbnails.append(t.value for t in sim.getChildren("thumbnail"))
2015-06-04 21:09:46 +00:00
def validate(self):
for t in self._thumbnails:
if not os.path.exists(os.path.join(self._path, t)):
raise RuntimeError("missing thumbnail:" + t);
2015-06-04 21:09:46 +00:00
def generateZip(self, outDir):
self._revision = self._previousRevision + 1
zipName = self.id + ".zip"
2015-06-04 21:09:46 +00:00
zipFilePath = os.path.join(outDir, zipName)
os.chdir(os.path.dirname(self.path))
print "Creating zip", zipFilePath
2015-06-04 21:09:46 +00:00
# TODO: exclude certain files
# anything we can do to make this faster?
subprocess.call(['zip', '--quiet', '-r', zipFilePath, self.id])
zipFile = open(zipFilePath, 'r')
2015-06-04 21:09:46 +00:00
self._md5 = hashlib.md5(zipFile.read()).hexdigest()
self._fileSize = os.path.getsize(zipFilePath)
def useExistingCatalogData(self):
self._md5 = self._previousMD5
def packageNode(self, mirrorUrls, thumbnailUrl):
2015-06-04 21:09:46 +00:00
self._node.getChild("md5", create = True).value = self._md5
self._node.getChild("file-size-bytes", create = True).value = self._fileSize
self._node.getChild("revision", create = True).value = int(self._revision)
self._node.getChild("scm-revision", create = True).value = self.scmRevision
2015-06-04 21:09:46 +00:00
for m in mirrorUrls:
self._node.addChild("url").value = m + "/" + self.id + ".zip"
2015-06-04 21:09:46 +00:00
for t in self._thumbnails:
self._node.addChild("thumbnail").value = thumbnailUrl + "/" + self.id + "_" + t
2015-06-04 21:09:46 +00:00
for pr in self._variants:
for vr in self._variants[pr]:
self._node.addChild(vr.catalogNode)
2015-06-04 21:09:46 +00:00
return self._node
def extractThumbnails(self, thumbnailDir):
2015-06-04 21:09:46 +00:00
for t in self._thumbnails:
fullName = self.id + "_" + t
os.file.copy(os.path.join(self._path, t),
os.path.join(thumbnailDir, fullName)
)
# TODO : verify image format, size and so on
2015-06-04 21:09:46 +00:00
def scanPackages(globPath):
result = []
print "Scanning", globPath
print os.getcwd()
for d in glob.glob(globPath):
2015-06-04 21:09:46 +00:00
result.append(PackageData(d))
2015-06-04 21:09:46 +00:00
return result
def initScmRepository(node):
scmType = node.getValue("type")
if (scmType == "svn"):
svnPath = node.getValue("path")
return svn_catalog_repository.SVNCatalogRepository(svnPath)
elif (scmType == "git"):
2015-06-04 21:09:46 +00:00
gitPath = node.getValue("path")
usesSubmodules = node.getValue("uses-submodules", False)
return git_catalog_repository.GitCatalogRepository(gitPath, usesSubmodules)
elif (scmType == "git-discrete"):
return git_discrete_repository.GitDiscreteSCM(node)
elif (scmType == None):
2015-06-04 21:09:46 +00:00
raise RuntimeError("No scm/type defined in catalog configuration")
else:
raise RuntimeError("Unspported SCM type:" + scmType)
2015-06-04 21:09:46 +00:00
def processUpload(node, outputPath):
print "Enabled value is:", node.getValue("enabled")
if not node.getValue("enabled", True):
print "Upload disabled"
return
2015-06-04 21:09:46 +00:00
uploadType = node.getValue("type")
if (uploadType == "rsync"):
subprocess.call(["rsync", node.getValue("args", "-az"), ".",
2015-06-04 21:09:46 +00:00
node.getValue("remote")],
cwd = outputPath)
elif (uploadType == "scp"):
2015-06-04 21:09:46 +00:00
subprocess.call(["scp", node.getValue("args", "-r"), outputPath,
node.getValue("remote")])
else:
raise RuntimeError("Unsupported upload type:" + uploadType)
# dictionary
2015-06-04 21:09:46 +00:00
packages = {}
if len(sys.argv) < 2:
raise RuntimeError("no root dir specified")
2015-06-04 21:09:46 +00:00
rootDir = sys.argv[1]
if not os.path.isabs(rootDir):
rootDir = os.path.abspath(rootDir)
os.chdir(rootDir)
print "Root path is:", rootDir
2015-06-04 21:09:46 +00:00
configPath = 'catalog.config.xml'
if not os.path.exists(configPath):
2015-06-04 21:09:46 +00:00
raise RuntimeError("no config file found at:" + configPath)
config = sgprops.readProps(configPath)
2015-06-04 21:09:46 +00:00
# out path
outPath = config.getValue('output-dir')
if outPath is None:
# default out path
outPath = os.path.join(rootDir, "output")
elif not os.path.isabs(outPath):
outPath = os.path.join(rootDir, "output")
if not os.path.exists(outPath):
os.mkdir(outPath)
2015-06-04 21:09:46 +00:00
print "Output path is:" + outPath
thumbnailPath = os.path.join(outPath, config.getValue('thumbnail-dir', "thumbnails"))
thumbnailUrl = config.getValue('thumbnail-url')
print "Thumbnail url is:", thumbnailUrl
mirrorUrls = []
2015-06-04 21:09:46 +00:00
# contains existing catalog
existingCatalogPath = os.path.join(outPath, 'catalog.xml')
scmRepo = initScmRepository(config.getChild('scm'))
# scan the directories in the aircraft paths
for g in config.getChildren("aircraft-dir"):
for p in scanPackages(g.value):
2015-06-04 21:09:46 +00:00
packages[p.id] = p
if os.path.exists(existingCatalogPath):
try:
previousCatalog = sgprops.readProps(existingCatalogPath)
except:
print "Previous catalog is malformed"
previousCatalog = sgprops.Node()
for p in previousCatalog.getChildren("package"):
pkgId = p.getValue("id")
if not pkgId in packages.keys():
print "Orphaned old package:", pkgId
continue
2015-06-04 21:09:46 +00:00
packages[pkgId].setPreviousData(p)
else:
print "No previous catalog"
2015-06-04 21:09:46 +00:00
catalogNode = sgprops.Node("catalog")
2015-06-04 21:09:46 +00:00
sgprops.copy(config.getChild("template"), catalogNode)
mirrorUrls = (m.value for m in config.getChildren("mirror"))
2015-06-04 21:09:46 +00:00
packagesToGenerate = []
for p in packages.values():
2015-07-15 02:37:39 +00:00
p.scanSetXmlFiles()
2015-06-04 21:09:46 +00:00
if (p.isSourceModified(scmRepo)):
packagesToGenerate.append(p)
else:
p.useExistingCatalogData()
2015-06-04 21:09:46 +00:00
for p in packagesToGenerate:
p.generateZip(outPath)
p.extractThumbnails(thumbnailPath)
print "Creating catalog"
for p in packages.values():
catalogNode.addChild(p.packageNode(mirrorUrls, thumbnailUrl))
catalogNode.write(os.path.join(outPath, "catalog.xml"))
print "Uploading"
2015-06-04 21:09:46 +00:00
if config.hasChild("upload"):
processUpload(config.getChild("upload"), outPath)