def __init__(self, main_window, subreddits=None, *args, **kwargs):
        super(SubredditModel, self).__init__()
        # The internal storage that will store configuration tuples.
        self.subreddits = subreddits or []

        # Setting up the settings containing the secret information for the reddit instance and the blacklist.
        self.settings = Settings()

        self.main_window = main_window
Esempio n. 2
0
    def __init__(self):
        # The filename of the image that is currently the desktop background.
        self.current_background = ""

        # The path to the folder that contains the images that can be picked as the desktop background.
        self.image_folder = os.path.abspath("../data/images") + "/"

        self.settings = Settings()

        # Setting up the timer that changes the background to a random picture according to the time interval.
        self.timer = QTimer()
        self.timer.timeout.connect(self.change_background)
        self.timer.start(self.settings.change_frequency * 60000)

        # Changing the background immediately since the application is launched on computer startup.
        self.change_background()
class SettingsDialog(QtWidgets.QDialog):
    def __init__(self, background_changer, *args, **kwargs):
        super(SettingsDialog, self).__init__(*args, **kwargs)

        # Load the UI Page.
        uic.loadUi("../resources/settingsdialog.ui", self)

        # Used to restart the timer if the change frequency was changed.
        self.background_changer = background_changer

        # Loading the settings and writing them to their respective line edits.
        self.settings = Settings()
        self.clientIDLineEdit.setText(self.settings.client_id)
        self.clientSecretLineEdit.setText(self.settings.client_secret)
        self.userAgentLineEdit.setText(self.settings.user_agent)
        self.changeFrequencySpinBox.setValue(self.settings.change_frequency)

        # Saving the text in the line edits to the settings file when the user presses the "OK" button.
        self.accepted.connect(self.ok)

        # Reverting any changes if the user presses the "Cancel" button.
        self.rejected.connect(self.cancel)

    def ok(self):
        """Saving the text in the line edits to the settings file."""
        self.settings.client_id = self.clientIDLineEdit.text()
        self.settings.client_secret = self.clientSecretLineEdit.text()
        self.settings.user_agent = self.userAgentLineEdit.text()

        initial_change_frequency = self.settings.change_frequency
        self.settings.change_frequency = self.changeFrequencySpinBox.value()

        # Restarting the background changer timer with the new change frequency if it was changed.
        if self.settings.change_frequency != initial_change_frequency:
            self.background_changer.timer.start(self.settings.change_frequency * 60000)

        self.settings.save_settings()

    def cancel(self):
        """Reverting the text in the line edits back to the initial text."""
        self.clientIDLineEdit.setText(self.settings.client_id)
        self.clientSecretLineEdit.setText(self.settings.client_secret)
        self.userAgentLineEdit.setText(self.settings.user_agent)
        self.changeFrequencySpinBox.setValue(self.settings.change_frequency)
    def __init__(self, background_changer, main_window, app):
        self.background_changer = background_changer
        self.main_window = main_window
        self.app = app

        # Loading the settings from the app_settings json file to get the blacklist.
        self.settings = Settings()

        # Setting up the system tray icon itself.
        self.tray = QSystemTrayIcon()
        self.tray.setIcon(QIcon("../resources/reddit_icon.ico"))
        self.tray.setVisible(True)

        # Creating the menu.
        self.menu = QMenu()

        # Opening the main window when the tray icon is double clicked. We only want to call show() if the activation
        # reason is 2, meaning that the icon was double clicked.
        self.tray.activated.connect(lambda activation_reason: self.main_window.
                                    show() if activation_reason == 2 else None)

        # Creating an action that opens the main window.
        self.open_window_action = QAction("Open")
        self.open_window_action.triggered.connect(self.main_window.show)
        self.menu.addAction(self.open_window_action)

        # Creating an action that exits the application
        self.exit_app_action = QAction("Exit")
        self.exit_app_action.triggered.connect(self.app.exit)
        self.menu.addAction(self.exit_app_action)

        # Separating the default actions and the application specific actions.
        self.menu.addSeparator()

        # Creating an action that changes the background manually and adding it to the menu of the tray icon.
        self.change_background_action = QAction("Change background")
        self.change_background_action.triggered.connect(self.change_background)
        self.menu.addAction(self.change_background_action)

        # Add the menu to the tray.
        self.tray.setContextMenu(self.menu)
    def __init__(self, background_changer, *args, **kwargs):
        super(SettingsDialog, self).__init__(*args, **kwargs)

        # Load the UI Page.
        uic.loadUi("../resources/settingsdialog.ui", self)

        # Used to restart the timer if the change frequency was changed.
        self.background_changer = background_changer

        # Loading the settings and writing them to their respective line edits.
        self.settings = Settings()
        self.clientIDLineEdit.setText(self.settings.client_id)
        self.clientSecretLineEdit.setText(self.settings.client_secret)
        self.userAgentLineEdit.setText(self.settings.user_agent)
        self.changeFrequencySpinBox.setValue(self.settings.change_frequency)

        # Saving the text in the line edits to the settings file when the user presses the "OK" button.
        self.accepted.connect(self.ok)

        # Reverting any changes if the user presses the "Cancel" button.
        self.rejected.connect(self.cancel)
