#! /usr/bin/env python
#
# Copyright 2009-2011 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the
# OpenSSL library under certain conditions as described in each
# individual source file, and distribute linked combinations
# including the two.
# You must obey the GNU General Public License in all respects
# for all of the code used other than OpenSSL.  If you modify
# file(s) with this exception, you may extend this exception to your
# version of the file(s), but you are not obligated to do so.  If you
# do not wish to do so, delete this exception statement from your
# version.  If you delete this exception statement from all source
# files in the program, then also delete it here.
"""A command-line utility to interact with ubuntuone-syncdaemon."""

import sys

if sys.platform != 'win32':
    try:
        from twisted.internet import gireactor
        gireactor.install()
    except ImportError:
        from twisted.internet import glib2reactor
        glib2reactor.install()

    import dbus
    from dbus.mainloop.glib import DBusGMainLoop
    loop = DBusGMainLoop(set_as_default=True)
    bus = dbus.SessionBus(mainloop=loop)
else:
    bus = None

import codecs
import os
import warnings

from optparse import OptionParser
from twisted.internet import reactor, defer

# make configglue be quiet about itself using deprecated stuff
warnings.filterwarnings('ignore', message='.*Config(Option|Section).*',
                        category=DeprecationWarning)

from ubuntuone.platform.tools import (
    SyncDaemonTool,
    is_already_running,
    show_dirty_nodes,
    show_downloads,
    show_error,
    show_folders,
    show_free_space,
    show_path_info,
    show_public_file_info,
    show_shared,
    show_shares,
    show_state,
    show_uploads,
    show_waiting,
    show_waiting_content,
    show_waiting_metadata,
)
from ubuntuone.clientdefs import VERSION


@defer.inlineCallbacks
def main(options, args, stdout):
    """Entry point."""
    sync_daemon_tool = SyncDaemonTool(bus)

    # get the encoding of the output stream, defaults to UTF-8
    out_encoding = getattr(stdout, 'encoding', 'utf-8')
    if out_encoding is None:
        out_encoding = 'utf-8'
    out = codecs.getwriter(out_encoding)(stdout, errors='replace')
    # start syncdaemon if it's required
    running = yield is_already_running()
    should_start = not running and not options.quit and not options.start

    try:
        if should_start:
            yield sync_daemon_tool.start()
        yield run(options, sync_daemon_tool, out)
    except Exception, e:
        out.write("\nOops, an error ocurred:\n%s\n" % e)
    finally:
        if reactor.running:
            reactor.stop()


