Initial commit
This commit is contained in:
22
LICENSE.md
Normal file
22
LICENSE.md
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 David Álvarez González
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
50
README.md
Normal file
50
README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# MusicAdvisor
|
||||
CLI app using Spotify API to create a personal music advisor that makes preference-based suggestions and even shares links to new releases and featured playlists.
|
||||
|
||||
## How to run
|
||||
In root of the project:
|
||||
|
||||
> gradle run -q --console=plain
|
||||
|
||||
|
||||
## Parameters
|
||||
`-access`
|
||||
|
||||
Authorization server path. If it isn't set, default server is https://accounts.spotify.com
|
||||
|
||||
`-resource`
|
||||
|
||||
API server path. If it isn't set, default server is https://api.spotify.com
|
||||
|
||||
`-page`
|
||||
|
||||
Number of entries that should be shown on a page. If it isn't set, default value is 5.
|
||||
|
||||
## Commands
|
||||
`auth` Creates a link to confirm access of the app.
|
||||
|
||||

|
||||
|
||||
`featured` Gets a paginated list of Spotify-featured playlists with their links fetched from API.
|
||||
|
||||

|
||||
|
||||
`new` Gets a paginated list of new albums with artists and links on Spotify.
|
||||
|
||||

|
||||
|
||||
`categories` Gets a paginated list of all available categories on Spotify (just their names)
|
||||
|
||||

|
||||
|
||||
`playlists C_NAME` Gets a paginated list containing playlists of this category (where C_NAME is the name of category) and their links on Spotify.
|
||||
|
||||

|
||||
|
||||
`next` Goes to next page.
|
||||
|
||||
`prev` Goes to previous page.
|
||||
|
||||
`exit` Exits the program.
|
||||
|
||||

