def ShowContextMenu(self, index, global_pos):
    if not self.menu:
      self.menu = QMenu()

      for action in self.GetPlaylistActions():
          self.menu.addAction(action)
      self.menu.addAction(clementine.IconLoader.Load("download"),
        self.tr("Open " + self.HOMEPAGE_NAME + " in browser"), self.Homepage)
      self.menu.addAction(clementine.IconLoader.Load("view-refresh"),
        self.tr("Refresh streams"), self.RefreshStreams)

      self.menu.addSeparator()

      self.menu.addAction(clementine.IconLoader.Load("configure"),
        self.tr("Configure..."), self.settings_dialog_callback())

    self.context_index = index
    self.menu.popup(global_pos)
class DigitallyImportedServiceBase(clementine.InternetService):
  # Set these in subclasses
  HOMEPAGE_URL = None
  HOMEPAGE_NAME = None
  STREAM_LIST_URL = None
  ICON_FILENAME = None
  SERVICE_NAME = None
  SERVICE_DESCRIPTION = None
  PLAYLISTS = []
  URL_SCHEME = None

  SETTINGS_GROUP = "digitally_imported"

  def Init(self, model, settings_dialog_callback):
    clementine.InternetService.__init__(self, self.SERVICE_NAME, model)

    # We must hold a weak reference to the callback or else it makes a circular
    # reference between the services and Plugin from main.py.
    self.settings_dialog_callback = weakref.ref(settings_dialog_callback)

    self.url_handler = DigitallyImportedUrlHandler(self.URL_SCHEME, self)
    clementine.player.RegisterUrlHandler(self.url_handler)

    self.network = clementine.NetworkAccessManager(None)
    self.path = os.path.dirname(__file__)

    self.audio_type = 0
    self.username = ""
    self.password = ""

    self.context_index = None
    self.menu = None
    self.root = None
    self.task_id = None
    self.refresh_streams_reply = None
    self.load_station_reply = None
    self.items = []

    self.ReloadSettings()

  def ReloadSettings(self):
    settings = QSettings()
    settings.beginGroup(self.SETTINGS_GROUP)

    self.audio_type = int(settings.value("audio_type", 0))
    self.username = unicode(settings.value("username", ""))
    self.password = unicode(settings.value("password", ""))

  def CreateRootItem(self):
    self.root = QStandardItem(QIcon(os.path.join(self.path, self.ICON_FILENAME)),
                              self.SERVICE_DESCRIPTION)
    self.root.setData(True, clementine.InternetModel.Role_CanLazyLoad)
    return self.root

  def LazyPopulate(self, parent):
    if parent == self.root:
      # Download the list of streams the first time the user expands the root
      self.RefreshStreams()

  def ShowContextMenu(self, index, global_pos):
    if not self.menu:
      self.menu = QMenu()

      for action in self.GetPlaylistActions():
          self.menu.addAction(action)
      self.menu.addAction(clementine.IconLoader.Load("download"),
        self.tr("Open " + self.HOMEPAGE_NAME + " in browser"), self.Homepage)
      self.menu.addAction(clementine.IconLoader.Load("view-refresh"),
        self.tr("Refresh streams"), self.RefreshStreams)

      self.menu.addSeparator()

      self.menu.addAction(clementine.IconLoader.Load("configure"),
        self.tr("Configure..."), self.settings_dialog_callback())

    self.context_index = index
    self.menu.popup(global_pos)

  def GetCurrentIndex(self):
    return self.context_index

  def Homepage(self):
    QDesktopServices.openUrl(self.HOMEPAGE_URL)

  def RefreshStreams(self):
    if self.task_id is not None:
      return

    LOGGER.info("Getting stream list from '%s'", self.STREAM_LIST_URL)

    # Request the list of stations
    self.refresh_streams_reply = self.network.get(QNetworkRequest(self.STREAM_LIST_URL))
    self.refresh_streams_reply.connect("finished()", self.RefreshStreamsFinished)

    # Give the user some indication that we're doing something
    self.task_id = clementine.task_manager.StartTask(self.tr("Getting streams"))

  def RefreshStreamsFinished(self):
    if self.refresh_streams_reply is None:
      return
    if self.task_id is None:
      return

    # Stop the spinner in the status bar
    clementine.task_manager.SetTaskFinished(self.task_id)
    self.task_id = None

    # Read the data and parse the json object inside
    json_data = self.refresh_streams_reply.readAll().data()
    streams = json.loads(json_data)

    # Sort by name
    streams = sorted(streams, key=operator.itemgetter("name"))

    LOGGER.info("Loaded %d streams", len(streams))

    # Now we have the list of streams, so clear any existing items in the list
    # and insert the new ones
    if self.root.hasChildren():
      self.root.removeRows(0, self.root.rowCount())

    for stream in streams:
      song = clementine.Song()
      song.set_title(stream["name"])
      song.set_artist(self.SERVICE_DESCRIPTION)
      song.set_url(QUrl("%s://%s" % (self.URL_SCHEME, stream["key"])))

      item = QStandardItem(QIcon(":last.fm/icon_radio.png"), stream["name"])
      item.setData(stream["description"], PythonQt.QtCore.Qt.ToolTipRole)
      item.setData(clementine.InternetModel.PlayBehaviour_SingleItem, clementine.InternetModel.Role_PlayBehaviour)
      item.setData(song, clementine.InternetModel.Role_SongMetadata)
      self.root.appendRow(item)

      # Keep references to the items otherwise Python will delete them
      self.items.append(item)

  def playlistitem_options(self):
    return clementine.PlaylistItem.Options(clementine.PlaylistItem.PauseDisabled)

  def LoadStation(self, key):
    raise NotImplementedError()

  def LoadPlaylistFinished(self):
    self.url_handler.LoadPlaylistFinished(self.load_station_reply)
    def __init__(self,
                 schid,
                 cid,
                 password='',
                 path='/',
                 parent=None,
                 *,
                 staticpath=False,
                 readonly=False,
                 downloaddir=None,
                 iconpack=None):
        """
        Instantiates a new object.
        @param schid: the id of the serverconnection handler
        @type schid: int
        @param cid: the id of the channel
        @type cid: int
        @param password: password to the channel, defaults to an empty string
        @type password: str
        @param path: path to display, defaults to the root path
        @type path: str
        @param parent: parent of the dialog; optional keyword arg;
        defaults to None
        @type parent: QWidget
        @param staticpath: if set to True, the initial path can't be
        changed by the user; optional keyword arg; defaults to False
        @type staticpath: bool
        @param readonly: if set to True, the user can't download, upload
        or delete files, or create new directories; optional keyword arg;
        defaults to False
        @type readonly: bool
        @param downloaddir: directory to download files to; optional keyword
        arg; defaults to None; if set to None, the TS3 client's download
        directory is used
        @type downloaddir: str
        @param iconpack: iconpack to load icons from; optional keyword arg;
        defaults to None; if set to None, the current iconpack is used
        @type iconpack: ts3client.IconPack
        """
        super(QDialog, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)

        iconpackopened = False
        if not iconpack:
            try:
                iconpack = ts3client.IconPack.current()
                iconpack.open()
                iconpackopened = True
            except Exception as e:
                self.delete()
                raise e

        try:
            setupUi(self,
                    pytson.getPluginPath("ressources", "filebrowser.ui"),
                    iconpack=iconpack)

            self.statusbar = SmartStatusBar(self)
            self.layout().addWidget(self.statusbar)
            self.statusbar.hide()
        except Exception as e:
            self.delete()
            raise e

        err, cname = ts3lib.getChannelVariableAsString(
            schid, cid, ChannelProperties.CHANNEL_NAME)

        if err == ERROR_ok:
            self.setWindowTitle(
                self._tr("File Browser - {cname}").format(cname=cname))
        else:
            self.setWindowTitle(self._tr("File Browser"))

        self.schid = schid
        self.cid = cid
        self.password = password
        self.path = None

        self.staticpath = staticpath
        self.readonly = readonly

        self.createretcode = None
        self.delretcode = None

        if not self.readonly and not downloaddir:
            cfg = ts3client.Config()
            q = cfg.query("SELECT value FROM filetransfer "
                          "WHERE key='DownloadDir'")
            del cfg

            if q.next():
                self.downloaddir = q.value("value")
            else:
                self.delete()
                raise Exception("Error getting DownloadDir from config")
        else:
            self.downloaddir = downloaddir

        if not self.readonly:
            menu = self.menu = QMenu(self)

            self.openAction = menu.addAction(QIcon(iconpack.icon("FILE_UP")),
                                             self._tr("Open"))
            self.openAction.connect("triggered()",
                                    self.on_openAction_triggered)

            self.downAction = menu.addAction(QIcon(iconpack.icon("DOWN")),
                                             self._tr("Download"))
            self.downAction.connect("triggered()", self.downloadFiles)
            self.renameAction = menu.addAction(QIcon(iconpack.icon("EDIT")),
                                               self._tr("Rename"))
            self.renameAction.connect("triggered()",
                                      self.on_renameAction_triggered)
            self.copyAction = menu.addAction(QIcon(iconpack.icon("COPY")),
                                             self._tr("Copy URL"))
            self.copyAction.connect("triggered()",
                                    self.on_copyAction_triggered)
            self.delAction = menu.addAction(QIcon(iconpack.icon("DELETE")),
                                            self._tr("Delete"))
            self.delAction.connect("triggered()", self.deleteFiles)

            self.upAction = menu.addAction(QIcon(iconpack.icon("UP")),
                                           self._tr("Upload files"))
            self.upAction.connect("triggered()", self.uploadFiles)
            self.createAction = menu.addAction(QIcon.fromTheme("folder"),
                                               self._tr("Create Folder"))
            self.createAction.connect("triggered()", self.createFolder)
            self.refreshAction = menu.addAction(
                QIcon(iconpack.icon("FILE_REFRESH")), self._tr("Refresh"))
            self.refreshAction.connect("triggered()", self.refresh)

            self.allactions = [
                self.openAction, self.downAction, self.renameAction,
                self.copyAction, self.delAction, self.upAction,
                self.createAction, self.refreshAction
            ]

        self.collector = FileCollector(schid, cid, password, self.downloaddir)
        self.collector.collectionFinished.connect(self._startDownload)
        self.collector.collectionError.connect(self.showError)

        self.fileDoubleClicked = Signal()
        self.contextMenuRequested = Signal()

        self.transdlg = None

        self.listmodel = FileListModel(schid,
                                       cid,
                                       password,
                                       self,
                                       readonly=readonly)
        self.listmodel.pathChanged.connect(self.onPathChanged)
        self.listmodel.error.connect(self.showError)

        self.proxy = QSortFilterProxyModel(self)
        self.proxy.setSortRole(Qt.UserRole)
        self.proxy.setSortCaseSensitivity(Qt.CaseInsensitive)
        self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.proxy.setSourceModel(self.listmodel)

        self.listmodel.path = path

        self._adjustUi()

        if iconpackopened:
            iconpack.close()

        PluginHost.registerCallbackProxy(self)