13 - Ejemplo práctico 2: gestor de impresión con prioridad

Este segundo ejemplo agrega un nivel de complejidad: administramos un spool de impresión que atiende trabajos urgentes antes que los normales utilizando dos colas circulares.

13.1 Planteo del problema

El laboratorio comparte una impresora. Los usuarios pueden enviar trabajos normales o urgentes. El operador quiere:

  • Registrar cada trabajo indicando nombre del archivo, cantidad de páginas y prioridad.
  • Procesar siempre primero la cola urgente y luego la normal.
  • Consultar el estado de ambas colas en cualquier momento.

13.2 Modelo de datos

Implementaremos una estructura reutilizable de cola circular y la instanciaremos dos veces: una para trabajos urgentes y otra para normales.

#define CAP_IMP 20
#define MAX_NOMBRE_DOC 48

typedef struct {
  char archivo[MAX_NOMBRE_DOC];
  int paginas;
} Trabajo;

typedef struct {
  Trabajo datos[CAP_IMP];
  int frente;
  int final;
  int cantidad;
} ColaTrabajos;

void cola_init(ColaTrabajos *cola) {
  cola->frente = 0;
  cola->final = 0;
  cola->cantidad = 0;
}

int cola_encolar(ColaTrabajos *cola, Trabajo t) {
  if (cola->cantidad == CAP_IMP) {
    return 0;
  }
  cola->datos[cola->final] = t;
  cola->final = (cola->final + 1) % CAP_IMP;
  cola->cantidad++;
  return 1;
}

int cola_desencolar(ColaTrabajos *cola, Trabajo *t) {
  if (cola->cantidad == 0) {
    return 0;
  }
  *t = cola->datos[cola->frente];
  cola->frente = (cola->frente + 1) % CAP_IMP;
  cola->cantidad--;
  return 1;
}

13.3 Operaciones del gestor

El gestor recibe dos colas y determina la prioridad al procesar.

typedef struct {
  ColaTrabajos urgente;
  ColaTrabajos normal;
} GestorImpresion;

void gestor_init(GestorImpresion *gestor) {
  cola_init(&gestor->urgente);
  cola_init(&gestor->normal);
}

int agregarTrabajo(GestorImpresion *gestor, const char *archivo, int paginas, int esUrgente) {
  Trabajo t;
  strncpy(t.archivo, archivo, MAX_NOMBRE_DOC - 1);
  t.archivo[MAX_NOMBRE_DOC - 1] = '\0';
  t.paginas = paginas;
  return esUrgente ? cola_encolar(&gestor->urgente, t) : cola_encolar(&gestor->normal, t);
}

int procesarTrabajo(GestorImpresion *gestor, Trabajo *t) {
  if (cola_desencolar(&gestor->urgente, t)) {
    return 1;
  }
  return cola_desencolar(&gestor->normal, t);
}

void mostrarEstado(const GestorImpresion *gestor) {
  printf("Urgentes (%d):\n", gestor->urgente.cantidad);
  for (int i = 0, idx = gestor->urgente.frente; i < gestor->urgente.cantidad; ++i, idx = (idx + 1) % CAP_IMP) {
    printf("  ! %s (%d pags)\n", gestor->urgente.datos[idx].archivo, gestor->urgente.datos[idx].paginas);
  }
  printf("Normales (%d):\n", gestor->normal.cantidad);
  for (int i = 0, idx = gestor->normal.frente; i < gestor->normal.cantidad; ++i, idx = (idx + 1) % CAP_IMP) {
    printf("  - %s (%d pags)\n", gestor->normal.datos[idx].archivo, gestor->normal.datos[idx].paginas);
  }
}

13.4 Interfaz de consola

Un menú similar al del ejemplo anterior permite agregar trabajos, procesarlos y consultar el estado.

void limpiarBuffer(void) {
  int c;
  while ((c = getchar()) != '\n' && c != EOF) {}
}

int main(void) {
  GestorImpresion gestor;
  gestor_init(&gestor);

  int opcion;
  char archivo[MAX_NOMBRE_DOC];
  int paginas;
  Trabajo trabajo;

  do {
    puts("\n1) Agregar trabajo normal");
    puts("2) Agregar trabajo urgente");
    puts("3) Procesar siguiente");
    puts("4) Mostrar estado");
    puts("0) Salir");
    printf("Opcion: ");
    if (scanf("%d", &opcion) != 1) {
      puts("Entrada invalida");
      limpiarBuffer();
      continue;
    }
    limpiarBuffer();
    switch (opcion) {
      case 1:
      case 2:
        printf("Archivo: ");
        if (!fgets(archivo, sizeof archivo, stdin)) break;
        archivo[strcspn(archivo, "\n")] = '\0';
        printf("Paginas: ");
        if (scanf("%d", &paginas) != 1) {
          puts("Valor incorrecto");
          limpiarBuffer();
          break;
        }
        limpiarBuffer();
        if (agregarTrabajo(&gestor, archivo, paginas, opcion == 2)) {
          puts("Trabajo encolado correctamente.");
        } else {
          puts("Cola correspondiente llena.");
        }
        break;
      case 3:
        if (procesarTrabajo(&gestor, &trabajo)) {
          printf("Imprimiendo: %s (%d pags)\n", trabajo.archivo, trabajo.paginas);
        } else {
          puts("No hay trabajos pendientes.");
        }
        break;
      case 4:
        mostrarEstado(&gestor);
        break;
      case 0:
        puts("Fin del gestor");
        break;
      default:
        puts("Opcion no valida");
    }
  } while (opcion != 0);

  return 0;
}