Esempio n. 6
0
class BackgroundChanger:
    def __init__(self):
        # The filename of the image that is currently the desktop background.
        self.current_background = ""

        # The path to the folder that contains the images that can be picked as the desktop background.
        self.image_folder = os.path.abspath("../data/images") + "/"

        self.settings = Settings()

        # Setting up the timer that changes the background to a random picture according to the time interval.
        self.timer = QTimer()
        self.timer.timeout.connect(self.change_background)
        self.timer.start(self.settings.change_frequency * 60000)

        # Changing the background immediately since the application is launched on computer startup.
        self.change_background()

    def change_background(self):
        """Changes the background of the desktop to a random image from the self.image_dict folder."""
        # Loading the most recent settings.
        self.settings.load_settings()

        # Wrapping in a try-except to handle invalid/broken images and the case where there are no images in the folder.
        try:
            # Choosing a random image from the folder containing all possible backgrounds.
            background_name = random.choice(os.listdir(self.image_folder))

            # Setting the new desktop background image to the chosen image.
            ctypes.windll.user32.SystemParametersInfoW(20, 0, self.image_folder + background_name, 0)

            self.current_background = background_name

            # Restarting the timer.
            self.timer.start(self.settings.change_frequency * 60000)
        except Exception as e:
            print("Background changer: " + str(e))
class SystemTray:
    """
    Class for creating the icon in the system tray that can be used to open the window and quickly change
    background manually.
    """
    def __init__(self, background_changer, main_window, app):
        self.background_changer = background_changer
        self.main_window = main_window
        self.app = app

        # Loading the settings from the app_settings json file to get the blacklist.
        self.settings = Settings()

        # Setting up the system tray icon itself.
        self.tray = QSystemTrayIcon()
        self.tray.setIcon(QIcon("../resources/reddit_icon.ico"))
        self.tray.setVisible(True)

        # Creating the menu.
        self.menu = QMenu()

        # Opening the main window when the tray icon is double clicked. We only want to call show() if the activation
        # reason is 2, meaning that the icon was double clicked.
        self.tray.activated.connect(lambda activation_reason: self.main_window.
                                    show() if activation_reason == 2 else None)

        # Creating an action that opens the main window.
        self.open_window_action = QAction("Open")
        self.open_window_action.triggered.connect(self.main_window.show)
        self.menu.addAction(self.open_window_action)

        # Creating an action that exits the application
        self.exit_app_action = QAction("Exit")
        self.exit_app_action.triggered.connect(self.app.exit)
        self.menu.addAction(self.exit_app_action)

        # Separating the default actions and the application specific actions.
        self.menu.addSeparator()

        # Creating an action that changes the background manually and adding it to the menu of the tray icon.
        self.change_background_action = QAction("Change background")
        self.change_background_action.triggered.connect(self.change_background)
        self.menu.addAction(self.change_background_action)

        # Add the menu to the tray.
        self.tray.setContextMenu(self.menu)

    def change_background(self):
        """
        Changing the background manually and adding the old background image to the blacklist. We assume that the
        background was changed manually due to the background being undesirable.
        """
        initial_background = self.background_changer.current_background

        if initial_background != "":
            # Removing the initial image from the folder containing the possible backgrounds.
            os.remove("../data/images/" + initial_background)

            # Adding the initial image to the blacklist and updating the settings with the new blacklist.
            self.settings.blacklist.append(initial_background)
            self.settings.save_settings()

        self.background_changer.change_background()
