diff --git a/pom.xml b/pom.xml index 91c9624..140354a 100644 --- a/pom.xml +++ b/pom.xml @@ -3,6 +3,23 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + + org.apache.commons + commons-io + 1.3.2 + + + junit + junit + 4.12 + + + + + 1.8 + 1.8 + ru.spbau.mit.kravchenkoyura hw diff --git a/src/main/java/ru/spbau/mit/hw4/ClientUI.java b/src/main/java/ru/spbau/mit/hw4/ClientUI.java new file mode 100644 index 0000000..fe33298 --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/ClientUI.java @@ -0,0 +1,159 @@ +package ru.spbau.mit.hw4; +import java.awt.*; +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import javax.swing.*; +import javax.swing.text.JTextComponent; + +public class ClientUI extends JFrame { + TorrentClient client; + Box eastBox; + Box centerBox; + + ClientUI() { + super("main"); + try { + client = new TorrentClient(); + } catch (IOException e) { + return; + } + try { + client.read(new DataInputStream(new FileInputStream("client.info"))); + } catch (IOException e) { + } + + setBounds(100, 100, 700, 700); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + eastBox = Box.createVerticalBox(); + getContentPane().add(eastBox, BorderLayout.EAST); + + centerBox = Box.createVerticalBox(); + getContentPane().add(centerBox, BorderLayout.CENTER); + + Box northBox = Box.createHorizontalBox(); + getContentPane().add(northBox, BorderLayout.NORTH); + + JButton button = new JButton("upload"); + button.addActionListener((e) -> upload()); + northBox.add(button); + + button = new JButton("refresh list"); + button.addActionListener((e) -> evallist()); + northBox.add(button); + + button = new JButton("exit"); + button.addActionListener((e) -> { + try { + client.write(new DataOutputStream(new FileOutputStream("client.info", false))); + } catch (IOException e1) { + } + System.exit(0); + }); + northBox.add(button); + + evallist(); + + setVisible(true); + } + + private void download(PartableFile file) { + System.out.print(client.files.size()); + + HashMap> sids = null; + try { + sids = client.getSids(file.getId()); + } catch (IOException e) { + return; + } + + JFrame frame = new JFrame("chose folder"); + JFileChooser directoryChooser = new JFileChooser(); + directoryChooser.setCurrentDirectory(new File(System.getProperty("user.home"))); + directoryChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + frame.setVisible(true); + frame.add(directoryChooser); + if (directoryChooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) { + try { + file.createFile(directoryChooser.getSelectedFile().getPath()); + RandomAccessFile rAFile = new RandomAccessFile(file.getFile().getPath(), "rw"); + rAFile.setLength(file.getSize()); + }catch (FileNotFoundException e) { + return; + } catch (IOException e) { + return; + } + } + else { + return; + } + frame.setVisible(false); + + JProgressBar pb = new JProgressBar(0, (int) Math.ceil(file.getSize() / (1.0 * TorrentClient.size))); + centerBox.add(new JTextField(file.toString())); + centerBox.add(pb); + centerBox.revalidate(); + + client.files.add(file); + + ArrayList threads = new ArrayList<>(); + for (Map.Entry> part : sids.entrySet()) { + Thread thread = new Thread(() -> { + if (client.downloadPart(part.getKey(), part.getValue(), file)) { + synchronized (pb) { + pb.setValue(pb.getValue() + 1); + } + } + }); + thread.start(); + threads.add(thread); + } + for (Thread thread : threads) { + try { + thread.join(); + } catch (InterruptedException e) { + } + } + } + + + private void evallist() { + Iterable files = null; + try { + files = client.list(); + } catch (IOException e) { + return; + } + eastBox.removeAll(); + for (PartableFile f : files) { + eastBox.add(new JTextField(f.toString())); + JButton button = new JButton("download"); + button.addActionListener(e -> download(f)); + eastBox.add(button); + } + eastBox.revalidate(); + } + + private void upload() { + JFrame frame = new JFrame("chose file"); + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setCurrentDirectory(new File(System.getProperty("user.home"))); + frame.setVisible(true); + int result = fileChooser.showOpenDialog(frame); + frame.add(fileChooser); + if (result == JFileChooser.APPROVE_OPTION) { + try { + client.upload(new PartableFile(fileChooser.getSelectedFile())); + } catch (IOException e) { + } + System.out.println("Selected file: " + fileChooser.getSelectedFile().getAbsolutePath()); + } + frame.setVisible(false); + } + + public static void main(String[] args) throws IOException { + ClientUI ui = new ClientUI(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/spbau/mit/hw4/PartableFile.java b/src/main/java/ru/spbau/mit/hw4/PartableFile.java new file mode 100644 index 0000000..193f85b --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/PartableFile.java @@ -0,0 +1,92 @@ +package ru.spbau.mit.hw4; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +class PartableFile { + private int id; + private File file = null; + private String name = null; + private long size; + ArrayList parts = new ArrayList<>(); + PartableFile(File file) { + this.file = file; + name = file.getName(); + size = file.length(); + for (int i = 0; i * (TorrentClient.size) < file.length(); ++i) { + parts.add(i); + } + } + PartableFile(int id, String name, long size) { + this.id = id; + this.name = name; + this.size = size; + } + + PartableFile(DataInputStream in) throws IOException { + id = in.readInt(); + if (in.readBoolean()) { + file = new File(in.readUTF()); + name = file.getName(); + size = file.length(); + } + else { + name = in.readUTF(); + size = in.readLong(); + } + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + parts.add(in.readInt()); + } + } + + void write(DataOutputStream out) throws IOException { + out.writeInt(id); + if (file != null) { + out.writeBoolean(true); + out.writeUTF(file.getPath()); + } + else { + out.writeBoolean(false); + out.writeUTF(name); + out.writeLong(size); + } + out.writeInt(parts.size()); + for (Integer i : parts) { + out.writeInt(i); + } + } + + void setId(int id) { + this.id = id; + } + String getName() { + return name; + } + long getSize() { + return size; + } + File getFile() { + return file; + } + void createFile(String path) throws IOException { + file = new File(path + "//" + name); + file.getParentFile().mkdirs(); + file.createNewFile(); + } + int getId() { + return id; + } + ArrayList getParts() { + return parts; + } + void addPart(int number) { + parts.add(number); + } + public String toString() { + return String.valueOf(id) + " " + getName() + " " + String.valueOf(getSize()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/spbau/mit/hw4/Sid.java b/src/main/java/ru/spbau/mit/hw4/Sid.java new file mode 100644 index 0000000..478cef8 --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/Sid.java @@ -0,0 +1,23 @@ +package ru.spbau.mit.hw4; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +class Sid { + private byte[] ip; + private int port; + Sid(byte[] ip, int port) { + this.ip = ip; + this.port = port; + } + byte[] ip() { + return ip; + } + int getPort() { + return port; + } + Socket connect() throws IOException { + return new Socket(InetAddress.getByAddress(ip), port); + } +} diff --git a/src/main/java/ru/spbau/mit/hw4/Test.java b/src/main/java/ru/spbau/mit/hw4/Test.java new file mode 100644 index 0000000..b026a70 --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/Test.java @@ -0,0 +1,77 @@ +package ru.spbau.mit.hw4; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.junit.BeforeClass; + +import static org.junit.Assert.assertTrue; + +public class Test { + + @BeforeClass + public static void setUp() throws Exception { + TorrentServer server = new TorrentServer(); + Thread thread = new Thread(server); + thread.setDaemon(true); + thread.start(); + } + + @org.junit.Test + public void onePartTest() throws IOException { + TorrentClient client1 = new TorrentClient(); + TorrentClient client2 = new TorrentClient(); + File in = new File("src/main/resources/from/file1.txt"); + File out = new File("src/main/resources/to/file1.txt"); + int id = client1.upload(new PartableFile(in)); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + client2.download(id, "src/main/resources/to"); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + assertTrue(FileUtils.contentEquals(in, out)); + out.delete(); + } + @org.junit.Test + public void twoPartsTest() throws IOException { + TorrentClient client1 = new TorrentClient(); + TorrentClient client2 = new TorrentClient(); + TorrentClient client3 = new TorrentClient(); + File in = new File("src/main/resources/from/file2.txt"); + File middle = new File("src/main/resources/middle/file2.txt"); + File out = new File("src/main/resources/to/file2.txt"); + int id = client1.upload(new PartableFile(in)); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + client2.download(id, "src/main/resources/middle"); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + client1.files.get(0).parts.clear(); + client1.files.get(0).parts.add(0); + client2.files.get(0).parts.clear(); + client2.files.get(0).parts.add(1); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + client3.download(id, "src/main/resources/to"); + try { + Thread.sleep(TorrentClient.time * 2); + } catch (InterruptedException e) { + } + assertTrue(FileUtils.contentEquals(in, out)); + assertTrue(FileUtils.contentEquals(in, middle)); + assertTrue(FileUtils.contentEquals(middle, out)); + middle.delete(); + out.delete(); + } +} diff --git a/src/main/java/ru/spbau/mit/hw4/TorrentClient.java b/src/main/java/ru/spbau/mit/hw4/TorrentClient.java new file mode 100644 index 0000000..b7749f4 --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/TorrentClient.java @@ -0,0 +1,353 @@ +package ru.spbau.mit.hw4; + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.*; + +public class TorrentClient { + private Sid server; + private ClientSid client; + private Socket serverConnection; + private DataInputStream in; + private DataOutputStream out; + ArrayList files; + static int time = 3000; + static int size = 1 << 10; + + private class ClientSid implements Runnable { + private ServerSocket serverSocket; + + @Override + public void run() { + while (true) { + Thread thread = null; + try { + thread = new Thread(new Handler(serverSocket.accept())); + thread.setDaemon(true); + thread.start(); + } catch (IOException e) { + } + } + } + + private class Handler implements Runnable{ + DataInputStream in; + DataOutputStream out; + public Handler(Socket connection) throws IOException { + in = new DataInputStream(connection.getInputStream()); + out = new DataOutputStream(connection.getOutputStream()); + } + + @Override + public void run() { + try { + int id; + switch(in.readByte()) { + case 1: + id = in.readInt(); + for (PartableFile file : files) { + if (file.getId() == id) { + out.writeInt(file.getParts().size()); + for (int number : file.getParts()) { + out.writeInt(number); + } + break; + } + } + break; + case 2: + id = in.readInt(); + int part = in.readInt(); + PartableFile file = null; + for(PartableFile f : files) { + if (f.getId() == id) { + file = f; + break; + } + } + RandomAccessFile f = new RandomAccessFile(file.getFile(), "rw"); + byte[] buffer = new byte[size]; + f.seek((size) * part); + int len = f.read(buffer, 0, (size)); + out.write(buffer, 0, len); + break; + } + } catch (IOException e) { + } + } + } + public ClientSid() throws IOException { + serverSocket = new ServerSocket(0); + } + public int getPort() { + return serverSocket.getLocalPort(); + } + } + + Iterable list() throws IOException { + synchronized (serverConnection) { + ArrayList result = new ArrayList<>(); + out.writeByte(1); + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + result.add(new PartableFile(in.readInt(), in.readUTF(), in.readLong())); + } + return result; + } + } + + int upload(PartableFile f) throws IOException { + synchronized (serverConnection) { + out.writeByte(2); + out.writeUTF(f.getName()); + out.writeLong(f.getSize()); + files.add(f); + int id = in.readInt(); + f.setId(id); + return id; + } + } + + HashMap> getSids(int id) throws IOException { + HashMap> sids = new HashMap<>(); + ArrayList threads = new ArrayList<>(); + Iterable source = sources(id); + for (Sid sid : source) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + Socket connection = null; + DataInputStream in = null; + DataOutputStream out = null; + + try { + connection = sid.connect(); + in = new DataInputStream(connection.getInputStream()); + out = new DataOutputStream(connection.getOutputStream()); + out.writeByte(1); + out.writeInt(id); + int n = in.readInt(); + for (int i = 0; i < n; ++i) { + int cur = in.readInt(); + synchronized (sids) { + if (sids.get(cur) == null) { + sids.put(cur, new ArrayList<>()); + } + sids.get(cur).add(sid); + } + } + } catch (IOException e) { + } + } + }); + thread.start(); + threads.add(thread); + } + for (Thread thread : threads) { + try { + thread.join(); + } catch (InterruptedException e) { + } + } + return sids; + } + + boolean downloadPart(int partNumber, ArrayList sids, PartableFile file) { + Socket connection = null; + DataInputStream in = null; + DataOutputStream out = null; + for (Sid sid : sids) { + try { + connection = sid.connect(); + in = new DataInputStream(connection.getInputStream()); + out = new DataOutputStream(connection.getOutputStream()); + out.writeByte(2); + out.writeInt(file.getId()); + out.writeInt(partNumber); + byte[] buffer = new byte[1 << 10]; + int len = in.read(buffer); + RandomAccessFile rAFile = new RandomAccessFile(file.getFile().getPath(), "rw"); + rAFile.seek((1 << 10) * partNumber); + rAFile.write(buffer, 0, len); + file.addPart(partNumber); + return true; + } catch (IOException e) { + } + } + return false; + } + + void downloadFile(Map> sids, PartableFile file) { + ArrayList threads = new ArrayList<>(); + for (Map.Entry> part : sids.entrySet()) { + Thread thread = new Thread(() -> downloadPart(part.getKey(), part.getValue(), file)); + thread.start(); + threads.add(thread); + } + for (Thread thread : threads) { + try { + thread.join(); + } catch (InterruptedException e) { + } + } + } + + void download(int id, String path) throws IOException { + PartableFile file = null; + for (PartableFile f : list()) { + if (f.getId() == id) { + file = f; + break; + } + } + files.add(file); + + final PartableFile finalFile = file; + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + HashMap> sids = null; + try { + sids = getSids(id); + } catch (IOException e) { + return; + } + RandomAccessFile file = null; + try { + finalFile.createFile(path); + file = new RandomAccessFile(finalFile.getFile().getPath(), "rw"); + file.setLength(finalFile.getSize()); + } catch (FileNotFoundException e) { + } catch (IOException e) { + } + downloadFile(sids, finalFile); + } + }); + thread.setDaemon(true); + thread.start(); + } + + private Iterable sources(int id) throws IOException { + synchronized (serverConnection) { + ArrayList result = new ArrayList<>(); + out.writeByte(3); + out.writeInt(id); + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + byte[] ip = new byte[4]; + for (int j = 0; j < 4; ++j) { + ip[j] = in.readByte(); + } + result.add(new Sid(ip, in.readInt())); + } + return result; + } + } + + private void setUpdate() { + new Timer(true).schedule(new TimerTask() { + @Override + public void run() { + synchronized (serverConnection) { + ArrayList oldFiles = new ArrayList(files); + try { + out.writeByte(4); + out.writeInt(client.getPort()); + out.writeInt(oldFiles.size()); + for (PartableFile file : oldFiles) { + out.writeInt(file.getId()); + } + in.readBoolean(); + } catch (IOException e) { + } + } + } + }, 0, time); + } + + void write(DataOutputStream out) throws IOException { + out.writeInt(files.size()); + for (PartableFile file : files) { + file.write(out); + } + } + + void read(DataInputStream in) throws IOException { + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + files.add(new PartableFile(in)); + } + } + + TorrentClient() throws IOException { + serverConnection = new Socket("127.0.0.1", 8081); + in = new DataInputStream(serverConnection.getInputStream()); + out = new DataOutputStream(serverConnection.getOutputStream()); + client = new ClientSid(); + files = new ArrayList<>(); + Thread thread = new Thread(client); + thread.setDaemon(true); + thread.start(); + setUpdate(); + } + + public static void main(String[] args) { + TorrentClient client = null; + try { + client = new TorrentClient(); + } catch (IOException e) { + System.out.println("fail"); + System.exit(0); + } + try { + client.read(new DataInputStream(new FileInputStream("client.info"))); + } catch (IOException e) { + System.out.println("can't read file"); + } + Scanner in = new Scanner(System.in); + while (true) { + String[] command = in.nextLine().split(" "); + if (command.length == 0) { + System.out.println("wrong"); + continue; + } + try { + switch (command[0]) { + case ("list"): + for (PartableFile file : client.list()) { + System.out.println(file.toString()); + } + break; + case ("upload"): + if (command.length < 2) { + System.out.println("wrong"); + continue; + } + System.out.println(client.upload(new PartableFile(new File(command[1])))); + break; + case ("download"): + if (command.length < 3) { + System.out.println("wrong"); + continue; + } + client.download(Integer.valueOf(command[1]), command[2]); + break; + case ("exit"): + try { + client.write(new DataOutputStream(new FileOutputStream("client.info", false))); + } catch (FileNotFoundException e) { + System.out.println("can't find file"); + } catch (IOException e) { + System.out.println("can't write file"); + } + System.exit(0); + default: + System.out.println("wrong"); + } + }catch (IOException e) { + System.out.println("error"); + } + } + } +} diff --git a/src/main/java/ru/spbau/mit/hw4/TorrentServer.java b/src/main/java/ru/spbau/mit/hw4/TorrentServer.java new file mode 100644 index 0000000..9d7d05b --- /dev/null +++ b/src/main/java/ru/spbau/mit/hw4/TorrentServer.java @@ -0,0 +1,166 @@ +package ru.spbau.mit.hw4; + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.*; + +public class TorrentServer implements Runnable{ + private ServerSocket serverSocket; + private HashMap> newSids; + private HashMap> oldSids; + private ArrayList files; + + @Override + public void run() { + while (true) { + Thread thread = null; + try { + thread = new Thread(new Handler(serverSocket.accept())); + thread.setDaemon(true); + thread.start(); + } catch (IOException e) { + } + } + } + + private class Handler implements Runnable{ + DataInputStream in; + DataOutputStream out; + byte[] ip; + public Handler(Socket connection) throws IOException { + in = new DataInputStream(connection.getInputStream()); + out = new DataOutputStream(connection.getOutputStream()); + ip = connection.getInetAddress().getAddress(); + } + @Override + public void run() { + while (true) { + int id; + try { + switch (in.readByte()) { + case 1: + out.writeInt(files.size()); + for (PartableFile file : files) { + out.writeInt(file.getId()); + out.writeUTF(file.getName()); + out.writeLong(file.getSize()); + } + break; + case 2: + PartableFile file = new PartableFile(files.size(), in.readUTF(), in.readLong()); + out.writeInt(files.size()); + files.add(file); + break; + case 3: + synchronized (oldSids) { + id = in.readInt(); + if (oldSids.get(id) == null) { + out.writeInt(0); + break; + } + out.writeInt(oldSids.get(id).size()); + for (Sid sid : oldSids.get(id)) { + for (byte b : sid.ip()) { + out.writeByte(b); + } + out.writeInt(sid.getPort()); + } + } + break; + case 4: + synchronized (newSids) { + int port = in.readInt(); + Sid sid = new Sid(ip, port); + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + id = in.readInt(); + if (newSids.get(id) == null) { + newSids.put(id, new HashSet<>()); + } + newSids.get(id).add(sid); + } + out.writeBoolean(true); + break; + } + } + } catch (IOException e) { + } + } + } + }; + + private void setClear() { + new Timer(true).schedule(new TimerTask() { + @Override + public void run() { + synchronized (oldSids) { + synchronized (newSids) { + oldSids = newSids; + newSids = new HashMap<>(); + } + } + } + }, 0, TorrentClient.time * 2); + } + + private void write(DataOutputStream out) throws IOException { + out.writeInt(files.size()); + for (PartableFile file : files) { + file.write(out); + } + } + + private void read(DataInputStream in) throws IOException { + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + files.add(new PartableFile(in)); + } + } + + TorrentServer() throws IOException { + serverSocket = new ServerSocket(8081); + files = new ArrayList<>(); + newSids = new HashMap<>(); + oldSids = new HashMap<>(); + setClear(); + } + public static void main(String[] args) { + TorrentServer server = null; + try { + server = new TorrentServer(); + } catch (IOException e) { + System.out.println("fail"); + System.exit(0); + } + try { + server.read(new DataInputStream(new FileInputStream("server.info"))); + } catch (IOException e) { + System.out.println("can't read file"); + } + Scanner in = new Scanner(System.in); + while (true) { + String[] command = in.nextLine().split(" "); + if (command.length == 0) { + System.out.println("wrong"); + continue; + } + switch (command[0]) { + case "start": + new Thread(server).start(); + break; + case "exit": + try { + server.write(new DataOutputStream(new FileOutputStream("server.info", false))); + } catch (FileNotFoundException e) { + System.out.println("can't find file"); + } catch (IOException e) { + System.out.println("can't write file"); + } + System.exit(0); + default: + System.out.println("wrong"); + } + } + } +} diff --git a/src/main/resources/from/file1.txt b/src/main/resources/from/file1.txt new file mode 100644 index 0000000..a85e37f --- /dev/null +++ b/src/main/resources/from/file1.txt @@ -0,0 +1,18 @@ + ,----------------, ,---------, + ,-----------------------, ," ,"| + ," ,"| ," ," | + +-----------------------+ | ," ," | + | .-----------------. | | +---------+ | + | | | | | | -==----'| | + | | I LOVE DOS! | | | | | | + | | Bad command or | | |/----|`---= | | + | | C:\>_ | | | ,/|==== ooo | ; + | | | | | // |(((( [33]| ," + | `-----------------' |," .;'| |(((( | ," + +-----------------------+ ;; | | |," + /_)______________(_/ //' | +---------+ + ___________________________/___ `, + / oooooooooooooooo .o. oooo /, \,"----------- + / ==ooooooooooooooo==.o. ooo= // ,`\--{)B ," +/_==__==========__==_ooo__ooo=_/' /___________," +`-----------------------------' \ No newline at end of file diff --git a/src/main/resources/from/file2.txt b/src/main/resources/from/file2.txt new file mode 100644 index 0000000..e6cc8a2 --- /dev/null +++ b/src/main/resources/from/file2.txt @@ -0,0 +1,36 @@ + ,----------------, ,---------, + ,-----------------------, ," ,"| + ," ,"| ," ," | + +-----------------------+ | ," ," | + | .-----------------. | | +---------+ | + | | | | | | -==----'| | + | | I LOVE DOS! | | | | | | + | | Bad command or | | |/----|`---= | | + | | C:\>_ | | | ,/|==== ooo | ; + | | | | | // |(((( [33]| ," + | `-----------------' |," .;'| |(((( | ," + +-----------------------+ ;; | | |," + /_)______________(_/ //' | +---------+ + ___________________________/___ `, + / oooooooooooooooo .o. oooo /, \,"----------- + / ==ooooooooooooooo==.o. ooo= // ,`\--{)B ," +/_==__==========__==_ooo__ooo=_/' /___________," +`-----------------------------' + ,----------------, ,---------, + ,-----------------------, ," ,"| + ," ,"| ," ," | + +-----------------------+ | ," ," | + | .-----------------. | | +---------+ | + | | | | | | -==----'| | + | | I LOVE DOS! | | | | | | + | | Bad command or | | |/----|`---= | | + | | C:\>_ | | | ,/|==== ooo | ; + | | | | | // |(((( [33]| ," + | `-----------------' |," .;'| |(((( | ," + +-----------------------+ ;; | | |," + /_)______________(_/ //' | +---------+ + ___________________________/___ `, + / oooooooooooooooo .o. oooo /, \,"----------- + / ==ooooooooooooooo==.o. ooo= // ,`\--{)B ," +/_==__==========__==_ooo__ooo=_/' /___________," +`-----------------------------'