Skip to content

Commit

Permalink
Add get since th day if-empty option (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
dplocki authored Nov 11, 2022
1 parent 9058160 commit 99e01da
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 18 deletions.
60 changes: 47 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,25 @@ Using the [example above](#example), the result will be:

The configuration file is placed in home directory.

The name: `.podcast_downloader_config.json`.

The format is: [JSON](https://en.wikipedia.org/wiki/JSON).
The name: `.podcast_downloader_config.json`. The file is format in [JSON](https://en.wikipedia.org/wiki/JSON).

### The settings hierarchy

The script will replace default values by read from configuration file.
Those will be cover by all values given by command line.

```
command line parameters > configuration file > default values
```

### The main options

| Property | Type | Required | Default | Note |
|:---------------------|:---------:|:--------:|:------------------------:|:-----|
| `downloads_limit` | number | no | infinity | |
| `if_directory_empty` | string | no | download_last | See [In case of empty directory](#in-case-of-empty-directory) |
| `podcast_extensions` | key-value | no | `{".mp3": "audio/mpeg"}` | The file filter |
| Property | Type | Required | Default | Note |
|:---------------------|:----------:|:--------:|:------------------------:|:-----|
| `downloads_limit` | number | no | infinity | |
| `if_directory_empty` | string | no | download_last | See [In case of empty directory](#in-case-of-empty-directory) |
| `podcast_extensions` | key-value | no | `{".mp3": "audio/mpeg"}` | The file filter |
| `podcasts` | subsection | yes | `[]` | See [Podcasts sub category](#podcasts-sub-category) |

### Podcasts sub category

Expand Down Expand Up @@ -134,8 +137,16 @@ Notes: the dot on the file extension is require.

If a directory for podcast is empty, the script needs to recognize what to do. Due to lack of database, you can:

* download only the last episode
* download all new episode from last n days
* [download all episodes from feed](#download-all-from-feed)
* [download only the last episode](#only-last)
* [download all new episode from last n days](#download-all-from-n-days)
* [download all new episode since day after, the last episode should appear](#download-all-episode-since-last-excepted)

### Download all from feed

The script will download all episodes from the feed.

Set by `download_all_from_feed`.

### Only last

Expand All @@ -150,11 +161,34 @@ Set by `download_last`.
The script will download all episodes which appear in last *n* days. I can be use when you are downloading on regular schedule.
The *n* number is given within the setup value: `download_from_n_days`. For example: `download_from_3_days` means download all episodes from last 3 days.

### Download all from feed
### Download all episode since last excepted

The script will download all episodes from the feed.
The script will download all episodes which appear after the day of release of last episode.

Set by `download_all_from_feed`.
The *n* number is the day of the normal episode.
You can provide here week days as word (size of the letters is ignored)

| Full week day | Shorten name |
|:--------------|:-------------|
| Monday | Mon |
| Tuesday | Tues |
| Wednesday | Weds |
| Thursday | Thurs |
| Friday | Fri |
| Saturday | Sat |
| Sunday | Sun |

You can provide the number, it will means the day of the month. The script accepts only number from 1 to 28.

Set by `download_from_`.

Examples:

| Example value | Meaning |
|------------------------|---------|
| `download_from_monday` | New episodes appear in Monday. The script will download all episodes since last Tuesday (including it) |
| `download_from_Fri` | New episodes appear in Friday. The script will download all episodes since last Saturday (including it) |
| `download_from_12` | New episodes appear each 12th of month. The script will download all episodes since 13 month before |

## The analyze of the RSS feed

Expand Down
3 changes: 1 addition & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
version: '3.9'
services:
app:
tests:
build:
context: .
volumes:
- .:/app/

17 changes: 15 additions & 2 deletions podcast_downloader/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
from functools import partial
from . import configuration

from podcast_downloader.configuration import configuration_verification, get_n_age_date
from podcast_downloader.configuration import (
configuration_verification,
get_label_to_date,
get_n_age_date,
parse_day_label,
)
from .utils import log, compose
from .downloaded import get_extensions_checker, get_last_downloaded
from .parameters import merge_parameters_collection, load_configuration_file, parse_argv
Expand Down Expand Up @@ -62,11 +67,19 @@ def configuration_to_function(
if configuration_value == "download_all_from_feed":
return lambda source: source

local_time = time.localtime()

from_n_day_match = re.match(r"^download_from_(\d+)_days$", configuration_value)
if from_n_day_match:
from_date = get_n_age_date(int(from_n_day_match[1]), time.localtime())
from_date = get_n_age_date(int(from_n_day_match[1]), local_time)
return only_entities_from_date(from_date)

from_nth_day_match = re.match(r"^download_from_(.*)", configuration_value)
if from_nth_day_match:
day_label = parse_day_label(from_nth_day_match[1])

return only_entities_from_date(get_label_to_date(day_label)(local_time))

raise Exception(f"The value the '{configuration_value}' is not recognizable")


Expand Down
74 changes: 73 additions & 1 deletion podcast_downloader/configuration.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import List, Tuple
from functools import partial
from typing import List, Tuple, Union
from datetime import datetime, timedelta
import time

SECONDS_IN_DAY = 24 * 60 * 60
Expand All @@ -14,6 +16,16 @@
CONFIG_PODCASTS_REQUIRE_DATE = "require_date"
CONFIG_PODCASTS_DISABLE = "disable"

WEEK_DAYS = (
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
)


def configuration_verification(config: dict) -> Tuple[bool, List[str]]:
for podcast in config[CONFIG_PODCASTS]:
Expand All @@ -37,3 +49,63 @@ def configuration_verification(config: dict) -> Tuple[bool, List[str]]:

def get_n_age_date(day_number: int, from_date: time.struct_time) -> time.struct_time:
return time.localtime(time.mktime(from_date) - day_number * SECONDS_IN_DAY)


def get_label_to_date(day_label: Union[str, int]) -> partial:
if day_label in WEEK_DAYS:
return partial(get_week_day, day_label)

return partial(get_nth_day, int(day_label))


def get_week_day(weekday_label: str, from_date: time.struct_time) -> time.struct_time:
from_datetime = datetime(*from_date[:6])
weekday_from_date = from_datetime.weekday()
weekday_label_index = WEEK_DAYS.index(weekday_label)
result_datetime = from_datetime - timedelta(
6
if weekday_from_date == weekday_label_index
else weekday_from_date - weekday_label_index - 1
)

return result_datetime.timetuple()


def get_nth_day(day: int, from_date: time.struct_time) -> time.struct_time:
from_datetime = datetime(*from_date[:6])

day_difference = from_date[2] - day
datetime_result = (
from_datetime - timedelta(days=day_difference - 1)
if day_difference > 0
else (from_datetime.replace(day=1) - timedelta(days=28)).replace(day=day + 1)
)

return datetime_result.timetuple()


def parse_day_label(raw_label: str) -> Union[str, int]:
if raw_label.isnumeric():
return int(raw_label)

if raw_label == "1st":
return 1

if raw_label == "2nd":
return 2

if raw_label == "3rd":
return 3

if raw_label[-2:] == "th":
return int(raw_label[:-2])

capitalize_raw_label = raw_label.capitalize()
if capitalize_raw_label in WEEK_DAYS:
return capitalize_raw_label

short_weekdays = ("Mon", "Tues", "Weds", "Thurs", "Fri", "Sat", "Sun")
if capitalize_raw_label in short_weekdays:
return WEEK_DAYS[short_weekdays.index(capitalize_raw_label)]

raise Exception(f"Cannot read weekday name '{raw_label}'")
1 change: 1 addition & 0 deletions tests/get_all_entites_from_n_days_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
)
from commons import rss_entity_generator, build_timestamp


class TestAllEntitiesFromNDays(unittest.TestCase):
def test_of_filter_function(self):
# Assign
Expand Down
128 changes: 128 additions & 0 deletions tests/get_all_entries_from_th_day_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from time import strptime
import unittest

from podcast_downloader.configuration import (
WEEK_DAYS,
get_label_to_date,
get_nth_day,
get_week_day,
parse_day_label,
)


class TestParseDayLabel(unittest.TestCase):
def test_parse_day_label_correct_values(self):
test_parameters = {
"Monday": "Monday",
"mon": "Monday",
"wednesday": "Wednesday",
"fRIDay": "Friday",
"saturday": "Saturday",
"1st": 1,
"2nd": 2,
"3rd": 3,
"4th": 4,
"6": 6,
"25th": 25,
}

for test_value, expected_value in test_parameters.items():
result = parse_day_label(test_value)
self.assertEqual(result, expected_value, "Day of week incorrect recognized")

def test_parse_day_label_incorrect_values(self):
self.assertRaises(Exception, parse_day_label, "abcde")

def test_get_label_to_date_correct_values(self):
for week_day in WEEK_DAYS:
result = get_label_to_date(week_day)
self.assertEqual(result.func, get_week_day)

for day_number in range(1, 32):
result = get_label_to_date(day_number)
self.assertEqual(result.func, get_nth_day)


class TestGetWeekDay(unittest.TestCase):
"""Used in examples:
Mo Tu We Th Fr Sa Su
17 18 19 20 21 22 23
24 25 26 27 28 29 30
"""

def test_for_day_before(self):
# Assign
current_date = strptime("29.10.2022", "%d.%m.%Y")
day_after_last_monday = strptime("25.10.2022", "%d.%m.%Y")

# Act
result = get_week_day(WEEK_DAYS[0], current_date) # Monday

# Assert
self.assertEqual(
result, day_after_last_monday, "Expected first Tuesday before the date"
)

def test_for_day_in_the_day(self):
# Assign
current_date = strptime("26.10.2022", "%d.%m.%Y")
day_after_last_wednesday = strptime("20.10.2022", "%d.%m.%Y")

# Act
result = get_week_day(WEEK_DAYS[2], current_date) # Wednesday

# Assert
self.assertEqual(
result, day_after_last_wednesday, "Expected first Thursday before the date"
)


class TestGetNthDay(unittest.TestCase):
def test_for_day_inside_the_month(self):
# Assign
nth_day = 2
current_date = strptime("23.10.2022", "%d.%m.%Y")
expected_date = strptime(f"{nth_day + 1}.10.2022", "%d.%m.%Y")

# Act
result = get_nth_day(nth_day, current_date)

# Assert
self.assertEqual(
result,
expected_date,
f"Expected return the {nth_day + 1} of the same month",
)

def test_for_day_month_before(self):
# Assign
nth_day = 17
current_date = strptime("4.10.2022", "%d.%m.%Y")
expected_date = strptime(f"{nth_day + 1}.09.2022", "%d.%m.%Y")

# Act
result = get_nth_day(nth_day, current_date)

# Assert
self.assertEqual(
result,
expected_date,
f"Expected return the {nth_day} of the previous month",
)

def test_for_day_month_before_january(self):
# Assign
nth_day = 17
current_date = strptime("4.01.2022", "%d.%m.%Y")
expected_date = strptime(f"{nth_day + 1}.12.2021", "%d.%m.%Y")

# Act
result = get_nth_day(nth_day, current_date)

# Assert
self.assertEqual(
result,
expected_date,
f"Expected return the {nth_day} of the December",
)

0 comments on commit 99e01da

Please sign in to comment.