class SubredditModel(QtCore.QAbstractListModel):
    """
    Class for creating an abstract list model that is subclassed to support a list of subreddits. This involves saving
    a list of tuples, with the format (name, time limit, picture limit).

    Here time limit describes the setting for "top" which can be "Now", "Today", "This week", "This month", "This year"
    and "All time". Picture limit describes how many pictures we want from the specific subreddit.

    The tuple (/r/aww, "this year", 25) would get the top 25 pictures within the last year from the subreddit /r/aww.
    """
    def __init__(self, main_window, subreddits=None, *args, **kwargs):
        super(SubredditModel, self).__init__()
        # The internal storage that will store configuration tuples.
        self.subreddits = subreddits or []

        # Setting up the settings containing the secret information for the reddit instance and the blacklist.
        self.settings = Settings()

        self.main_window = main_window

    def data(self, QModelIndex, role=None):
        """
        Returns the data stored under the given role for the item referred to by the index.

        :param QModelIndex: The specific index of the model that we wish to extract data for.
        :param role: The specific data that we wish to extract.
        :return: The name of the subreddit if the role is DisplayRole.
        """
        name, _, _ = self.subreddits[QModelIndex.row()]

        if role == Qt.DisplayRole:
            return "/r/" + name

        # Inserting the subreddit icon before the name in each row.
        if role == Qt.DecorationRole:
            # Iterating through the icons.
            for filename in os.listdir("../data/icons/"):
                # When we find the correct icon for the subreddit we return a scaled version.
                if filename.lower().startswith(name.lower()):
                    icon = QtGui.QImage("../data/icons/" + filename)
                    return icon.scaled(25, 25)

    def rowCount(self, parent=None, *args, **kwargs):
        """
        Simple function that returns the total rowcount of the internal model representation. Since we use a list this
        is simply the length of the list.
        """
        return len(self.subreddits)

    def get_images(self, subreddit_config, save_path):
        """
        Uses PRAW to get the images from reddit that are described by the specific subreddit configuration. Once gotten
        the function saves them to the specified folder.

        :param save_path: The path to the folder in which we save the images.
        :param subreddit_config: The subreddit configuration that describes the subreddit we should search, the time
        limit the search should be within and the amount of images we should find.
        """
        # Loading the most recent settings.
        self.settings.load_settings()

        # Pulling the information from the configuration to increase readability.
        name, time_limit, number_of_images = subreddit_config

        # Creating the reddit instance using the secret information.
        reddit = praw.Reddit(client_id=self.settings.client_id,
                             client_secret=self.settings.client_secret,
                             user_agent=self.settings.user_agent)

        # Stopping further execution of the method if the subreddit does not exist.
        if not self.check_subreddit_exists(reddit, subreddit_config):
            return

        subreddit = reddit.subreddit(name)

        # Incrementing getting_images since one more worker is getting images.
        self.main_window.getting_images += 1
        # Disabling the delete and update buttons since the application crashes if the user delete while getting images.
        self.main_window.deleteButton.setEnabled(False)
        self.main_window.updateButton.setEnabled(False)

        # Visually changing the buttons so it is clear that they are disabled.
        self.main_window.deleteButton.setFlat(True)
        self.main_window.updateButton.setFlat(True)

        # Converting the time limit into the corresponding time filter that can be used in top().
        time_limit = self.convert_time_limit(time_limit)

        image_counter = 0
        for submission in subreddit.top(time_filter=time_limit, limit=1000):
            # Ensuring that we only retrieve the requested amount of images.
            if image_counter == number_of_images:
                break

            # Catching rare problematic submissions and ignoring them if they cause problems.
            try:
                filename = name + "_" + submission.name + ".jpg"
                if self.check_background_viability(submission, filename):
                    # Downloading the image from the url and saving it to the folder using its unique name.
                    urllib.request.urlretrieve(
                        submission.preview["images"][0]["source"]["url"],
                        save_path + filename)
                    image_counter += 1
            except Exception as e:
                print("Image getter:" + str(e))
                continue

        # Adding the subreddit icon to the icon folder.
        self.get_icon(subreddit, "../data/icons/")

        self.main_window.getting_images -= 1

        # Enabling the delete and update buttons again if there is no workers currently getting images.
        if self.main_window.getting_images == 0:
            self.main_window.deleteButton.setEnabled(True)
            self.main_window.updateButton.setEnabled(True)

            # Visually changing the buttons back so it is clear they are enabled again.
            self.main_window.deleteButton.setFlat(False)
            self.main_window.updateButton.setFlat(False)

    def check_background_viability(self, submission, filename):
        """
        Checks whether or not the given submission is viable to use as a desktop background. This includes checking that
        the submission is hosted on the reddit media domain, checking that the submission is an image, checking that
        the image is large enough, checking that the aspect ratio is similar to the monitor aspect ratio and finally
        checking if the image is in the blacklist.

        :param submission: The reddit submission that is checked for its viability as an background image.
        :param filename: The filename of the image if it was downloaded, used to check if the image is in the blacklist.
        :return: True if the submission is viable as a desktop background, false otherwise.
        """
        if submission.is_reddit_media_domain:
            if submission.is_video is False:
                image_width = submission.preview["images"][0]["source"][
                    "width"]
                image_height = submission.preview["images"][0]["source"][
                    "height"]
                monitor_width = ctypes.windll.user32.GetSystemMetrics(0)
                monitor_height = ctypes.windll.user32.GetSystemMetrics(1)

                if image_width >= monitor_width and image_height >= monitor_height:
                    image_aspect_ratio = image_width / image_height
                    monitor_aspect_ratio = monitor_width / monitor_height

                    if abs(monitor_aspect_ratio - image_aspect_ratio) <= 0.2:
                        if filename not in self.settings.blacklist:
                            return True
        return False

    @staticmethod
    def delete_images(subreddit_name):
        """
        Deletes all images from the background image pool that are from the given subreddit. Also deletes the
        subreddits icon from the icon folder.

        :param subreddit_name: The subreddit specifying what images and which icon that should be deleted.
        """
        # Deleting the images from the background image pool.
        for filename in os.listdir("../data/images/"):
            if filename[:-14].lower() == subreddit_name.lower():
                os.remove(os.path.join("../data/images/", filename))

        # Deleting the icon from the icon folder.
        for filename in os.listdir("../data/icons/"):
            if filename[:-4].lower() == subreddit_name.lower():
                os.remove(os.path.join("../data/icons/", filename))

    @staticmethod
    def convert_time_limit(time_limit):
        """
        Converts the time limit from the combobox into the corresponding internal value that can be used in PRAW.

        :param time_limit: The time limit that we wish to convert.
        :return: The value that corresponds to the time_limit argument.
        """
        return {
            "Now": "hour",
            "Today": "day",
            "This week": "week",
            "This month": "month",
            "This year": "year",
            "All time": "all"
        }[time_limit]

    @staticmethod
    def get_icon(subreddit, save_path):
        """
        Saves the icon of the given subreddit to the folder specified by the save path argument.
        :param subreddit: The subreddit that we wish to find the icon of.
        :param save_path: The folder we should save the icon to.
        """
        # If the subreddit has an icon.
        if subreddit.icon_img != "":
            # Save the icon to the icons folder.
            urllib.request.urlretrieve(
                subreddit.icon_img,
                save_path + subreddit.display_name + ".png")
        # If not we just save the subreddit icon as the default icon.
        else:
            copyfile("../resources/default_subreddit_icon.png",
                     save_path + subreddit.display_name + ".png")

    def check_subreddit_exists(self, reddit, subreddit_config):
        """
        Checks if the given subreddit exists. If not it removes the subreddit from the internal model.

        :param reddit: The praw reddit instance used to access reddit.
        :param subreddit_config: The subreddit configuration that describes the subreddit we should search for.
        :return: True if the subreddits exists, otherwise false.
        """
        # Pulling the subreddit name from the configuration.
        name, _, _ = subreddit_config

        try:
            reddit.subreddits.search_by_name(name, exact=True)
        except prawcore.NotFound as e:
            print("Subreddit does not exist: " + str(e))

            # Removing the subreddit from the internal model.
            self.main_window.model.subreddits.remove(subreddit_config)
            self.main_window.model.layoutChanged.emit()
            self.main_window.save_subreddits()

            return False

        return True