// NewDBusProxy creates a new dbus proxy.
func NewDBusProxy(conn *dbus.Conn, dest string, objPath string, iface string, flags dbus.Flags) (*DBusProxy, error) {
	if conn == nil {
		return nil, ErrorProxyNilConnection
	}
	if dest == "" {
		return nil, ErrorProxyEmptyDestination
	}
	if objPath == "" {
		return nil, ErrorProxyEmptyObjectPath
	}
	if !dbus.ObjectPath(objPath).IsValid() {
		return nil, ErrorProxyInvalidObjectPath
	}
	if iface == "" {
		return nil, ErrorProxyEmptyInterface
	}
	obj := conn.Object(dest, dbus.ObjectPath(objPath))
	proxy := &DBusProxy{
		conn:              conn,
		obj:               obj,
		dest:              dest,
		objPath:           objPath,
		iface:             iface,
		introspectorIface: "org.freedesktop.DBus.Introspectable",
		flags:             flags,
		sigChanMap:        map[string][]Signal{},
	}
	runtime.SetFinalizer(proxy, func(proxy *DBusProxy) {
		proxy.finalize()
	})
	return proxy, nil
}
func (manager *MonitorManager) Watch(fileURI string) (string, dbus.ObjectPath, string, error) {
	watcherID := atomic.AddUint32(&_WatcherCounter, 1)
	watcher, err := NewWatcher(watcherID, fileURI)
	if err != nil {
		return "", dbus.ObjectPath("/"), "", err
	}

	if err := dbus.InstallOnSession(watcher); err != nil {
		watcher.finalize()
		return "", dbus.ObjectPath("/"), "", err
	}

	manager.watchers[WatcherID(watcherID)] = watcher
	dbusInfo := watcher.GetDBusInfo()
	return dbusInfo.Dest, dbus.ObjectPath(dbusInfo.ObjectPath), dbusInfo.Interface, nil
}
func (manager *MonitorManager) Monitor(fileURI string, flags uint32) (string, dbus.ObjectPath, string, error) {
	monitorID := atomic.AddUint32(&_MonitorCounter, 1)
	monitor, err := NewMonitor(monitorID, fileURI, gio.FileMonitorFlags(flags))
	if monitor == nil {
		return "", dbus.ObjectPath("/"), "", err
	}

	if err := dbus.InstallOnSession(monitor); err != nil {
		Log.Error("Install Monitor to Session Bus failed:", err)
		monitor.finalize()
		return "", dbus.ObjectPath("/"), "", err
	}

	manager.monitors[MonitorID(monitorID)] = monitor
	dbusInfo := monitor.GetDBusInfo()
	return dbusInfo.Dest, dbus.ObjectPath(dbusInfo.ObjectPath), dbusInfo.Interface, nil
}
// Because empty object path is invalid, so JobObjectPath is used as default value.
// So empty interface means install failed.
func installJob(job dbus.DBusObject) (string, dbus.ObjectPath, string, error) {
	dest := ""
	objPath := dbus.ObjectPath(d.JobObjectPath)
	iface := ""
	if job == nil {
		Log.Warning("cannot install a nil object on dbus.")
		return dest, objPath, iface, errors.New("cannot install a nil object on dbus.")
	}

	err := dbus.InstallOnSession(job)
	if err != nil {
		Log.Warning("install dbus on session bus failed:", err)
		return dest, objPath, iface, err
	}

	dbusInfo := job.GetDBusInfo()
	return dbusInfo.Dest, dbus.ObjectPath(dbusInfo.ObjectPath), dbusInfo.Interface, nil
}
// create a new operation from fn and install it to session bus.
// NB: using closure and anonymous function is ok,
// but variable-length argument list is much more safer and much more portable.
func newOperationJob(paths []string, fn func([]string, ...interface{}) dbus.DBusObject, args ...interface{}) (string, dbus.ObjectPath, string, error) {
	objPath := dbus.ObjectPath(d.JobObjectPath)
	iface := ""

	srcURLs := make([]string, len(paths))
	for i, path := range paths {
		srcURL, err := pathToURL(path)
		if err != nil {
			Log.Error("convert path to URL failed:", err)
			return "", objPath, iface, err // maybe continue is a better choice.
		}
		srcURLs[i] = srcURL.String()
	}

	return installJob(fn(srcURLs, args...))
}
func (proxy *DBusProxy) Subscribe(sigName string, f interface{}) func() {
	sig := proxy.createSignalChan(sigName)
	rule := proxy.buildRule(sigName)
	proxy.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
	go func() {
		fn := reflect.ValueOf(f)
		for v := range sig.Chan {
			if v.Name != SignalName(proxy.iface, sigName) || v.Path != dbus.ObjectPath(proxy.objPath) {
				continue
			}
			l := len(v.Body)
			args := make([]reflect.Value, l)
			for i, v := range v.Body {
				args[i] = reflect.ValueOf(v)
			}
			fn.Call(args)
		}
	}()
	return func() {
		proxy.removeSignalChan(sig)
	}
}