def run(options, sync_daemon_tool, out):
    if options.wait:
        def callback(result):
            """ wait_for_nirvana callback (stop the reactor and exit)"""
            out.write("\nubuntuone-syncdaemon became a fully "
                      "enlightened Buddha!\n")

        d = sync_daemon_tool.wait_for_nirvana(verbose=True)
        d.addCallbacks(callback)
    elif options.list_shares:
        d = sync_daemon_tool.get_shares()
        d.addCallback(lambda r: show_shares(r, out))
    elif options.accept_share:
        d = sync_daemon_tool.accept_share(options.accept_share)
    elif options.reject_share:
        d = sync_daemon_tool.reject_share(options.reject_share)
    elif options.subscribe_share:
        d = sync_daemon_tool.subscribe_share(options.subscribe_share)
        d.addErrback(lambda r: show_error(r, out))
    elif options.unsubscribe_share:
        d = sync_daemon_tool.unsubscribe_share(options.unsubscribe_share)
        d.addErrback(lambda r: show_error(r, out))
    elif options.refresh_shares:
        d = sync_daemon_tool.refresh_shares()
    elif options.refresh_volumes:
        d = sync_daemon_tool.refresh_volumes()
    elif options.offer_share:
        path, username, name, access_level = options.offer_share
        path = os.path.abspath(path)
        d = sync_daemon_tool.offer_share(path, username, name, access_level)
    elif options.list_shared:
        d = sync_daemon_tool.list_shared()
        d.addCallback(lambda r: show_shared(r, out))
    elif options.create_folder:
        path = os.path.abspath(options.create_folder)
        if not os.path.exists(path):
            parser.error("PATH: '%s' don't exists" % path)
        d = sync_daemon_tool.create_folder(path)
        d.addErrback(lambda r: show_error(r, out))
    elif options.delete_folder:
        d = sync_daemon_tool.delete_folder(options.delete_folder)
        d.addErrback(lambda r: show_error(r, out))
    elif options.list_folders:
        d = sync_daemon_tool.get_folders()
        d.addCallback(lambda r: show_folders(r, out))
    elif options.subscribe_folder:
        d = sync_daemon_tool.subscribe_folder(options.subscribe_folder)
        d.addErrback(lambda r: show_error(r, out))
    elif options.unsubscribe_folder:
        d = sync_daemon_tool.unsubscribe_folder(options.unsubscribe_folder)
        d.addErrback(lambda r: show_error(r, out))
    elif options.rescan_from_scratch is not None:
        d = sync_daemon_tool.rescan_from_scratch(options.rescan_from_scratch)
        d.addErrback(lambda r: show_error(r, out))
    elif options.publish_file:
        path = os.path.abspath(options.publish_file)
        d = sync_daemon_tool.change_public_access(path, True)
        d.addCallback(lambda info: show_public_file_info(info, out))
        d.addErrback(lambda failure: show_error(failure, out))
    elif options.unpublish_file:
        path = os.path.abspath(options.unpublish_file)
        d = sync_daemon_tool.change_public_access(path, False)
        d.addCallback(lambda info: show_public_file_info(info, out))
        d.addErrback(lambda failure: show_error(failure, out))
    elif options.path_info:
        try:
            path = options.path_info.decode(sys.getfilesystemencoding())
        except (UnicodeDecodeError, UnicodeEncodeError):
            parser.error('PATH %r could not be decoded using the filesystem '
                         'encoding %r.' % (
                             options.path_info, sys.getfilesystemencoding()))
        path = os.path.abspath(path)
        if not os.path.exists(path):
            parser.error("PATH: '%s' don't exists" % path)
        d = sync_daemon_tool.get_metadata(path)
        d.addCallback(lambda r: show_path_info(r, path, out))
    elif options.dirty_nodes:
        d = sync_daemon_tool.get_dirty_nodes()
        d.addCallback(lambda r: show_dirty_nodes(r, out))
    elif options.current_transfers:
        d = sync_daemon_tool.get_current_uploads()
        d.addCallback(lambda r: show_uploads(r, out))
        d.addCallback(lambda _: sync_daemon_tool.get_current_downloads())
        d.addCallback(lambda r: show_downloads(r, out))
    elif options.quit:
        d = sync_daemon_tool.quit()

        @defer.inlineCallbacks
        def shutdown_check(result):
            """Shutdown and check if really stopped."""
            running = yield is_already_running()
            if result is None and not running:
                out.write("ubuntuone-syncdaemon stopped.\n")
            else:
                out.write("ubuntuone-syncdaemon still running.\n")

        d.addBoth(shutdown_check)
    elif options.connect:
        d = sync_daemon_tool.connect()
    elif options.disconnect:
        d = sync_daemon_tool.disconnect()
    elif options.status:
        d = sync_daemon_tool.get_status()
        d.addCallback(lambda r: show_state(r, out))
    elif options.waiting:
        d = sync_daemon_tool.waiting()
        d.addCallback(lambda r: show_waiting(r, out))
    elif options.waiting_metadata:
        d = sync_daemon_tool.waiting_metadata()
        d.addCallback(lambda r: show_waiting_metadata(r, out))
    elif options.waiting_content:
        d = sync_daemon_tool.waiting_content()
        d.addCallback(lambda r: show_waiting_content(r, out))
    elif options.free_space is not None:
        d = sync_daemon_tool.free_space(options.free_space)
        d.addCallback(lambda r: show_free_space(r, out))
    elif options.start:
        d = sync_daemon_tool.start()
    elif options.version:
        print '%s - Version %s' % (os.path.basename(sys.argv[0]), VERSION)
        d = defer.succeed(None)
    else:
        parser.print_help()
        d = defer.succeed(None)
    return d


