Recipes

Mostly tech, sometimes food

Performant pagination in C#

Pagination on IEnumerable<> is typically done using .Skip() and .Take(). But if you want more than one page this is going to iterate over the enumerable multiple times. I don't feel like figuring it out, but the performance will definitely be worse than O(n). So here's a way to turn an enumerable into a paged enumerable of enumerables that iterates the original only once.

public static class IEnumerableExtensions
{
  public static IEnumerable<IEnumerable<T>>(this IEnumerable<T> data, int pageSize)
  {
    var enumerator = data.GetEnumerator();

    while(enumerator.MoveNext())
    {
      yield return GetPage(enumerator, pageSize);
    }

    static IEnumerable<T> GetPage(IEnumerator<T> enumerator, int pageSize)
    {
      var count = 0;

      do
      {
        yield return enumerator.Current;
        count++;
      } while (count < pageSize && enumerator.MoveNext());
    }
  }
}

Build a docker image for WriteFreely. This is designed to be run behind a reverse proxy and “patches” the configuration as such.

To build and run:

podman build . --build-arg VERSION=0.16.0 -t writefreely:0.16.0
podman run --rm -it -v ~/volumes/writefreely:/data writefreely:0.16.0
# Follow directions to create initial config.ini and if all goes well, serve up in your preferred style

Containerfile

FROM docker.io/alpine:3

COPY wf-get.sh /tmp/wf-get.sh
COPY wf-startup.sh /wf-startup.sh

ARG VERSION

RUN apk add --no-cache openssl && \
  sh /tmp/wf-get.sh ${VERSION} && \
  tar xzvf /tmp/wf.tar.gz && \
  mkdir /data && \
  rm /tmp/wf-get.sh /tmp/wf.tar.gz

VOLUME ["/data"]

EXPOSE 8080

CMD /bin/sh /wf-startup.sh

wf-get.sh

#!/bin/sh

VERSION=$1

OUT_PATH=/tmp/wf.tar.gz

BASE_URL=https://github.com/writefreely/writefreely/releases/download/v$VERSION/

ARM_URL=$BASE_URL/writefreely_${VERSION}_linux_arm64.tar.gz
X64_URL=$BASE_URL/writefreely_${VERSION}_linux_amd64.tar.gz

case $(arch) in
  "aarch64")
    wget $ARM_URL -O $OUT_PATH
    ;;
  "x86_64")
    wget $X64_URL -O $OUT_PATH
    ;;
  *)
    echo "Unknown arch"
    exit 1
    ;;
esac

wf-startup.sh

#!/bin/sh

DATA_DIR="/data"
CONFIG_FILE="/data/config.ini"
DATABASE_FILE="/data/writefreely.db"
KEYS_DIR="/data/keys"

genpass() {
  PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1)
}

if [ ! -f $CONFIG_FILE ]; then
  echo -e "\e[0;33m"
  echo "STARTING CONFIGURATION"
  echo "Your choices for server and database setup will be overwritten"
  echo "For simplicity, select Development and SQLite when given those options"
  echo "The true configuration begins with App setup and the Site type"
  echo -e "\e[0m"

  cd /data
  /writefreely/writefreely config start

  echo -e "\e[0;33m"
  echo "PATCHING CONFIGURATION"
  echo -e "\e[0m"

  sed -i "
    /\[server\]/,/^$/ {
      s:^port.*:port = 8080:
      s:^bind.*:bind = 0.0.0.0:
      s:^tls_cert_path.*:tls_cert_path = :
      s:^tls_key_path.*:tls_key_path = :
      s:^autocert.*:autocert = false:
      s:^keys_parent_dir.*:keys_parent_dir = $DATA_DIR:
    }
    /\[database\]/,/^$/ {
      s:^type.*:type = sqlite3:
      s:^filename.*:filename = $DATABASE_FILE:
    }
  " $CONFIG_FILE

  cd /writefreely/

  echo -e "\e[0;33m"
  echo "If you chose a multi-user blog you'll probably want to create an admin user."
  echo "This user can still blog in addition to being used for system administration."
  echo "If you created a single user blog then you can skip this step."
  echo -e "\e[0m"
  read -p "Create admin user? [y/N] " response
  if [ $response = "Y" ] || [ $response = "y" ]; then
    read -p "Username: " username

    genpass

    ./writefreely -c $CONFIG_FILE user add --admin $username:$PASS

    echo -e "\e[0;33m"
    echo "Created $username with password $PASS. Please change this as soon as possible."
    echo -e "\e[0m"
  fi
fi

if [ ! -f $DATABASE_FILE ]; then
  /writefreely/writefreely -c $CONFIG_FILE db init
else
  /writefreely/writefreely -c $CONFIG_FILE db migrate
fi

if [ ! -e $KEYS_DIR ]; then
  mkdir $KEYS_DIR
fi

if [ -z "$(ls -A $KEYS_DIR )" ]; then
  /writefreely/writefreely -c $CONFIG_FILE keys generate
fi

cd /writefreely
./writefreely -c $CONFIG_FILE