13.5 Código completo para CLion

Copia este archivo único para probar el gestor de impresión sin pegar fragmentos por separado:

#include <stdio.h>
#include <string.h>

#define CAP_IMP 20
#define MAX_NOMBRE_DOC 48

typedef struct {
  char archivo[MAX_NOMBRE_DOC];
  int paginas;
} Trabajo;

typedef struct {
  Trabajo datos[CAP_IMP];
  int frente;
  int final;
  int cantidad;
} ColaTrabajos;

typedef struct {
  ColaTrabajos urgente;
  ColaTrabajos normal;
} GestorImpresion;

void cola_init(ColaTrabajos *cola) {
  cola->frente = 0;
  cola->final = 0;
  cola->cantidad = 0;
}

int cola_encolar(ColaTrabajos *cola, Trabajo t) {
  if (cola->cantidad == CAP_IMP) {
    return 0;
  }
  cola->datos[cola->final] = t;
  cola->final = (cola->final + 1) % CAP_IMP;
  cola->cantidad++;
  return 1;
}

int cola_desencolar(ColaTrabajos *cola, Trabajo *t) {
  if (cola->cantidad == 0) {
    return 0;
  }
  *t = cola->datos[cola->frente];
  cola->frente = (cola->frente + 1) % CAP_IMP;
  cola->cantidad--;
  return 1;
}

void gestor_init(GestorImpresion *gestor) {
  cola_init(&gestor->urgente);
  cola_init(&gestor->normal);
}

int agregarTrabajo(GestorImpresion *gestor, const char *archivo, int paginas, int esUrgente) {
  Trabajo t;
  strncpy(t.archivo, archivo, MAX_NOMBRE_DOC - 1);
  t.archivo[MAX_NOMBRE_DOC - 1] = '\0';
  t.paginas = paginas;
  return esUrgente ? cola_encolar(&gestor->urgente, t) : cola_encolar(&gestor->normal, t);
}

int procesarTrabajo(GestorImpresion *gestor, Trabajo *t) {
  if (cola_desencolar(&gestor->urgente, t)) {
    return 1;
  }
  return cola_desencolar(&gestor->normal, t);
}

void mostrarEstado(const GestorImpresion *gestor) {
  printf("Urgentes (%d):\n", gestor->urgente.cantidad);
  for (int i = 0, idx = gestor->urgente.frente; i < gestor->urgente.cantidad; ++i, idx = (idx + 1) % CAP_IMP) {
    printf("  ! %s (%d pags)\n", gestor->urgente.datos[idx].archivo, gestor->urgente.datos[idx].paginas);
  }
  printf("Normales (%d):\n", gestor->normal.cantidad);
  for (int i = 0, idx = gestor->normal.frente; i < gestor->normal.cantidad; ++i, idx = (idx + 1) % CAP_IMP) {
    printf("  - %s (%d pags)\n", gestor->normal.datos[idx].archivo, gestor->normal.datos[idx].paginas);
  }
}

void limpiarBuffer(void) {
  int c;
  while ((c = getchar()) != '\n' && c != EOF) {}
}

int main(void) {
  GestorImpresion gestor;
  gestor_init(&gestor);

  int opcion;
  char archivo[MAX_NOMBRE_DOC];
  int paginas;
  Trabajo trabajo;

  do {
    puts("\n1) Agregar trabajo normal");
    puts("2) Agregar trabajo urgente");
    puts("3) Procesar siguiente");
    puts("4) Mostrar estado");
    puts("0) Salir");
    printf("Opcion: ");
    if (scanf("%d", &opcion) != 1) {
      puts("Entrada invalida");
      limpiarBuffer();
      continue;
    }
    limpiarBuffer();
    switch (opcion) {
      case 1:
      case 2:
        printf("Archivo: ");
        if (!fgets(archivo, sizeof archivo, stdin)) break;
        archivo[strcspn(archivo, "\n")] = '\0';
        printf("Paginas: ");
        if (scanf("%d", &paginas) != 1) {
          puts("Valor incorrecto");
          limpiarBuffer();
          break;
        }
        limpiarBuffer();
        if (agregarTrabajo(&gestor, archivo, paginas, opcion == 2)) {
          puts("Trabajo encolado correctamente.");
        } else {
          puts("Cola correspondiente llena.");
        }
        break;
      case 3:
        if (procesarTrabajo(&gestor, &trabajo)) {
          printf("Imprimiendo: %s (%d pags)\n", trabajo.archivo, trabajo.paginas);
        } else {
          puts("No hay trabajos pendientes.");
        }
        break;
      case 4:
        mostrarEstado(&gestor);
        break;
      case 0:
        puts("Fin del gestor");
        break;
      default:
        puts("Opcion no valida");
    }
  } while (opcion != 0);

  return 0;
}

13.6 Pruebas sugeridas

  • Enviar más de 20 trabajos urgentes para validar el mensaje de cola llena.
  • Agregar trabajos alternando prioridad y verificar que siempre se procesen primero los urgentes.
  • Simular un apagado guardando las colas en disco antes de salir para practicar serialización.

El tercer ejemplo práctico incorporará hilos y almacenamiento persistente para acercarse a un spool profesional.