├── ApiController.cs ├── BlockTransfer.cs ├── Client.cs ├── CloudChiaPlotter.csproj ├── PlotWriter.cs ├── Program.cs ├── Properties └── PublishProfiles │ ├── linux-arm.pubxml │ ├── linux-arm64.pubxml │ ├── linux-x64.pubxml │ ├── osx-x64.pubxml │ └── win-x64.pubxml ├── README.md ├── _config.yml ├── cloudchiaplotter.sln ├── create.png ├── gui.png ├── gui.ps1 ├── step1.png ├── step2.png ├── step3.png ├── step4.png └── windows-gui.md /ApiController.cs: -------------------------------------------------------------------------------- 1 | /* copyright @ anyplots.com, All rights reserved, visit https://anyplots.com/ for more information */ 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Net; 6 | using System.IO; 7 | using System.Threading; 8 | using System.Diagnostics; 9 | using Newtonsoft.Json; 10 | 11 | 12 | using System.Net.Sockets; 13 | namespace anyplots 14 | { 15 | static class ApiController 16 | { 17 | public static int ClientBandwidth = 0; 18 | public static string ProjectToken = "", ClientToken = ""; 19 | public static string Ext { get { return ClientToken.Substring(0, 8); } } 20 | static ApiController() 21 | { 22 | ServicePointManager.DefaultConnectionLimit = 10000; 23 | ServicePointManager.MaxServicePoints = 10000; 24 | ThreadPool.SetMaxThreads(1024, 1024); 25 | } 26 | static string DownloadData(string address, string postdata, int timeout) 27 | { 28 | HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(address); 29 | req.Timeout = timeout; 30 | req.ProtocolVersion = HttpVersion.Version11; 31 | req.UserAgent = "CloudChiaPlotter"; 32 | req.AllowReadStreamBuffering = true; 33 | req.KeepAlive = true; 34 | req.Method = "POST"; 35 | ServicePoint point = req.ServicePoint; 36 | req.AutomaticDecompression = DecompressionMethods.GZip; 37 | if (!string.IsNullOrEmpty(postdata)) 38 | { 39 | req.ContentType = "application/json; charset=UTF-8"; 40 | req.Accept = "application/json"; 41 | using (Stream stream = req.GetRequestStream()) 42 | { 43 | Byte[] bytes_ = Encoding.UTF8.GetBytes(postdata); 44 | stream.Write(bytes_, 0, bytes_.Length); 45 | } 46 | } 47 | IAsyncResult ar = req.BeginGetResponse(null, null); 48 | if (ar.AsyncWaitHandle.WaitOne(timeout)) 49 | { 50 | using (HttpWebResponse res = (HttpWebResponse)req.EndGetResponse(ar)) 51 | { 52 | using (StreamReader stream = new StreamReader(res.GetResponseStream(), Encoding.UTF8)) 53 | { 54 | return stream.ReadToEnd(); 55 | } 56 | } 57 | } 58 | else 59 | { 60 | throw new Exception("request timeout:" + timeout); 61 | } 62 | } 63 | static string HttpRequest(string url, Object result) 64 | { 65 | Byte[] buffer = new byte[1024 * 10]; 66 | string postdata = ""; 67 | if (result != null) 68 | { 69 | postdata = JsonConvert.SerializeObject(result); 70 | postdata = JsonConvert.ToString(postdata); 71 | } 72 | return DownloadData(url, postdata, 30000); 73 | } 74 | class LoginArg 75 | { 76 | public string client_token = ""; 77 | } 78 | public static string Login(string project_token, string client_token) 79 | { 80 | string res = HttpRequest("https://anyplots.com/api/v1/client/login", new { project_token = project_token, client_token= client_token, bandwidth = ClientBandwidth, version = Program.Version }); 81 | LoginArg args = JsonConvert.DeserializeObject(res) ?? new LoginArg(); 82 | return args.client_token; 83 | } 84 | class GetPlotArg 85 | { 86 | public int id = 0; 87 | public Int64 size = 0; 88 | public string name = ""; 89 | public string url = ""; 90 | } 91 | public static string GetPlot(ref int id, ref long filesize, ref string filename) 92 | { 93 | string res = HttpRequest("https://anyplots.com/api/v1/client/get_plot", new { token = ClientToken, id = id, size = filesize, name = filename }); 94 | GetPlotArg args = JsonConvert.DeserializeObject(res) ?? new GetPlotArg(); 95 | id = args.id; 96 | filesize = args.size; 97 | filename = args.name; 98 | return args.url; 99 | } 100 | class StatsRes 101 | { 102 | public int action = 128; 103 | public int status = 0; 104 | public string result; 105 | } 106 | public static int Stats(int id, float percent, float speed, int pingmin, int pingavg, int pinglos) 107 | { 108 | int freespace, disks; 109 | freespace = Program.GetFreeSpace(out disks); 110 | string res = HttpRequest("https://anyplots.com/api/v1/client/stats", 111 | new { token = ClientToken, id = id, percent = percent, speed = speed, pingmin=pingmin, pingavg, pinglos = pinglos, freespace = freespace, disks = disks }); 112 | StatsRes ret = JsonConvert.DeserializeObject(res) ?? new StatsRes(); 113 | return ret.action; 114 | } 115 | public static void Logging(bool server, int fileid, string logs) 116 | { 117 | Console.WriteLine("\r\n" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + logs); 118 | if (server) 119 | { 120 | try 121 | { 122 | HttpRequest("https://anyplots.com/api/v1/client/logging", new { token = ClientToken, id = fileid, data = logs }); 123 | } 124 | catch (Exception ex) 125 | { 126 | try 127 | { 128 | HttpRequest("https://anyplots.com/api/v1/client/logging", new { token = ClientToken, id = fileid, data = logs }); 129 | } 130 | catch { } 131 | } 132 | } 133 | } 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /BlockTransfer.cs: -------------------------------------------------------------------------------- 1 | /* copyright @ anyplots.com, All rights reserved, visit https://anyplots.com/ for more information */ 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Net; 6 | using System.IO; 7 | using System.Threading; 8 | using System.Diagnostics; 9 | using Newtonsoft.Json; 10 | using System.Net.Sockets; 11 | namespace anyplots 12 | { 13 | static class BlockTransfer 14 | { 15 | static Int32 ReadInt32(byte[] buffer, int offset) //prevent Big-Endian, Little-Endian problem 16 | { 17 | return Convert.ToInt32(Encoding.ASCII.GetString(buffer, offset, 8), 16); 18 | } 19 | static Byte[] Int32Bytes(int v) //prevent Big-Endian, Little-Endian problem 20 | { 21 | return Encoding.ASCII.GetBytes(v.ToString("x8")); 22 | } 23 | static Queue> stats = new Queue>(100000); 24 | static Int64 counter = 0; 25 | public static float Speed 26 | { 27 | get 28 | { 29 | lock (stats) 30 | { 31 | Int64 ticks = DateTime.Now.Ticks - 3000000000; 32 | while (stats.Count > 0 && stats.Peek().Key < ticks) 33 | { 34 | stats.Dequeue(); 35 | } 36 | if (stats.Count > 0) 37 | { 38 | float taken = (DateTime.Now.Ticks - stats.Peek().Key) / 10000000.0f; 39 | float speed = (float)((counter - stats.Peek().Value) * 8 / taken ) / 1024 / 1024; 40 | return speed; 41 | } 42 | } 43 | return 0f; 44 | } 45 | } 46 | public static bool GetBlock(string url, Block block) 47 | { 48 | block.size = 0; 49 | using (Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp)) 50 | { 51 | 52 | try 53 | { 54 | socket.NoDelay = true; 55 | socket.ReceiveTimeout = 300000; 56 | socket.SendTimeout = 1000; 57 | socket.ReceiveBufferSize = 6 * (1 << 20) + 1024; 58 | string[] items = url.Split('/'); 59 | IPAddress ip = IPAddress.Parse(items[0].Split(':')[0]); 60 | Int32 port = Int32.Parse(items[0].Split(':')[1]); 61 | IAsyncResult ar = socket.BeginConnect(ip, port, null, null); 62 | if (ar.AsyncWaitHandle.WaitOne(10000)) 63 | { 64 | socket.EndConnect(ar); 65 | if (socket.Connected) 66 | { 67 | string request = "GET\n" + items[1] + "\n" + block.position.ToString() + "\n" + (block.position + block.buffer.Length - 1).ToString() + "\n"; 68 | Byte[] data_ = Encoding.ASCII.GetBytes(request.PadRight(248, ' ')); 69 | if (socket.Send(data_) != 248) 70 | { 71 | ApiController.Logging(false, 0, "send request error!"); 72 | return false; 73 | } 74 | if (socket.Send(Int32Bytes(CRC(data_, data_.Length))) != 8) 75 | { 76 | ApiController.Logging(false, 0, "send crc error!"); 77 | return false; 78 | 79 | } 80 | int received = 0; 81 | while (received < block.buffer.Length) 82 | { 83 | int ret = socket.Receive(block.buffer, received, block.buffer.Length - received, SocketFlags.None); 84 | if (ret > 0) 85 | { 86 | received += ret; 87 | lock (stats) 88 | { 89 | counter += ret; 90 | stats.Enqueue(new KeyValuePair(DateTime.Now.Ticks, counter)); 91 | while (stats.Count > 100000) 92 | { 93 | stats.Dequeue(); 94 | } 95 | } 96 | if(received == 8 && 97 | block.buffer[0] == (byte)'7' && 98 | block.buffer[1] == (byte)'f' && 99 | block.buffer[2] == (byte)'f' && 100 | block.buffer[3] == (byte)'f' && 101 | block.buffer[4] == (byte)'f' && 102 | block.buffer[5] == (byte)'f' && 103 | block.buffer[6] == (byte)'f' && 104 | block.buffer[7] == (byte)'f') 105 | { 106 | return false; 107 | } 108 | } 109 | else 110 | { 111 | return false; 112 | } 113 | } 114 | Byte[] crc = new byte[8]; 115 | if (socket.Receive(crc) != 8 || CRC(block.buffer, received) != ReadInt32(crc, 0)) 116 | { 117 | ApiController.Logging(false, 0, "crc error!"); 118 | return false; 119 | } 120 | block.size = block.buffer.Length; 121 | return true; 122 | } 123 | } 124 | } 125 | catch (SocketException ex) 126 | { 127 | if(ex.SocketErrorCode == SocketError.ConnectionReset || ex.SocketErrorCode == SocketError.TimedOut) 128 | { 129 | return false; 130 | } 131 | ApiController.Logging(false, 0, ex.SocketErrorCode + "\r\n" + ex.Message + "\r\n" + ex.StackTrace); 132 | } 133 | catch (Exception ex) 134 | { 135 | ApiController.Logging(false,0,ex.Message + "\r\n" + ex.StackTrace); 136 | } 137 | finally 138 | { 139 | try { socket.Shutdown(SocketShutdown.Both); } catch { } 140 | } 141 | } 142 | return false; 143 | } 144 | 145 | private const UInt32 magic = 0xedb88320u; 146 | 147 | private static UInt32[] table = new UInt32[16 * 256]; 148 | 149 | static BlockTransfer() 150 | { 151 | for (UInt32 i = 0; i < 256; i++) 152 | { 153 | UInt32 res = i; 154 | for (int t = 0; t < 16; t++) 155 | { 156 | for (int k = 0; k < 8; k++) res = (res & 1) == 1 ? magic ^ (res >> 1) : (res >> 1); 157 | table[(t * 256) + i] = res; 158 | } 159 | } 160 | } 161 | public static Int32 CRC(byte[] buffer, int length) 162 | { 163 | UInt32 value = 0xffffffff; 164 | int offset = 0; 165 | while (length >= 16) 166 | { 167 | var a = table[(3 * 256) + buffer[offset + 12]] 168 | ^ table[(2 * 256) + buffer[offset + 13]] 169 | ^ table[(1 * 256) + buffer[offset + 14]] 170 | ^ table[(0 * 256) + buffer[offset + 15]]; 171 | 172 | var b = table[(7 * 256) + buffer[offset + 8]] 173 | ^ table[(6 * 256) + buffer[offset + 9]] 174 | ^ table[(5 * 256) + buffer[offset + 10]] 175 | ^ table[(4 * 256) + buffer[offset + 11]]; 176 | 177 | var c = table[(11 * 256) + buffer[offset + 4]] 178 | ^ table[(10 * 256) + buffer[offset + 5]] 179 | ^ table[(9 * 256) + buffer[offset + 6]] 180 | ^ table[(8 * 256) + buffer[offset + 7]]; 181 | 182 | var d = table[(15 * 256) + ((byte)value ^ buffer[offset])] 183 | ^ table[(14 * 256) + ((byte)(value >> 8) ^ buffer[offset + 1])] 184 | ^ table[(13 * 256) + ((byte)(value >> 16) ^ buffer[offset + 2])] 185 | ^ table[(12 * 256) + ((value >> 24) ^ buffer[offset + 3])]; 186 | 187 | value = d ^ c ^ b ^ a; 188 | offset += 16; 189 | length -= 16; 190 | } 191 | 192 | while (--length >= 0) 193 | value = table[(byte)(value ^ buffer[offset++])] ^ value >> 8; 194 | 195 | return (Int32)(value & 0x7fffffff); 196 | } 197 | static string MD5(string content) 198 | { 199 | using (var md5 = System.Security.Cryptography.MD5.Create()) 200 | { 201 | var result = md5.ComputeHash(Encoding.UTF8.GetBytes(content)); 202 | return BitConverter.ToString(result).Replace("-", ""); 203 | } 204 | } 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /Client.cs: -------------------------------------------------------------------------------- 1 | /* copyright @ anyplots.com, All rights reserved, visit https://anyplots.com/ for more information */ 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Net; 6 | using System.Net.NetworkInformation; 7 | using System.IO; 8 | using System.Threading; 9 | using System.Diagnostics; 10 | namespace anyplots 11 | { 12 | class Client 13 | { 14 | public static String downloadstatus = ""; 15 | public static int optimizedmaxthreads = -1; 16 | string url = ""; 17 | int id = 0; 18 | PlotWriter writer = null; 19 | bool running = true; 20 | static int maxthreads = 8; 21 | List threads = new List(); 22 | 23 | bool eof = false; 24 | long position = 0; 25 | long endposition = 0; 26 | object syncposition = new object(); 27 | Block NextBlock() 28 | { 29 | Block block = null; 30 | lock (syncposition) 31 | { 32 | try 33 | { 34 | if (endposition > position) 35 | { 36 | block = new Block(position, (int)Math.Min(Block.BlockSize, endposition - position)); 37 | return block; 38 | } 39 | else 40 | { 41 | eof = true; 42 | return null;// end 43 | } 44 | } 45 | finally 46 | { 47 | if (block != null) position += block.buffer.Length; 48 | } 49 | } 50 | } 51 | public Client(string url, int id, PlotWriter writer) 52 | { 53 | this.url = url; 54 | this.id = id; 55 | this.writer = writer; 56 | this.endposition = writer.FileSize; 57 | this.position = writer.DownloadedPosition; 58 | } 59 | bool diskslow = false; 60 | void DownloadThread(object o) 61 | { 62 | int tid = (int)o; 63 | Block block = null; 64 | Stopwatch sw = new Stopwatch(); 65 | Random rnd = new Random(); 66 | while (running) 67 | { 68 | try 69 | { 70 | if (block == null) 71 | { 72 | if (tid >= maxthreads) 73 | { 74 | Thread.Sleep(rnd.Next(5, 15)); 75 | if (eof) 76 | { 77 | return; // end 78 | } 79 | continue; 80 | } 81 | } 82 | for (int i = 0; running && i < 100000; i++) 83 | { 84 | if (!writer.IsQueueFull) break; 85 | if (i > 1000) { diskslow = true; } 86 | Thread.Sleep(10); 87 | } 88 | diskslow = false; 89 | if (block == null) 90 | { 91 | block = NextBlock(); 92 | } 93 | if (block == null) 94 | { 95 | return; // end 96 | } 97 | sw.Restart(); 98 | sw.Start(); 99 | bool ret = BlockTransfer.GetBlock(url, block); 100 | sw.Stop(); 101 | if (ret) 102 | { 103 | writer.Write(block); 104 | block = null; 105 | } 106 | else 107 | { 108 | Thread.Sleep(5000); 109 | } 110 | } 111 | catch (Exception ex) 112 | { 113 | ApiController.Logging(true, id, "DownloadThread(" + tid + "):" + ex.Message + "\r\n" + ex.StackTrace); 114 | Thread.Sleep(10000); 115 | } 116 | } 117 | } 118 | int pingavg = 100; 119 | int pingmin = 2000; 120 | int pinglos = 0; 121 | void PingServer(object o) 122 | { 123 | IPAddress ip = IPAddress.Parse((string)o); 124 | Queue items = new Queue(500); 125 | Stopwatch watch = new Stopwatch(); 126 | while (running) 127 | { 128 | watch.Reset(); 129 | watch.Start(); 130 | using (Ping ping = new Ping()) 131 | { 132 | try 133 | { 134 | Byte[] data = Encoding.ASCII.GetBytes((Guid.NewGuid().ToString() + Guid.NewGuid().ToString() + Guid.NewGuid().ToString()).Substring(0, 64)); 135 | PingReply pr = ping.Send(ip, 1000, data); 136 | if (pr.Status == IPStatus.Success) 137 | { 138 | items.Enqueue((int)pr.RoundtripTime); 139 | } 140 | else 141 | { 142 | items.Enqueue(1000); 143 | } 144 | } 145 | catch { items.Enqueue(1000); } 146 | } 147 | Queue items_ = new Queue(500); 148 | while (items.Count > 300) items.Dequeue(); 149 | int total = 0,count = 0, min = 1000; 150 | while (items.Count > 0) 151 | { 152 | int t = items.Dequeue(); 153 | total += t; 154 | min = Math.Min(min, t); 155 | if (t >= 1000) count++; 156 | items_.Enqueue(t); 157 | } 158 | items = items_; 159 | pingmin = min; 160 | pingavg = total / items_.Count; 161 | pinglos = 100 * count / items_.Count; 162 | if (optimizedmaxthreads == -1) 163 | { 164 | float max_; 165 | if (items_.Count > 200 && pinglos > 0) 166 | { 167 | max_ = Math.Min(BlockTransfer.Speed * 5, ApiController.ClientBandwidth) / 16f * Math.Min(8, Math.Max(1, (pingavg / 50f))); 168 | } 169 | else 170 | { 171 | max_ = ApiController.ClientBandwidth / 16f * Math.Min(8, Math.Max(1, (pingavg / 50f))); 172 | } 173 | max_ -= max_ * pinglos * 2 / 100; 174 | if (max_ < 4) 175 | { 176 | max_ = 4; 177 | } 178 | if (max_ > threads.Count) 179 | { 180 | max_ = threads.Count; 181 | } 182 | maxthreads = (int)max_; 183 | } 184 | watch.Stop(); 185 | if (watch.ElapsedMilliseconds < 995) 186 | { 187 | Thread.Sleep(1000 - (int)watch.ElapsedMilliseconds); 188 | } 189 | } 190 | } 191 | public bool Run() 192 | { 193 | bool ret = true; 194 | try 195 | { 196 | maxthreads = ApiController.ClientBandwidth / 16; 197 | if (maxthreads > 128) maxthreads = 128; 198 | if (maxthreads < 4) maxthreads = 4; 199 | Thread t = new Thread(PingServer); 200 | t.Start(url.Split(':')[0]); 201 | for (int i = 0; i < 128; i++) 202 | { 203 | threads.Add(new Thread(DownloadThread)); 204 | threads[i].Start(i); 205 | Thread.Sleep(10); 206 | } 207 | try { if (Console.WindowWidth < 80) Console.WindowWidth = 80; } catch { } 208 | DateTime nextstat = DateTime.MinValue; 209 | string name = url.Split('/')[1].Split('.')[1]; 210 | while (running) 211 | { 212 | bool isalive = false; 213 | for (int i = 0; i < threads.Count; i++) 214 | { 215 | if (threads[i].IsAlive) 216 | { 217 | isalive = true; 218 | break; 219 | } 220 | } 221 | if (writer.Err != null) 222 | { 223 | ret = false; 224 | running = false; 225 | writer.Dispose(); 226 | ApiController.Logging(true, id, writer.Err.Message + "\r\n" + writer.Err.StackTrace); 227 | break; 228 | } 229 | if (isalive) 230 | { 231 | double percent = Math.Max(writer.Percent - 0.0001f, 0); // 100% will cause plot delete on the server side. 232 | float speed = BlockTransfer.Speed; 233 | Console.Write(("\r[" + id + "] " + percent.ToString("f4") + "%, " + speed.ToString("f1") + "Mbps, " + maxthreads + " Threads").PadRight(45, ' ') + 234 | (diskslow ? "write queue full, disk I/O speed is slow" : (name.Substring(0, 30) + "..."))); 235 | 236 | if (optimizedmaxthreads != -1) 237 | { 238 | maxthreads = optimizedmaxthreads; 239 | } 240 | if (nextstat < DateTime.Now) 241 | { 242 | nextstat = DateTime.Now.AddSeconds(30); 243 | int status = ApiController.Stats(id, (float)percent, (float)speed, pingmin, pingavg, pinglos); 244 | if (status == -9999) // error 245 | { 246 | running = false; 247 | ret = false; 248 | writer.Dispose(); 249 | break; 250 | } 251 | else if (status < 0) 252 | { 253 | optimizedmaxthreads = -status; 254 | } 255 | } 256 | } 257 | else 258 | { 259 | if (writer.IsCompleted) 260 | { 261 | writer.Finish(); 262 | ApiController.Stats(id, 100, 0, pingmin, pingavg, pinglos); 263 | break; 264 | } 265 | } 266 | Thread.Sleep(1000); 267 | } 268 | for (int i = 0; i < threads.Count; i++) 269 | { 270 | threads[i].Join(); 271 | } 272 | } 273 | finally 274 | { 275 | running = false; 276 | try { writer.Dispose(); } catch { } 277 | } 278 | return ret; 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /CloudChiaPlotter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | AnyCPU;x64 7 | 8 | 9 | 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /PlotWriter.cs: -------------------------------------------------------------------------------- 1 | /* copyright @ anyplots.com, All rights reserved, visit https://anyplots.com/ for more information */ 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Net; 6 | using System.IO; 7 | using System.Threading; 8 | using System.Diagnostics; 9 | namespace anyplots 10 | { 11 | class Block 12 | { 13 | public const long BlockSize = 1024 * 1024 * 4; 14 | public long position = 0; 15 | public int size = 0; 16 | public byte[] buffer; 17 | 18 | public Block(long pos, int size) 19 | { 20 | position = pos; 21 | buffer = new Byte[size]; 22 | } 23 | } 24 | 25 | class PlotWriter : IDisposable 26 | { 27 | string dir = ""; 28 | string fileid = ""; 29 | string filename = ""; 30 | Queue queue = new Queue(); 31 | SortedList downloaded = new SortedList(1024); 32 | FileStream data = null; 33 | FileStream conf = null; 34 | Thread writer = null; 35 | bool running = true; 36 | public Exception Err = null; 37 | public Int64 FileSize; 38 | public PlotWriter(string dir, string fileid, string filename, Int64 size) 39 | { 40 | this.dir = dir; 41 | this.fileid = fileid; 42 | this.filename = filename; 43 | this.FileSize = size; 44 | try 45 | { 46 | data = new FileStream(dir + fileid + "." + filename + ".data" + ApiController.Ext, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); 47 | conf = new FileStream(dir + fileid + "." + filename + ".conf" + ApiController.Ext, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); 48 | if (data.Length != size) 49 | { 50 | data.SetLength(size); 51 | data.Position = 0; 52 | } 53 | else 54 | { 55 | data.Position = DownloadedPosition; 56 | } 57 | downloaded.Add(data.Position, true); 58 | SaveProgress(true); 59 | writer = new Thread(Writing); 60 | writer.Start(); 61 | ApiController.Logging(false, 0, "Using Dir: " + dir); 62 | } 63 | catch (Exception ex) 64 | { 65 | Dispose(); 66 | try { File.Delete(dir + fileid + "." + filename + ".data" + ApiController.Ext); } catch { } 67 | try { File.Delete(dir + fileid + "." + filename + ".conf" + ApiController.Ext); } catch { } 68 | throw new Exception(ex.Message + ex.StackTrace); 69 | } 70 | } 71 | public void Dispose() 72 | { 73 | Err = new Exception("Disposed"); 74 | if (running) 75 | { 76 | running = false; 77 | } 78 | if (writer != null) 79 | { 80 | try 81 | { 82 | for (int i = 0; i < 10000 && writer.IsAlive; i++) 83 | { 84 | Thread.Sleep(10); 85 | } 86 | } 87 | catch { }; 88 | writer = null; 89 | } 90 | if (conf != null) 91 | { 92 | try { conf.Close(); } catch { } 93 | conf = null; 94 | } 95 | if (data != null) 96 | { 97 | try { data.Close(); } catch { } 98 | data = null; 99 | } 100 | } 101 | public long DownloadedPosition 102 | { 103 | get { 104 | Byte[] bytes_ = new byte[12]; 105 | conf.Position = 0; 106 | if (conf.Read(bytes_, 0, 12) == 12) 107 | { 108 | Int64 len = BitConverter.ToInt64(bytes_, 0); 109 | if (len.GetHashCode() == BitConverter.ToInt32(bytes_, 8)) 110 | { 111 | return len; 112 | } 113 | else 114 | { 115 | ApiController.Logging(false,0,"Hash Error"); 116 | return 0; 117 | } 118 | } 119 | return 0; 120 | } 121 | } 122 | void SaveProgress(bool changed) 123 | { 124 | while (downloaded.Count > 1) 125 | { 126 | if (downloaded.Keys[0] + Block.BlockSize == downloaded.Keys[1]) 127 | { 128 | downloaded.RemoveAt(0); 129 | changed = true; 130 | } 131 | else 132 | { 133 | break; 134 | } 135 | } 136 | if (changed && downloaded.Count > 0) 137 | { 138 | conf.Position = 0; 139 | conf.Write(BitConverter.GetBytes(downloaded.Keys[0]), 0, 8); 140 | conf.Write(BitConverter.GetBytes(downloaded.Keys[0].GetHashCode()), 0, 4); 141 | conf.Flush(); 142 | } 143 | } 144 | public double Percent = 0; 145 | public bool IsCompleted = false; 146 | 147 | public bool IsQueueFull 148 | { 149 | get 150 | { 151 | lock (queue) 152 | { 153 | return queue.Count > 128; 154 | } 155 | } 156 | } 157 | public void Write(Block block) 158 | {; 159 | if (Err != null) { return; } 160 | if (!running) { Err = new Exception("writing stopped!!!"); return; } 161 | lock (queue) 162 | { 163 | queue.Enqueue(block); 164 | } 165 | } 166 | public void Finish() 167 | { 168 | ApiController.Logging(false,0,"finishing ..."); 169 | if (Err != null || !IsCompleted) 170 | { 171 | throw new Exception("error, plot not completed !!!"); 172 | } 173 | Dispose(); 174 | File.Move(dir + fileid + "." + filename + ".data" + ApiController.Ext, dir + filename); 175 | if (File.Exists(dir + filename)) 176 | { 177 | File.Delete(dir + fileid + "." + filename + ".conf" + ApiController.Ext); 178 | } 179 | else 180 | { 181 | throw new Exception("rename file failed!!!"); 182 | } 183 | } 184 | void Writing(object o) 185 | { 186 | try 187 | { 188 | int waits = 0; 189 | int writed = 0; 190 | DateTime nextflush = DateTime.Now.AddSeconds(5); 191 | while (running) 192 | { 193 | 194 | List blocks = new List(200); 195 | lock (queue) 196 | { 197 | if (queue.Count > 16 || waits > 6) 198 | { 199 | while (queue.Count > 0) 200 | { 201 | blocks.Add(queue.Dequeue()); 202 | } 203 | waits = 0; 204 | } 205 | else 206 | { 207 | waits++; 208 | } 209 | } 210 | if (!running) return; 211 | if (blocks.Count > 0) 212 | { 213 | blocks.Sort(delegate (Block a, Block b) { return a.position.CompareTo(b.position); }); 214 | foreach (Block block in blocks) 215 | { 216 | if (!running) return; 217 | if (block.position < downloaded.Keys[0]) continue; 218 | data.Position = block.position; 219 | data.Write(block.buffer, 0, block.size); 220 | downloaded.Add(data.Position, true); 221 | writed++; 222 | } 223 | } 224 | else 225 | { 226 | Thread.Sleep(5); 227 | } 228 | if (writed > 0 && nextflush < DateTime.Now) 229 | { 230 | data.Flush(); 231 | SaveProgress(false); 232 | writed = 0; 233 | nextflush = DateTime.Now.AddSeconds(5); 234 | } 235 | Percent = Math.Min(99.9999,(downloaded.Keys[0] + (downloaded.Count - 1) * Block.BlockSize) * 100d / FileSize); 236 | if(downloaded.Keys[downloaded.Count - 1] == FileSize) 237 | { 238 | IsCompleted = downloaded.Keys[downloaded.Count - 1] - downloaded.Keys[0] < Block.BlockSize; 239 | if (IsCompleted) 240 | { 241 | data.Flush(); 242 | } 243 | } 244 | GC.Collect(); 245 | } 246 | } 247 | catch(Exception ex) 248 | { 249 | Err = ex; 250 | } 251 | running = false; 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | /* copyright @ anyplots.com, All rights reserved, visit https://anyplots.com/ for more information */ 2 | using System; 3 | using System.Threading; 4 | using System.IO; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Reflection; 8 | namespace anyplots 9 | { 10 | class Program 11 | { 12 | static string ver = null; 13 | public static string Version 14 | { 15 | get 16 | { 17 | if (ver == null) 18 | { 19 | try 20 | { 21 | using (Process p = Process.GetCurrentProcess()) 22 | { 23 | string path = p.MainModule.FileName; 24 | using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 25 | { 26 | byte[] bytes = new byte[fs.Length]; 27 | fs.Read(bytes, 0, bytes.Length); 28 | int crc = BlockTransfer.CRC(bytes, bytes.Length); 29 | ver = "2022021801@" + crc.ToString("x8"); 30 | } 31 | } 32 | } 33 | catch { ver = "-"; } 34 | } 35 | return ver; 36 | } 37 | } 38 | static int nextdir = 0; 39 | public static List Dirs = new List(); 40 | public static string NextDir 41 | { 42 | get { return Dirs[nextdir++ % Dirs.Count]; } 43 | } 44 | 45 | static void MyHandler(object sender, UnhandledExceptionEventArgs args) 46 | { 47 | try 48 | { 49 | if (args.ExceptionObject != null) 50 | { 51 | Exception e = (Exception)args.ExceptionObject; 52 | ApiController.Logging(false, 0, "Error caught : " + e.Message); 53 | ApiController.Logging(true, 0, "MyHandler1:" + args.IsTerminating + "\r\n" + e.Message + "\r\n" + e.StackTrace); 54 | } 55 | else 56 | { 57 | 58 | ApiController.Logging(true, 0, "MyHandler2:" + args.IsTerminating); 59 | } 60 | } 61 | catch { } 62 | } 63 | 64 | static FileStream fslock = null; 65 | static FileStream LockFile(string file) 66 | { 67 | try 68 | { 69 | FileStream fslock = new FileStream(file, FileMode.Create, FileAccess.ReadWrite, FileShare.None); 70 | try 71 | { 72 | fslock.Write(System.Text.Encoding.ASCII.GetBytes("lock"), 0, 4); 73 | return fslock; 74 | } 75 | catch { fslock.Close(); } 76 | } 77 | catch { } 78 | return null; 79 | } 80 | static void InitClient() 81 | { 82 | foreach (string file in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.client")) 83 | { 84 | FileStream lock_ = LockFile(file); 85 | if (lock_ != null) 86 | { 87 | string token = Path.GetFileNameWithoutExtension(file); 88 | if (token != ApiController.Login(ApiController.ProjectToken, token)) 89 | { 90 | lock_.Close(); 91 | File.Delete(file); 92 | } 93 | else 94 | { 95 | fslock = lock_; 96 | ApiController.ClientToken = token; 97 | return; 98 | } 99 | } 100 | Thread.Sleep(5000); 101 | } 102 | string newtoken_ = ApiController.Login(ApiController.ProjectToken, ""); 103 | if (newtoken_.Length == 40) 104 | { 105 | FileStream lock_ = LockFile(AppDomain.CurrentDomain.BaseDirectory + newtoken_ + ".client"); 106 | if (lock_ == null) 107 | { 108 | throw new Exception("unkown error"); 109 | } 110 | fslock = lock_; 111 | ApiController.ClientToken = newtoken_; 112 | } 113 | else 114 | { 115 | throw new Exception("login failed, unkown token: " + newtoken_); 116 | } 117 | } 118 | static bool IsWindows 119 | { 120 | get { return AppDomain.CurrentDomain.BaseDirectory.Contains('\\'); } 121 | } 122 | static string NormalizeDirPath(string path) 123 | { 124 | if (IsWindows) 125 | { 126 | return path.TrimEnd(new char[] { '/', '\\' }) + "\\"; 127 | } 128 | else 129 | { 130 | return path.TrimEnd(new char[] { '/', '\\' }) + "/"; 131 | } 132 | } 133 | static void Main(string[] args) 134 | { 135 | try 136 | { 137 | AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler); 138 | } 139 | catch { } 140 | if (args.Length < 6) 141 | { 142 | Console.WriteLine("Version: " + Version); 143 | Console.WriteLine("Usage: "); 144 | if (IsWindows) 145 | { 146 | Console.WriteLine("use the command like: .\\CloudChiaPlotter.exe -p {project_token} -d d:\\;e:\\;f:\\;g:\\ -b 1000"); 147 | Console.WriteLine("-p {project_token} such as: -p 000001236c46bb1d0cddf079c7c345ea5bd727e28c4"); 148 | Console.WriteLine("-d save the chia plots to these folders, such as: -d d:\\;e:\\;f:\\;g:\\"); 149 | } 150 | else 151 | { 152 | Console.WriteLine("use the command like: ./CloudChiaPlotter -p {project_token} -d /mnt/d;/mnt/e/;/mnt/f;/mnt/g -b 1000"); 153 | Console.WriteLine("-p {project_token} such as: -p 000001236c46bb1d0cddf079c7c345ea5bd727e28c4"); 154 | Console.WriteLine("-d save the chia plots to these folders, such as: -d /mnt/d;/mnt/e/;/mnt/f;/mnt/g"); 155 | } 156 | Console.WriteLine("-b your network bandwidth, such as: -b 1000 for 1000Mbps"); 157 | Console.WriteLine(" if you don't know it, please visit and test at https://speedtest.net"); 158 | Console.WriteLine("\r\nPress any key to continue"); 159 | Console.ReadKey(); 160 | return; 161 | } 162 | else 163 | { 164 | try 165 | { 166 | for (int i = 0; i < args.Length; i++) 167 | { 168 | switch (args[i]) 169 | { 170 | case "-p": 171 | ApiController.ProjectToken = args[++i]; 172 | break; 173 | case "-d": 174 | Dictionary filter = new Dictionary(); 175 | foreach (string item in args[++i].Split(';')) 176 | { 177 | string dir = NormalizeDirPath(item); 178 | if (Directory.Exists(dir)) 179 | { 180 | if (!filter.ContainsKey(dir.ToLower())) 181 | { 182 | if (TestDir(dir)) 183 | { 184 | filter.Add(dir.ToLower(), null); 185 | Dirs.Add(dir); 186 | ApiController.Logging(false, 0, "add dir:" + dir); 187 | } 188 | else 189 | { 190 | ApiController.Logging(false, 0, "test dir failed, skip dir:" + dir); 191 | } 192 | } 193 | else 194 | { 195 | ApiController.Logging(false, 0, "skip duplicate dir:" + dir); 196 | } 197 | } 198 | else 199 | { 200 | ApiController.Logging(false, 0, "unkown dir:" + dir); 201 | } 202 | } 203 | break; 204 | case "-b": 205 | ApiController.ClientBandwidth = int.Parse(args[++i]); 206 | break; 207 | default: 208 | ApiController.Logging(false, 0, "unkown argument:" + args[i]); 209 | break; 210 | } 211 | } 212 | if (ApiController.ProjectToken.Length != 40) 213 | { 214 | ApiController.Logging(false, 0, "Project_token is incorrect:" + ApiController.ProjectToken); 215 | throw new Exception("Project_token is incorrect:" + ApiController.ProjectToken); 216 | } 217 | if (Dirs.Count == 0) 218 | { 219 | ApiController.Logging(false, 0, "No available directories"); 220 | throw new Exception("No available directories"); 221 | } 222 | if (ApiController.ClientBandwidth == 0) 223 | { 224 | ApiController.Logging(false, 0, "Bandwidth not set"); 225 | throw new Exception("Bandwidth not set"); 226 | } 227 | } 228 | catch (Exception ex) 229 | { 230 | 231 | Console.WriteLine("Bad arguments!" + ex.Message); 232 | Console.WriteLine("Press any key to continue"); 233 | Console.ReadKey(); 234 | return; 235 | } 236 | } 237 | InitClient(); 238 | ApiController.Logging(false, 0, "client token:" + ApiController.ClientToken); 239 | Run(); 240 | } 241 | static long GetFreeSpace(string path) 242 | { 243 | try 244 | { 245 | return new DriveInfo(path).AvailableFreeSpace; 246 | } 247 | catch (Exception ex) 248 | { 249 | ApiController.Logging(false, 0, ex.Message); 250 | } 251 | return 0; 252 | } 253 | public static int GetFreeSpace(out int cnt) 254 | { 255 | int total = 0; 256 | cnt = 0; 257 | foreach (string dir in Program.Dirs) 258 | { 259 | try 260 | { 261 | cnt++; 262 | int ret = (int)(GetFreeSpace(dir) >> 30); 263 | if (ret > 1000000000) { ret = 0; } // error 264 | total += ret; 265 | } 266 | catch { } 267 | } 268 | return total; 269 | } 270 | static bool TestDir(string path) 271 | { 272 | try 273 | { 274 | using (FileStream fs = new FileStream(path + "testfile", FileMode.Create, FileAccess.Write, FileShare.Read)) 275 | { 276 | fs.WriteByte(0x0a); 277 | } 278 | } 279 | catch (Exception ex) 280 | { 281 | ApiController.Logging(false, 0, ex.Message + ex.StackTrace); 282 | return false; 283 | } 284 | finally 285 | { 286 | try { File.Delete(path + "testfile"); } catch { } 287 | } 288 | return true; 289 | } 290 | static Int64 GetFileSize(string file) 291 | { 292 | using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 293 | { 294 | return fs.Length; 295 | } 296 | } 297 | static string GetUncompletedPlot(out int fileid, out Int64 filesize, out string filename) 298 | { 299 | fileid = 0; 300 | filesize = 0; 301 | filename = ""; 302 | foreach (string dir in Program.Dirs) 303 | { 304 | try 305 | { 306 | foreach (string file in Directory.GetFiles(dir, "*.data" + ApiController.Ext)) 307 | { 308 | filesize = GetFileSize(file); 309 | string[] parts = Path.GetFileNameWithoutExtension(file).Split('.'); 310 | fileid = int.Parse(Path.GetFileNameWithoutExtension(parts[0])); 311 | filename = parts[1] + ".plot"; 312 | return file; 313 | } 314 | } 315 | catch (Exception ex) 316 | { 317 | fileid = 0; 318 | filesize = 0; 319 | ApiController.Logging(true, 0, "GetUncompletedPlot: " + ex.Message + "\r\n" + dir); 320 | } 321 | } 322 | return ""; 323 | } 324 | static void Clear(string path) 325 | { 326 | if (File.Exists(path)) 327 | { 328 | try 329 | { 330 | ApiController.Logging(false, 0, "Delete: " + path); 331 | File.Delete(path); 332 | } 333 | catch { } 334 | } 335 | path = path.Replace(".data" + ApiController.ClientToken, ".conf" + ApiController.ClientToken); 336 | if (File.Exists(path)) 337 | { 338 | try 339 | { 340 | ApiController.Logging(false, 0, "Delete: " + path); 341 | File.Delete(path); 342 | } 343 | catch { } 344 | } 345 | } 346 | static PlotWriter GetPlotWriter(int fileid, string filename, long filesize) 347 | { 348 | for (int i = 0; i < Program.Dirs.Count; i++) 349 | { 350 | string dir = Program.NextDir; 351 | try 352 | { 353 | if (GetFreeSpace(dir) > filesize || File.Exists(dir + fileid + "." + filename + ".data" + ApiController.Ext)) 354 | { 355 | return new PlotWriter(dir, fileid.ToString(), filename, filesize); 356 | } 357 | else 358 | { 359 | ApiController.Logging(true, fileid, "Skip No space: " + dir); 360 | } 361 | } 362 | catch (Exception ex) 363 | { 364 | ApiController.Logging(true, fileid, dir + "\r\n" + ex.Message + "\r\n" + ex.StackTrace); 365 | } 366 | } 367 | ApiController.Logging(true, fileid, "No space"); 368 | return null; 369 | } 370 | static void Run() 371 | { 372 | string lastname = ""; 373 | int fileid = 0; 374 | int errors = 0; 375 | while (true) 376 | { 377 | try 378 | { 379 | fileid = 0; 380 | Dictionary plots = new Dictionary(10000); //check duplicate plot file 381 | foreach (string dir in Program.Dirs) 382 | { 383 | try 384 | { 385 | foreach (string file in Directory.GetFiles(dir, "*.plot")) 386 | { 387 | string key = Path.GetFileName(file); 388 | if (!plots.ContainsKey(key)) 389 | { 390 | plots.Add(key, null); 391 | } 392 | } 393 | } 394 | catch { } 395 | } 396 | int uncompletedid = 0; 397 | long filesize; 398 | string filename; 399 | string path = GetUncompletedPlot(out uncompletedid, out filesize, out filename); 400 | fileid = uncompletedid; 401 | string url = ApiController.GetPlot(ref fileid, ref filesize, ref filename); 402 | if (uncompletedid > 0 && uncompletedid != fileid) 403 | { 404 | Clear(path); 405 | uncompletedid = 0; 406 | } 407 | if (fileid > 0) 408 | { 409 | ApiController.Logging(false, 0, "**************************************"); 410 | if (lastname == filename || plots.ContainsKey(filename)) 411 | { 412 | ApiController.Stats(fileid, 100, 0, -1, -1, -1); 413 | ApiController.Logging(true, fileid, "Skip same file: " + filename); 414 | } 415 | else 416 | { 417 | ApiController.Logging(false, fileid, "Start downloading: " + filename); 418 | using (PlotWriter writer = GetPlotWriter(fileid, filename, filesize)) 419 | { 420 | if (writer == null) 421 | { 422 | ApiController.Logging(true, fileid, "Download Failed: " + filename + ", it will auto try again in 300 seconds."); 423 | Thread.Sleep(300000); 424 | continue; 425 | } 426 | Client mc = new Client(url, fileid, writer); 427 | if (mc.Run()) 428 | { 429 | errors = 0; 430 | lastname = filename; 431 | ApiController.Logging(true, fileid, "Download Success: " + filename); 432 | } 433 | else 434 | { 435 | errors++; 436 | if (errors < 5) 437 | { 438 | ApiController.Logging(true, fileid, "Download Failed: " + filename + ", it will auto try again in 30 seconds."); 439 | Thread.Sleep(30000); 440 | } 441 | else 442 | { 443 | ApiController.Logging(true, fileid, "Download Failed: " + filename + ", it will auto try again in 300 seconds."); 444 | Thread.Sleep(300000); 445 | } 446 | } 447 | } 448 | } 449 | } 450 | else 451 | { 452 | for (int i = 0; i < 30; i++) 453 | { 454 | Console.Write("\r[" + DateTime.Now.ToString("HH:mm:ss") + "]" + filename); 455 | Thread.Sleep(1000); 456 | } 457 | } 458 | } 459 | catch (Exception ex) 460 | { 461 | ApiController.Logging(true, fileid, "Client.Run:" + ex.Message + ex.StackTrace); 462 | Thread.Sleep(30000); 463 | } 464 | } 465 | 466 | 467 | } 468 | } 469 | } -------------------------------------------------------------------------------- /Properties/PublishProfiles/linux-arm.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net6.0\publish\linux-arm 10 | FileSystem 11 | net6.0 12 | linux-arm 13 | True 14 | true 15 | Copyright © $([System.DateTime]::UtcNow.Year) anyplots.com ($([System.DateTime]::UtcNow.ToString("s"))) 16 | False 17 | 18 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/linux-arm64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net6.0\publish\linux-arm64 10 | FileSystem 11 | net6.0 12 | linux-arm64 13 | True 14 | True 15 | true 16 | Copyright © $([System.DateTime]::UtcNow.Year) anyplots.com ($([System.DateTime]::UtcNow.ToString("s"))) 17 | 18 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/linux-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net6.0\publish\linux-x64 10 | FileSystem 11 | net6.0 12 | linux-x64 13 | true 14 | true 15 | true 16 | Copyright © $([System.DateTime]::UtcNow.Year) anyplots.com ($([System.DateTime]::UtcNow.ToString("s"))) 17 | 18 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/osx-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net6.0\publish\osx-x64 10 | FileSystem 11 | net6.0 12 | osx-x64 13 | true 14 | true 15 | true 16 | Copyright © $([System.DateTime]::UtcNow.Year) anyplots.com ($([System.DateTime]::UtcNow.ToString("s"))) 17 | 18 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/win-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net6.0\publish\win-x64 10 | FileSystem 11 | net6.0 12 | win-x64 13 | true 14 | true 15 | true 16 | true 17 | true 18 | Copyright © $([System.DateTime]::UtcNow.Year) anyplots.com ($([System.DateTime]::UtcNow.ToString("s"))) 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cloud chia plotter: fast chia plotting without SSD disk or large amount of memory. 2 | 3 | It does not require SSD disk or large amount of memory, and can run well on low configuration rig, such as raspberry pi. 4 | 5 | https://anyplots.com/ The marketplace to buy and sell chia plots, starts at $0.20/plot. 6 | 7 | 8 |
9 |

Cloud Chia Plotter Usage

10 | 11 |

Arguments:

12 |
 13 | -p  project token ( get it from your project page https://anyplots.com/buy-chia-plot/projects ), such as(40 chars):
 14 |         -p 000005e13f5fc456753081edf7dcc98986dcffa15 
 15 | 
 16 | 
17 | -d directories for save plots, separate multiple directories with semicolons, such as: 18 | -d "/mnt/d/;/mnt/e/;/mnt/f/" 19 | -d "d:\;e:\;f:\" 20 |
21 | -b set your real network bandwidth/speed(Mbps), such as: 22 | for 500Mbps: -b 500 , for 1000Mbps: -b 1000 23 | please test your network speed at https://speedtest.net 24 |
25 | for example: 26 | 27 | .\\CloudChiaPlotter.exe -p 000005e13f5fc456753081edf7dcc98986dcffa15 -d "d:\\;e:\\;f:\\" -b 500 28 | 29 | ./CloudChiaPlotter -p 000005e13f5fc456753081edf7dcc98986dcffa15 -d "/mnt/d/;/mnt/e/;/mnt/f/" -b 500 30 | 31 | You can run multiple processes at the same time. 32 | 33 |
34 | 35 |

Tips

36 |

Do not set bandwidth parameter that are inconsistent with your actual bandwidth, Please test your network speed at 37 | https://speedtest.net 38 |
If you set -b 1000 for the actual bandwidth of 500mbps, it will reduce your download speed in most cases, because the probability of network packet loss will increase. 39 | 40 |

41 |

For bandwidth greater than 500mbps, two processes can be run at the same time, but the bandwidth parameter should be divided by 2.

42 |

Try to use the network cable to download. The wireless network is unstable and the download speed is affected.

43 |

Under the condition that the program does not change the directory configuration, for uncompleted plot, It will continue to transmit the remaining part only.

44 | 45 |
46 |
47 | 48 |

for Windows.

49 | For beginners, we provide a simple GUI program. go Windows-GUI
50 |

51 | If you are familiar with PowerShell, you can use the following scripts. 52 |
 53 | #Run it on Windows
 54 | #Now, open a powershell window (Click Start, type PowerShell, and then click Windows PowerShell)
 55 | #run the follow command(If there is a problem with the execution sequence, please execute line by line)
 56 | 
 57 | #del .\\CloudChiaPlotter.exe
 58 | 
 59 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
 60 | Invoke-WebRequest -Uri  https://github.com/anyplots/cloud-chia-plotter/releases/download/v3/cloud-chia-plotter-v3-win-x64.exe -Outfile .\\CloudChiaPlotter.exe
 61 | .\\CloudChiaPlotter.exe -p {your project token} -d  "d:\\;e:\\;f:\\" -b {your bandwidth}
 62 | 
63 | 64 | 65 |

for Linux series.

66 |
 67 | # run the follow script in the linux console
 68 | # Note that different platforms correspond to different link versions
 69 | 
 70 | #rm ./CloudChiaPlotter
 71 | 
 72 | wget -O CloudChiaPlotter https://github.com/anyplots/cloud-chia-plotter/releases/download/v3/cloud-chia-plotter-v3-linux-x64
 73 | chmod +x ./CloudChiaPlotter
 74 | ./CloudChiaPlotter -p {your project token} -d  "/mnt/d/;/mnt/e/;/mnt/f/" -b {your bandwidth}
 75 | 
76 | 77 | 78 |

for Mac OS series.

79 |
 80 | #Running the CloudChiaPlotter on Mac OS
 81 | #Now, open a terminal window (Mac OS version of the command line)
 82 | #run the follow command:
 83 | 
 84 | #rm ./CloudChiaPlotter
 85 | curl  -LJ https://github.com/anyplots/cloud-chia-plotter/releases/download/v3/cloud-chia-plotter-v3-osx-x64 -o ./CloudChiaPlotter
 86 | chmod +x ./CloudChiaPlotter
 87 | ./CloudChiaPlotter -p {your project token} -d  "/mnt/d/;/mnt/e/;/mnt/f/" -b {your bandwidth}
 88 | 
89 | 90 | 91 |

for Arm64 Linux series(Raspberry Pi 3, Pi 4, etc)

92 |
 93 | # run the follow script in the linux console
 94 | # Note that different platforms correspond to different link versions
 95 | 
 96 | #rm ./CloudChiaPlotter
 97 | 
 98 | wget -O CloudChiaPlotter https://github.com/anyplots/cloud-chia-plotter/releases/download/v3/cloud-chia-plotter-v3-linux-arm64
 99 | chmod +x ./CloudChiaPlotter
100 | ./CloudChiaPlotter -p {your project token} -d  "/mnt/d/;/mnt/e/;/mnt/f/" -b {your bandwidth}
101 | 
102 | 103 | 104 |

for Arm32 Linux series.

105 |
106 | # run the follow script in the linux console
107 | # Note that different platforms correspond to different link versions
108 | 
109 | #rm ./CloudChiaPlotter
110 | 
111 | wget -O CloudChiaPlotter https://github.com/anyplots/cloud-chia-plotter/releases/download/v3/cloud-chia-plotter-v3-linux-arm32
112 | chmod +x ./CloudChiaPlotter
113 | ./CloudChiaPlotter -p {your project token} -d  "/mnt/d/;/mnt/e/;/mnt/f/" -b {your bandwidth}
114 | 
115 | 116 |

Tested download Speed

117 | Different user experiences may vary, depending on your network and the plotting server you choose. 118 |
119 | download from U.S. based plotting server at the following countries(bandwidth 1 Gbit/s):
120 | US: ping latency ~50ms,  download speed > 1000Mbit/s, ~ 13 minutes per plot.
121 | UK: ping latency ~80ms,  download speed > 1000Mbit/s. ~ 13 minutes per plot.    
122 | DE: ping latency ~100ms, download speed > 1000Mbit/s. ~ 13 minutes per plot.    
123 | FR: ping latency ~100ms, download speed > 1000Mbit/s. ~ 13 minutes per plot.    
124 | JP: ping latency ~160ms, download speed > 900Mbit/s.  ~ 15 minutes per plot.  
125 | AU: ping latency ~220ms, download speed > 800Mbit/s.  ~ 16 minutes per plot.  
126 | IN: ping latency ~280ms, download speed > 700Mbit/s.  ~ 18 minutes per plot.  
127 | 
128 | 
129 | 
130 | 
131 | 
132 | 133 |

How to build it from source code?

134 | 135 | As this project is based on Microsoft .NET 6.0(more information https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
136 | You can use Microsoft Visual Studio 2022 to quickly compile various system versions

137 | 138 | We have precompiled some executable files for several common OS(see here https://github.com/anyplots/cloud-chia-plotter/releases).
139 | the compile configuration files is located "Properties/PublishProfiles/"
140 | linux-arm.pubxml
141 | linux-arm64.pubxml
142 | linux-x64.pubxml
143 | osx-x64.pubxml
144 | win-x64.pubxml

145 | 146 | You also can compile it for other OS by yourself.
147 | It is not recommended to change the business logic in the code, otherwise you may not be able to download or occurs unknown errors. 148 | 149 | 150 |

Build it for linux without VS2022

151 |
152 | git clone https://github.com/anyplots/cloud-chia-plotter
153 | wget -O ./dotnet-install.sh https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh
154 | chmod +x ./dotnet-install.sh
155 | ./dotnet-install.sh
156 | ./.dotnet/dotnet  publish ./cloud-chia-plotter/CloudChiaPlotter.csproj /p:PublishProfile=./cloud-chia-plotter/Properties/PublishProfiles/linux-x64.pubxml
157 | 
158 | 159 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /cloudchiaplotter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31912.275 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudChiaPlotter", "CloudChiaPlotter.csproj", "{9B17F841-6FA6-4254-B558-6B436D3D2599}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Debug|x64.ActiveCfg = Debug|x64 21 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Debug|x64.Build.0 = Debug|x64 22 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Debug|x86.ActiveCfg = Debug|Any CPU 23 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Debug|x86.Build.0 = Debug|Any CPU 24 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Release|x64.ActiveCfg = Release|x64 27 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Release|x64.Build.0 = Release|x64 28 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Release|x86.ActiveCfg = Release|Any CPU 29 | {9B17F841-6FA6-4254-B558-6B436D3D2599}.Release|x86.Build.0 = Release|Any CPU 30 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Debug|x64.ActiveCfg = Debug|x64 33 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Debug|x64.Build.0 = Debug|x64 34 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Debug|x86.ActiveCfg = Debug|Any CPU 35 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Debug|x86.Build.0 = Debug|Any CPU 36 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Release|x64.ActiveCfg = Release|x64 39 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Release|x64.Build.0 = Release|x64 40 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Release|x86.ActiveCfg = Release|Any CPU 41 | {4F039A6B-61DB-4D2B-903A-011BA7FB3223}.Release|x86.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {AE496BEC-D01D-4170-8C10-CC4E066C3E17} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyplots/cloud-chia-plotter/c6dfbbcf9e8373392876a24816707394937b164d/create.png -------------------------------------------------------------------------------- /gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyplots/cloud-chia-plotter/c6dfbbcf9e8373392876a24816707394937b164d/gui.png -------------------------------------------------------------------------------- /gui.ps1: -------------------------------------------------------------------------------- 1 | #How to run it? 2 | #when select this file(gui.ps1), click the right button of the mouse, then select run with PowerShell on the right menu. 3 | #for more usage & tips, go https://github.com/anyplots/cloud-chia-plotter 4 | 5 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 6 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 7 | 8 | 9 | 10 | $path = (Split-Path -Parent $MyInvocation.MyCommand.Definition) + "\CloudChiaPlotter.exe" 11 | $tmppath = (Split-Path -Parent $MyInvocation.MyCommand.Definition) + "\CloudChiaPlotter.tmp" 12 | 13 | if(Test-Path $path ) 14 | { 15 | echo "CloudChiaPlotter.exe already was downloaded, if you want to update it, please delete it and run again." 16 | } 17 | else 18 | { 19 | echo "CloudChiaPlotter.exe Downloading" 20 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 21 | Invoke-WebRequest -Uri https://github.com/anyplots/cloud-chia-plotter/releases/download/v3/cloud-chia-plotter-v3-win-x64.exe -Outfile "$tmppath" 22 | Rename-Item -Path "$tmppath" -NewName "$path" 23 | } 24 | 25 | 26 | $objForm = New-Object System.Windows.Forms.Form 27 | $objForm.Text = "Cloud Chia Plotter" 28 | $objForm.Size = New-Object System.Drawing.Size(400,250) 29 | $objForm.StartPosition = "CenterScreen" 30 | $objForm.KeyPreview = $True 31 | 32 | $objTokenLabel = New-Object System.Windows.Forms.Label 33 | $objTokenLabel.Location = New-Object System.Drawing.Size(10,20) 34 | $objTokenLabel.Size = New-Object System.Drawing.Size(280,20) 35 | $objTokenLabel.Text = "Enter your project token 40 chars:" 36 | $objForm.Controls.Add($objTokenLabel) 37 | 38 | 39 | $objTokenText = New-Object System.Windows.Forms.TextBox 40 | $objTokenText.Location = New-Object System.Drawing.Size(10,40) 41 | $objTokenText.Size = New-Object System.Drawing.Size(360,20) 42 | $objTokenText.Text = "" 43 | $objForm.Controls.Add($objTokenText) 44 | 45 | 46 | $objDirsLabel = New-Object System.Windows.Forms.Label 47 | $objDirsLabel.Location = New-Object System.Drawing.Size(10,70) 48 | $objDirsLabel.Size = New-Object System.Drawing.Size(280,20) 49 | $objDirsLabel.Text = "Select some folders to save your plots:" 50 | $objForm.Controls.Add($objDirsLabel) 51 | 52 | 53 | $objDirsText = New-Object System.Windows.Forms.TextBox 54 | $objDirsText.Location = New-Object System.Drawing.Size(10,90) 55 | $objDirsText.Size = New-Object System.Drawing.Size(260,20) 56 | $objDirsText.Text = "" 57 | $objForm.Controls.Add($objDirsText) 58 | 59 | 60 | $DirsButton = New-Object System.Windows.Forms.Button 61 | $DirsButton.Location = New-Object System.Drawing.Size(280,90) 62 | $DirsButton.Size = New-Object System.Drawing.Size(90,23) 63 | $DirsButton.Text = "Add Folder ..." 64 | $DirsButton.Add_Click({ 65 | $objFolderForm = New-Object System.Windows.Forms.FolderBrowserDialog 66 | $objFolderForm.Description = "Select a folder to save chia plots:" 67 | if($objFolderForm.ShowDialog() -eq "OK"){ 68 | if($objFolderForm.SelectedPath.Contains(" ")){ 69 | [System.Windows.Forms.MessageBox]::Show("the selected path contains blank space, it was not supported: " + $objFolderForm.SelectedPath) 70 | return 71 | } 72 | if($objDirsText.Text -eq ""){ 73 | $objDirsText.Text += $objFolderForm.SelectedPath; 74 | } 75 | else{ 76 | $objDirsText.Text += ";" + $objFolderForm.SelectedPath; 77 | } 78 | } 79 | }) 80 | $objForm.Controls.Add($DirsButton) 81 | 82 | 83 | $objBandwidthLabel = New-Object System.Windows.Forms.Label 84 | $objBandwidthLabel.Location = New-Object System.Drawing.Size(10,120) 85 | $objBandwidthLabel.Size = New-Object System.Drawing.Size(380,20) 86 | $objBandwidthLabel.Text = "Set your ""real"" network bandwidth, such as ""500"" for 500Mbps" 87 | $objForm.Controls.Add($objBandwidthLabel) 88 | $objBandwidthLabel2 = New-Object System.Windows.Forms.Label 89 | $objBandwidthLabel2.Location = New-Object System.Drawing.Size(115,143) 90 | $objBandwidthLabel2.Size = New-Object System.Drawing.Size(40,20) 91 | $objBandwidthLabel2.Text = "Mbps" 92 | $objForm.Controls.Add($objBandwidthLabel2) 93 | 94 | 95 | $objBandwidthText = New-Object System.Windows.Forms.TextBox 96 | $objBandwidthText.Location = New-Object System.Drawing.Size(10,140) 97 | $objBandwidthText.Size = New-Object System.Drawing.Size(100,20) 98 | $objBandwidthText.Text = "" 99 | $objForm.Controls.Add($objBandwidthText) 100 | 101 | $OKButton = New-Object System.Windows.Forms.Button 102 | $OKButton.Location = New-Object System.Drawing.Size(150,170) 103 | $OKButton.Size = New-Object System.Drawing.Size(75,23) 104 | $OKButton.Text = "Start Now" 105 | $OKButton.Add_Click({ 106 | if( $objTokenText.Text.Length -ne 40){ 107 | [System.Windows.Forms.MessageBox]::Show("invalid project token") 108 | return; 109 | } 110 | if( $objDirsText.Text.Length -eq 0){ 111 | [System.Windows.Forms.MessageBox]::Show("you must add at least one directory to save your plots") 112 | return; 113 | } 114 | if( $objBandwidthText.Text.Length -eq 0){ 115 | [System.Windows.Forms.MessageBox]::Show("you must set your real network bandwidth, you can test your speed at https://speedtest.net") 116 | return; 117 | } 118 | $objForm.DialogResult = "OK" 119 | $objForm.Close() 120 | }) 121 | $objForm.Controls.Add($OKButton) 122 | 123 | echo "Waiting for set parameters ..." 124 | $objForm.Add_Shown({$objForm.Activate()}) 125 | if($objForm.ShowDialog() -eq "OK"){ 126 | $args1 = $objTokenText.Text 127 | $args2 = $objDirsText.Text 128 | $args3 = $objBandwidthText.Text 129 | echo "path: $path" 130 | echo "-p $args1" 131 | echo "-d $args2" 132 | echo "-b $args3" 133 | Start-Process -NoNewWindow -Wait -FilePath "$path" -ArgumentList "-p $args1","-d $args2","-b $args3" 134 | } 135 | else{ 136 | echo "Cancelled" 137 | } 138 | pause -------------------------------------------------------------------------------- /step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyplots/cloud-chia-plotter/c6dfbbcf9e8373392876a24816707394937b164d/step1.png -------------------------------------------------------------------------------- /step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyplots/cloud-chia-plotter/c6dfbbcf9e8373392876a24816707394937b164d/step2.png -------------------------------------------------------------------------------- /step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyplots/cloud-chia-plotter/c6dfbbcf9e8373392876a24816707394937b164d/step3.png -------------------------------------------------------------------------------- /step4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyplots/cloud-chia-plotter/c6dfbbcf9e8373392876a24816707394937b164d/step4.png -------------------------------------------------------------------------------- /windows-gui.md: -------------------------------------------------------------------------------- 1 | A PowerShell script GUI for windows series, download it at https://github.com/anyplots/cloud-chia-plotter/releases/download/v3/gui.ps1 2 |

3 | How to run the gui.ps1? see the following screen: right click it, it will show the right menu, then click the "Run with PowerShell" option. 4 | 5 |

6 | When it starting, it will auto download the CloudChiaPlotter.exe 7 | 8 |

9 | After download the CloudChiaPlotter.exe, it will show a setup window, setup it with your own parameters. 10 | 11 |

12 | Now, It's running, It will auto download all plots related to your project, just keep it running. 13 | 14 |

15 | --------------------------------------------------------------------------------