if __name__ == '__main__':
    # disable the dbus warnings
    warnings.filterwarnings('ignore', module='dbus')
    usage = "Usage: %prog [option]"
    parser = OptionParser(usage=usage)
    parser.add_option("-w", "--wait", dest="wait", action="store_true",
                      help="Wait until ubuntuone-syncdaemon reaches nirvana")
    parser.add_option("", "--accept-share", dest="accept_share",
                      metavar="SHARE_ID",
                      help="Accept the share with the specified id")
    parser.add_option("", "--reject-share", dest="reject_share",
                      metavar="SHARE_ID",
                      help="Reject the share with the specified id")
    parser.add_option("", "--list-shares", dest="list_shares",
                      action="store_true",
                      help="Get the list of shares")
    parser.add_option("", "--subscribe-share", dest="subscribe_share",
                      metavar="SHARE_ID",
                      help="Subscribe to a share specified by id")
    parser.add_option("", "--unsubscribe-share", dest="unsubscribe_share",
                      metavar="SHARE_ID",
                      help="Unsubscribe from a share specified by id")
    parser.add_option("", "--refresh-shares", dest="refresh_shares",
                      action="store_true",
                      help="Request a refresh of the list of shares to"
                      " the server")
    parser.add_option("", "--offer-share", dest="offer_share", type="string",
                      nargs=4, metavar="PATH USER SHARE_NAME ACCESS_LEVEL",
                      help="Share PATH to USER. ")
    parser.add_option("", "--list-shared", dest="list_shared",
                      action="store_true",
                      help="List the shared paths/shares offered. ")
    parser.add_option("", "--create-folder", dest="create_folder",
                      metavar="PATH",
                      help="Create user defined folder in the specified path")
    parser.add_option("", "--delete-folder", dest="delete_folder",
                      metavar="FOLDER_ID",
                      help="Delete user defined folder in the specified path")
    parser.add_option("", "--list-folders", dest="list_folders",
                      action="store_true",
                      help="List all the user defined folders")
    parser.add_option("", "--subscribe-folder", dest="subscribe_folder",
                      metavar="FOLDER_ID",
                      help="Subscribe to the folder specified by id")
    parser.add_option("", "--unsubscribe-folder", dest="unsubscribe_folder",
                      metavar="FOLDER_ID",
                      help="Unsubscribe from the folder specified by id")
    parser.add_option("", "--refresh-volumes", dest="refresh_volumes",
                      action="store_true",
                      help="Request a refresh of the list of volumes to"
                      " the server")
    parser.add_option("", "--rescan-from-scratch", dest="rescan_from_scratch",
                      metavar="VOLUME_ID",
                      help="Request a rescan from scratch for a volume.")
    parser.add_option("", "--publish-file", dest="publish_file",
                      metavar="PATH", help="Publish file publicly.")
    parser.add_option("", "--unpublish-file", dest="unpublish_file",
                      metavar="PATH", help="Stop publishing file publicly.")
    parser.add_option("", "--info", dest="path_info",
                      metavar="PATH", help="Request the metadata of PATH")

    parser.add_option("", "--list-dirty-nodes", dest="dirty_nodes",
                      action="store_true",
                      help="Show the list of nodes marked as 'dirty'")
    parser.add_option("", "--current-transfers", dest="current_transfers",
                      action="store_true",
                      help=" show the current uploads and downloads")
    parser.add_option("-q", "--quit", dest="quit", action='store_true',
                      help="Shutdown the syncdaemon")
    parser.add_option("-c", "--connect", dest="connect", action='store_true',
                      help="Connect the syncdaemon")
    parser.add_option("-d", "--disconnect", dest="disconnect",
                      action='store_true', help="Disconnect the syncdaemon")
    parser.add_option("-s", "--status", dest="status", action='store_true',
                      help="Get the current status of syncdaemon")
    parser.add_option("", "--waiting", dest="waiting",
                      action='store_true',
                      help="Get the list of operations being executed")
    parser.add_option("", "--waiting-content", dest="waiting_content",
                      action='store_true',
                      help="Get the waiting content list - Warning: this "
                      "option is deprecated, use '--waiting' instead")
    parser.add_option("", "--waiting-metadata", dest="waiting_metadata",
                      action='store_true',
                      help="Get the waiting metadata list - Warning: this "
                      "option is deprecated, use '--waiting' instead")
    parser.add_option("", "--free-space", dest="free_space",
                      metavar="VOLUME_ID",
                      help="Get the free space for the volume")
    parser.add_option("", "--start", dest="start", action='store_true',
                      help="Start syncdaemon if it's not running")
    parser.add_option("", "--version", dest="version", action='store_true',
                      help="Print the version number and exit")

    (options, args) = parser.parse_args(sys.argv)
    reactor.callWhenRunning(main, options, args, sys.stdout)
    reactor.run()