|
||||
29
build.gradle
Normal file
29
build.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'application'
|
||||
|
||||
group 'advisor'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
sourceCompatibility = 11
|
||||
mainClassName = 'advisor.Main'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.code.gson:gson:+'
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Main-Class' : 'advisor.Main'
|
||||
}
|
||||
from {
|
||||
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
}
|
||||
|
||||
run{
|
||||
standardInput = System.in
|
||||
}
|
||||
97
src/advisor/Main.java
Normal file
97
src/advisor/Main.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package advisor;
|
||||
|
||||
import advisor.auth.Authorization;
|
||||
import advisor.auth.LocalServer;
|
||||
import advisor.config.Params;
|
||||
import advisor.controller.AdvisorController;
|
||||
import advisor.model.AdvisorModel;
|
||||
import advisor.view.AdvisorView;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Scanner sc = new Scanner(System.in);
|
||||
String accessServer = Params.ACCESS_SERVER;
|
||||
String resourceServer = Params.RESOURCE_SERVER;
|
||||
boolean userAuth = false, exit = false, access = false, resource = false, page = false;
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].equals("-access") && !access) {
|
||||
accessServer = args[i + 1];
|
||||
access = true;
|
||||
i++;
|
||||
}
|
||||
if (args[i].equals("-resource") && !resource) {
|
||||
resourceServer = args[i + 1];
|
||||
resource = true;
|
||||
i++;
|
||||
}
|
||||
if (args[i].equals("-page") && !page) {
|
||||
Params.RESULTS_PER_PAGE = Integer.parseInt(args[i + 1]);
|
||||
page = true;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
AdvisorController advisorController = new AdvisorController(resourceServer);
|
||||
|
||||
while (!exit) {
|
||||
String[] input = sc.nextLine().split("\\s+");
|
||||
if (!userAuth) {
|
||||
if (input[0].equals("auth")) {
|
||||
LocalServer localServer = new LocalServer(accessServer);
|
||||
localServer.startServer();
|
||||
localServer.getCode();
|
||||
localServer.stopServer();
|
||||
Authorization authorization = new Authorization(accessServer);
|
||||
authorization.getToken();
|
||||
userAuth = true;
|
||||
AdvisorView.printMessage(Params.SUCCESS);
|
||||
} else {
|
||||
AdvisorView.printMessage(Params.ANSWER_DENIED_ACCESS);
|
||||
}
|
||||
} else {
|
||||
switch (input[0]) {
|
||||
case "featured":
|
||||
AdvisorView.print(advisorController.getFeaturedPlaylists());
|
||||
break;
|
||||
case "new":
|
||||
AdvisorView.print(advisorController.getNewReleases());
|
||||
break;
|
||||
case "categories":
|
||||
AdvisorView.print(advisorController.getCategories());
|
||||
break;
|
||||
case "playlists":
|
||||
StringJoiner sj = new StringJoiner(" ");
|
||||
for (int i = 1; i < input.length; i++) {
|
||||
sj.add(input[i]);
|
||||
}
|
||||
String categoryName = sj.toString();
|
||||
List<AdvisorModel> categoryPlaylists = advisorController.getCategoryPlaylists(categoryName);
|
||||
if (categoryPlaylists != null) {
|
||||
AdvisorView.print(categoryPlaylists);
|
||||
} else {
|
||||
AdvisorView.printMessage(Params.UNKNOWN_CATEGORY_NAME);
|
||||
}
|
||||
break;
|
||||
case "next":
|
||||
AdvisorView.printNextPage();
|
||||
break;
|
||||
case "prev":
|
||||
AdvisorView.printPrevPage();
|
||||
break;
|
||||
case "exit":
|
||||
AdvisorView.printMessage(Params.GOODBYE);
|
||||
exit = true;
|
||||
break;
|
||||
default:
|
||||
AdvisorView.printMessage(Params.INCORRECT_COMMAND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/advisor/auth/Authorization.java
Normal file
42
src/advisor/auth/Authorization.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package advisor.auth;
|
||||
|
||||
import advisor.config.Params;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class Authorization {
|
||||
|
||||
private static String accessServer;
|
||||
|
||||
public Authorization(String accessServer) {
|
||||
Authorization.accessServer = accessServer;
|
||||
}
|
||||
|
||||
public void getToken() throws IOException, InterruptedException {
|
||||
|
||||
System.out.println(Params.MAKING_HTTP_REQUEST_FOR_TOKEN);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.POST(HttpRequest.BodyPublishers.ofString(
|
||||
"client_id=" + Params.CLIENT_ID
|
||||
+ "&client_secret=" + Params.CLIENT_SECRET
|
||||
+ "&grant_type=" + Params.GRANT_TYPE
|
||||
+ "&code=" + Params.AUTH_CODE
|
||||
+ "&redirect_uri=" + Params.REDIRECT_URI))
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.uri(URI.create(accessServer + Params.TOKEN_PART))
|
||||
.build();
|
||||
|
||||
HttpClient client = HttpClient.newBuilder().build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
JsonObject responseJson = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
Params.TOKEN_CODE = responseJson.get("access_token").getAsString();
|
||||
}
|
||||
}
|
||||
61
src/advisor/auth/LocalServer.java
Normal file
61
src/advisor/auth/LocalServer.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package advisor.auth;
|
||||
|
||||
import advisor.config.Params;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class LocalServer {
|
||||
|
||||
private static String accessServer;
|
||||
private static HttpServer httpServer;
|
||||
|
||||
public LocalServer(String accessServer) {
|
||||
LocalServer.accessServer = accessServer;
|
||||
}
|
||||
|
||||
public void startServer() throws IOException {
|
||||
httpServer = HttpServer.create();
|
||||
httpServer.bind(new InetSocketAddress(8080), 0);
|
||||
httpServer.start();
|
||||
}
|
||||
|
||||
public void getCode() throws InterruptedException {
|
||||
System.out.println("use this link to request the access code:");
|
||||
System.out.println(accessServer + Params.AUTHORIZE_PART
|
||||
+ "?client_id=" + Params.CLIENT_ID
|
||||
+ "&redirect_uri=" + Params.REDIRECT_URI
|
||||
+ "&response_type=" + Params.RESPONSE_TYPE);
|
||||
System.out.println("waiting for code...");
|
||||
|
||||
httpServer.createContext("/",
|
||||
exchange -> {
|
||||
String code = exchange.getRequestURI().getQuery();
|
||||
String result, answer;
|
||||
|
||||
if (code != null && code.contains("code")) {
|
||||
Params.AUTH_CODE = code.substring(5);
|
||||
result = "Got the code. Return back to your program.";
|
||||
answer = "code received";
|
||||
} else {
|
||||
result = "Not found authorization code. Try again.";
|
||||
answer = "code not received";
|
||||
}
|
||||
|
||||
exchange.sendResponseHeaders(200, result.length());
|
||||
exchange.getResponseBody().write(result.getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
|
||||
System.out.println(answer);
|
||||
}
|
||||
);
|
||||
while (Params.AUTH_CODE.equals("")) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopServer() {
|
||||
httpServer.stop(10);
|
||||
}
|
||||
}
|
||||
27
src/advisor/config/Params.java
Normal file
27
src/advisor/config/Params.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package advisor.config;
|
||||
|
||||
public class Params {
|
||||
public static final String CLIENT_ID = "dd6e7d19d2624ee48db69174ac093a2f";
|
||||
public static final String CLIENT_SECRET = "84fcf5eab40a4854a722bd95802cec5c";
|
||||
public static final String AUTHORIZE_PART = "/authorize";
|
||||
public static final String RESPONSE_TYPE = "code";
|
||||
public static final String TOKEN_PART = "/api/token";
|
||||
public static final String GRANT_TYPE = "authorization_code";
|
||||
public static final String ACCESS_SERVER = "https://accounts.spotify.com";
|
||||
public static final String RESOURCE_SERVER = "https://api.spotify.com";
|
||||
public static final String NEW_RELEASES= "/v1/browse/new-releases";
|
||||
public static final String MAKING_HTTP_REQUEST_FOR_TOKEN= "making http request for access_token...";
|
||||
public static final String FEATURED_PLAYLISTS= "/v1/browse/featured-playlists";
|
||||
public static final String CATEGORIES = "/v1/browse/categories";
|
||||
public static final String PLAYLISTS= "/playlists";
|
||||
public static final String REDIRECT_URI = "http://localhost:8080";
|
||||
public static final String SUCCESS = "---SUCCESS---";
|
||||
public static final String ANSWER_DENIED_ACCESS = "Please, provide access for application.";
|
||||
public static final String UNKNOWN_CATEGORY_NAME = "Unknown category name.";
|
||||
public static final String GOODBYE = "---GOODBYE!---";
|
||||
public static final String INCORRECT_COMMAND = "Incorrect command. Try again.";
|
||||
public static final String NO_MORE_PAGES = "No more pages.";
|
||||
public static String AUTH_CODE = "";
|
||||
public static String TOKEN_CODE = "";
|
||||
public static int RESULTS_PER_PAGE = 5;
|
||||
}
|
||||
112
src/advisor/controller/AdvisorController.java
Normal file
112
src/advisor/controller/AdvisorController.java
Normal file
@@ -0,0 +1,112 @@
|
||||
package advisor.controller;
|
||||
|
||||
import advisor.model.AdvisorModel;
|
||||
import advisor.config.Params;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class AdvisorController {
|
||||
private static String resourceServer;
|
||||
|
||||
public AdvisorController(String resourceServer) {
|
||||
AdvisorController.resourceServer = resourceServer;
|
||||
}
|
||||
|
||||
public List<AdvisorModel> getNewReleases() throws IOException, InterruptedException {
|
||||
List<AdvisorModel> newReleases = new ArrayList<>();
|
||||
HttpRequest newReleasesRequest = createRequest(resourceServer + Params.NEW_RELEASES);
|
||||
for (JsonElement item: Objects.requireNonNull(getJsonItems(newReleasesRequest, "albums"))) {
|
||||
AdvisorModel album = new AdvisorModel();
|
||||
album.setAlbum(item.getAsJsonObject().get("name").toString());
|
||||
StringJoiner joiner = new StringJoiner(", ", "[", "]");
|
||||
for (JsonElement artist: item.getAsJsonObject().getAsJsonArray("artists")) {
|
||||
String artistName = artist.getAsJsonObject().get("name").getAsString();
|
||||
joiner.add(artistName);
|
||||
}
|
||||
album.setArtists(String.valueOf(joiner));
|
||||
album.setLink(item.getAsJsonObject().get("external_urls").getAsJsonObject().get("spotify").toString());
|
||||
newReleases.add(album);
|
||||
}
|
||||
return newReleases;
|
||||
}
|
||||
|
||||
public List<AdvisorModel> getFeaturedPlaylists() throws IOException, InterruptedException {
|
||||
List<AdvisorModel> featuredPlaylists = new ArrayList<>();
|
||||
HttpRequest featuredPlaylistsRequest = createRequest(resourceServer + Params.FEATURED_PLAYLISTS);
|
||||
return getPlaylists(featuredPlaylists, featuredPlaylistsRequest);
|
||||
}
|
||||
|
||||
private List<AdvisorModel> getPlaylists(List<AdvisorModel> categoryPlaylists, HttpRequest categoryPlaylistsRequest) throws IOException, InterruptedException {
|
||||
for (JsonElement item: Objects.requireNonNull(getJsonItems(categoryPlaylistsRequest, "playlists"))) {
|
||||
AdvisorModel categoryPlaylist = new AdvisorModel();
|
||||
categoryPlaylist.setAlbum(item.getAsJsonObject().get("name").toString());
|
||||
categoryPlaylist.setLink(item.getAsJsonObject().get("external_urls").getAsJsonObject().get("spotify").toString());
|
||||
categoryPlaylists.add(categoryPlaylist);
|
||||
}
|
||||
return categoryPlaylists;
|
||||
}
|
||||
|
||||
public List<AdvisorModel> getCategories() throws IOException, InterruptedException {
|
||||
List<AdvisorModel> categories = new ArrayList<>();
|
||||
HttpRequest categoriesRequest = createRequest(resourceServer + Params.CATEGORIES);
|
||||
for (JsonElement item: Objects.requireNonNull(getJsonItems(categoriesRequest, "categories"))) {
|
||||
AdvisorModel category = new AdvisorModel();
|
||||
category.setAlbum(item.getAsJsonObject().get("name").toString());
|
||||
categories.add(category);
|
||||
}
|
||||
return categories;
|
||||
}
|
||||
|
||||
public List<AdvisorModel> getCategoryPlaylists(String categoryName) throws IOException, InterruptedException {
|
||||
List<AdvisorModel> categoryPlaylists = new ArrayList<>();
|
||||
String categoryID = getCategoryIdByCategoryName(categoryName);
|
||||
HttpRequest categoryPlaylistsRequest =
|
||||
createRequest(resourceServer + Params.CATEGORIES + "/" + categoryID + Params.PLAYLISTS);
|
||||
return (categoryID == null) ? null : getPlaylists(categoryPlaylists, categoryPlaylistsRequest);
|
||||
}
|
||||
|
||||
private String getCategoryIdByCategoryName(String categoryName) throws IOException, InterruptedException {
|
||||
HttpRequest categories = createRequest(resourceServer + Params.CATEGORIES);
|
||||
JsonArray items = getJsonItems(categories, "categories");
|
||||
assert items != null;
|
||||
for(JsonElement item: items){
|
||||
String name = item.getAsJsonObject().get("name").getAsString();
|
||||
if(categoryName.equalsIgnoreCase(name)){
|
||||
return item.getAsJsonObject().get("id").getAsString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private JsonArray getJsonItems(HttpRequest request, String element) throws IOException, InterruptedException {
|
||||
HttpClient client = HttpClient.newBuilder().build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
JsonObject responseJson = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
if (responseJson.get("error") == null){
|
||||
JsonObject featured = responseJson.getAsJsonObject(element);
|
||||
return featured.getAsJsonArray("items");
|
||||
}
|
||||
System.out.println(responseJson.getAsJsonObject("error").get("message").getAsString());
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HttpRequest createRequest(String requestedFeatureURL) {
|
||||
return HttpRequest.newBuilder()
|
||||
.header("Authorization", "Bearer " + Params.TOKEN_CODE)
|
||||
.uri(URI.create(requestedFeatureURL))
|
||||
.GET()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
28
src/advisor/model/AdvisorModel.java
Normal file
28
src/advisor/model/AdvisorModel.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package advisor.model;
|
||||
|
||||
public class AdvisorModel {
|
||||
String album = null;
|
||||
String artists = null;
|
||||
String link = null;
|
||||
|
||||
public void setAlbum(String album) {
|
||||
this.album = album;
|
||||
}
|
||||
|
||||
public void setArtists(String authors) {
|
||||
this.artists = authors;
|
||||
}
|
||||
|
||||
public void setLink(String link) {
|
||||
this.link = link;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder info = new StringBuilder();
|
||||
if (album != null) { info.append(album).append("\n"); }
|
||||
if (artists != null) { info.append(artists).append("\n"); }
|
||||
if (link != null) { info.append(link).append("\n"); }
|
||||
return info.toString().replaceAll("\"", "");
|
||||
}
|
||||
}
|
||||
BIN
src/advisor/resources/auth.png
Normal file
BIN
src/advisor/resources/auth.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
src/advisor/resources/categories.png
Normal file
BIN
src/advisor/resources/categories.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src/advisor/resources/exit.png
Normal file
BIN
src/advisor/resources/exit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/advisor/resources/featured.png
Normal file
BIN
src/advisor/resources/featured.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
src/advisor/resources/new.png
Normal file
BIN
src/advisor/resources/new.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
src/advisor/resources/playlists.png
Normal file
BIN
src/advisor/resources/playlists.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
55
src/advisor/view/AdvisorView.java
Normal file
55
src/advisor/view/AdvisorView.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package advisor.view;
|
||||
|
||||
import advisor.model.AdvisorModel;
|
||||
import advisor.config.Params;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AdvisorView {
|
||||
static int elem;
|
||||
static int page;
|
||||
static List<AdvisorModel> data;
|
||||
static int pagesCount;
|
||||
|
||||
public static void printMessage(String message) {
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
public static void print(List<AdvisorModel> data) {
|
||||
elem -= Params.RESULTS_PER_PAGE;
|
||||
page = 0;
|
||||
AdvisorView.data = data;
|
||||
pagesCount = data.size() / Params.RESULTS_PER_PAGE;
|
||||
pagesCount += data.size() % Params.RESULTS_PER_PAGE != 0 ? 1 : 0;
|
||||
|
||||
printNextPage();
|
||||
}
|
||||
|
||||
public static void printNextPage() {
|
||||
if (page >= pagesCount) {
|
||||
System.out.println(Params.NO_MORE_PAGES);
|
||||
} else {
|
||||
elem += Params.RESULTS_PER_PAGE;
|
||||
page++;
|
||||
print();
|
||||
}
|
||||
}
|
||||
|
||||
public static void printPrevPage() {
|
||||
if (page == 1) {
|
||||
System.out.println(Params.NO_MORE_PAGES);
|
||||
} else {
|
||||
elem -= Params.RESULTS_PER_PAGE;
|
||||
page--;
|
||||
print();
|
||||
}
|
||||
}
|
||||
|
||||
public static void print() {
|
||||
data.stream()
|
||||
.skip(elem)
|
||||
.limit(Params.RESULTS_PER_PAGE)
|
||||
.forEach(System.out::println);
|
||||
System.out.printf("---PAGE %d OF %d---\n", page, pagesCount);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user