# debpartial_mirror - partial debian mirror package tool
# (c) 2004 Otavio Salvador <otavio@debian.org>, Marco Presi <zufus@debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	02111-1307	USA

import os
import sys

import apt_pkg

from cdd import FileSystem

from debpartial_mirror import Download


class Pool:
	"""
	This class provides methods to manage pool dirs into the
	partial-mirror.
	"""
	def __init__(self, backend):
		self._backend = backend
		self._got_files = []
		self._containsForeignPackages = False
		self._filesystem = FileSystem.FileSystem(
			backend["mirror_dir"],
			backend["name"]
		)

	def _process_package(self, pkg):
		"""
		This method is called for each package and should be override
		in each child class.
		"""
		raise NotImplemented

	def _calc_need_files(self):
		"""
		This method is called to build the list of need files to
		permit to us to clean up the mirror. If you need to handle in
		a special way to calc it, you're free to override this method.
		"""
		if not len(self._got_files):
			self._backend.process()
			need_files = []
			for architecture in self._backend["architectures"]:
				need_files.extend([pkg['Filename'] for pkg in self._backend.get_binary_list(architecture).values()])
		else:
			need_files = self._got_files

		return need_files

	def _link_internal(self, pkg, mirror):
		filename = pkg['Filename']
		self._filesystem.mkdir(os.path.dirname(filename))
		src = self._backend["mirror_dir"] + "/" + mirror["name"] + "/" + filename
		dst = os.path.join(self._filesystem.base(), filename)
		if not os.path.exists(dst):
			try:
				print "Linking [%s] %s" % (mirror["name"],  filename)
				os.link (src, dst)
			except OSError, msg:
				print "Error while linking a file:"
				print " [%s] %s" % (mirror["name"], filename)
				print " System message: %s" % msg
				sys.exit(1) #TODO: fixme
		self._got_files.append(filename)

	def upgrade(self):
		"""
		Manage the pool upgrade process
		TODO: Handle source packages.
		"""
		def processPackage(pkg, architecture):
			foundList =  self._backend.get_binary_list(architecture).whereis(pkg["Package"])
			if foundList in  (self._backend.get_binary_list(architecture), self._backend.get_full_binary_list(architecture)):
				self._process_package(pkg)
			else: # required dependency from another mirror
				for backend in self._backend.backends:
					if backend.get_full_binary_list(architecture) == foundList:
						break
				else:
					raise RuntimeError("No backend found for %s", pkg["Package"])

				self._link_internal(pkg, backend)
				self._containsForeignPackages = True

		for architecture in self._backend["architectures"]:
			for pkg in self._backend.get_binary_list(architecture).values():
				filename = pkg['Filename']
				if not self._filesystem.exists(filename):
					processPackage(pkg, architecture)
				elif self._filesystem.md5sum(filename) == pkg['MD5sum']:
					self._got_files.append(filename)
				else:
					print 'Removing corrupted package', pkg['Package']
					self._filesystem.remove(filename)
					processPackage(pkg, architecture)


	def clean (self):
		"""
		Clean old files
		"""
		need_files = self._calc_need_files()

		# Walk in all debian related files of mirror
		for current in self._filesystem.match("^.+\.(dsc|(diff|tar)\.gz|deb|udeb)$"):
			if current not in need_files:
				print "Removing unneeded file: %s" % current
				self._filesystem.remove(current)
				try:
					self._filesystem.rmdir(os.path.dirname(current), True)
				except OSError:
					pass

	def containsForeignPackages(self):
		return self._containsForeignPackages


class RemotePool (Pool):
	"""
	This class provides methods to fill pool dir downloading remote files
	"""
	def __init__ (self, backend):
		Pool.__init__(self, backend) # Call parent constructor

		self.download = Download.Download(name = "Pool_" + backend["name"]) # Our Download manager

	def _process_package (self, pkg):
		self._filesystem.mkdir(os.path.dirname(pkg['Filename']))

		self.download.get(
			os.path.join(self._backend["server"], pkg['Filename']),
			os.path.join(self._filesystem.base(), pkg['Filename'])
		)
		self._got_files.append(pkg['Filename'])

	def upgrade (self):
		Pool.upgrade(self) # Call parent method
		self.download.wait_mine() # Wait until our downloads finish


class LocalPool (Pool):
	"""
	This class provides methods to fill pool dir linking from local files
	"""
	def _process_package (self):
		self._filesystem.mkdir(os.path.dirname(pkg['Filename']))
		os.link(
			self._backend["server"].split('file://')[1] + pkg,
			self._local + pkg
		)
		self._got_files.append(pkg['Filename'])


class MergePool (Pool):
	"""
	This class provides methods to fill pool dir mergin other pools
	"""

	def _calc_need_files(self):
		need_files = []
		for architecture in self._backend["architectures"]:
			for mirror in self._backend.get_mirrors():
				for pkg in self._backend.get_packages_for_mirror(mirror, architecture).values():
					need_files.append(pkg['Filename'])
		return need_files

	def merge (self):
		for architecture in self._backend["architectures"]:
			for mirror in self._backend.get_mirrors():
				for pkg in self._backend.get_packages_for_mirror(mirror, architecture).values():
					self._link_internal(pkg, mirror)

